深入分析大厂面试题四

1 Spring高级部分

1.1 spring的aop顺序

  • 你肯定知道spring,那说说aop的全部通知顺序 springboot或springboot2对aop的执行顺序影响?
  • 说说你使用aop中碰到的坑

1.1.1 Aop常用注解

@Before  前置通知: 目标方法之前执行
@After   后置通知: 目标方法之后执行(始终执行)
@AfterReturning 返回后通知: 执行方法结束前执行(异常不执行)
@AfterThrowing  异常通知: 出现异常时候执行
@Around  环绕通知: 环绕目标方法执行

1.1.2 业务类

//接口CalcService
public interface CalcService {
    public int div(int x,int y);
}

//接口实现类CalcServicelmpl
import org.springframework.stereotype.Service;

@Service
public class CalcServiceImpl implements CalcService {

    @Override
    public int div(int x, int y) {
        int result = x / y;
        System.out.println("=========>CalcServiceImpl被调用了,我们的计算结果:"+result);
        return result;

    }
}
//想在除法方法前后各种通知,引入切面编程

新建一个切面类MyAspect并为切面类新增两个注解

  • @Aspect 指定一个类为切面类
  • @Component 纳入spring容器管理
@Aspect
@Component
public class MyAspect {
    
    @Before("execution(public int com.xubh.study.service.impl.CalcServiceImpl.*(..))")
    public void beforeNotify() {
        System.out.println("******** @Before我是前置通知MyAspect");
    }

    @After("execution(public int com.xubh.study.service.impl.CalcServiceImpl.*(..))")
    public void afterNotify() {
        System.out.println("******** @After我是后置通知");
    }

    @AfterReturning("execution(public int com.xubh.study.service.impl.CalcServiceImpl.*(..))")
    public void afterReturningNotify() {
        System.out.println("********@AfterReturning我是返回后通知");
    }

    @AfterThrowing("execution(public int com.xubh.study.service.impl.CalcServiceImpl.*(..))")
    public void afterThrowingNotify() {
        System.out.println("********@AfterThrowing我是异常通知");
    }

    @Around("execution(public int com.xubh.study.service.impl.CalcServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object retValue = null;
        System.out.println("我是环绕通知之前AAA");
        retValue = proceedingJoinPoint.proceed();
        System.out.println("我是环绕通知之后BBB");
        return retValue;
    }
}

1.1.3 Spring4+springboot1.5.x

@SpringBootTest
@RunWith(SpringRunner.class)
public class T1{
    @Autowired
    private CalcService service;
 
    System.out.println("spring版本:"+ SpringVersion.getVersion()+"\t"+"SpringBoot版本:"+     SpringBootVersion.getVersion());

    System.out.println();
    calcService.div(10,2);
}

aop正常顺序+异常顺序

@Before
method.invode(obj, args);
@AfterReturning
{catch(
@AfterThrowing
)}
@After

正常

image-20210122143701670.png

异常

image-20210122143719917.png

spring4默认用的是JDK的动态代理

1.1.4 Spring5+springboot2.3.x

aop正常顺序+异常顺序

spring5--aop正常流程

image-20210122143840875.png

spring5--aop异常流程

image-20210122143915081.png

spring5默认动态代理用的是cglib,不再是JDK的动态代理,因为JDK必须要实现接口,但有些类它并没有实现接口,所以更加通用的话就是cglib

1.1.5 总结

image-20210122144014724.png

1.2 spring的循环依赖

  • 你解释下spring中的三级缓存?
  • 三级缓存分别是什么?三个Map有什么异同?
  • 什么是循环依赖?请你谈谈?看过spring源码吗?一般我们说的spring容器是什么
  • 如何检测是否存在循环依赖?实际开发中见过循环依赖的异常吗?
  • 多例的情况下,循环依赖问题为什么无法解决?

1.2.1 什么是循环依赖

多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A

public class T1 {
    class A {
        B b;
    }
    class B { 
        C c;
    }
    class C {
        A a;
    }
}

比如:A依赖于B、B依赖于C、C依赖于A

通常来说,如果问spring容器内部如何解决循环依赖, 一定是指默认的单例Bean中,属性互相引用的场景

image-20210122144611965.png

也就是说,Spring的循环依赖,是Spring容器注入时候出现的问题

1.2.2 两种注入方式对循环依赖的影响

循环依赖官网说明

https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans

image-20210122144707814.png

我们AB循环依赖问题只要A的注入方式是setter且singleton, 就不会有循环依赖问题

1.2.3 spring容器循环依赖报错BeanCurrentlylnCreationException

image-20210122145608897.png

循环依赖现象在Spring容器中 注入依赖的对象,有2种情况

构造器方式注入依赖

@Component
public class ServiceA {

    private ServiceB serviceB;

    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}


@Component
public class ServiceB {

    private ServiceA serviceA;

    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}
 
/**
 * 通过构造器的方式注入依赖,构造器的方式注入依赖的bean,下面两个bean循环依赖
 *
 * 测试后发现,构造器循环依赖是无法解决的
 */
public class ClientConstructor {
    public static void main(String[] args) {
        new ServiceA(new ServiceB(new ServiceA(new ServiceB()))); ....
    }
}

构造器注入没有办法解决循环依赖, 你想让构造器注入支持循环依赖,是不存在的

以set方式注入依赖

@Component
public class ServiceA {

    private ServiceB serviceB;

    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
        System.out.println("A 里面设置了B");
    }
}

@Component
public class ServiceB {

    private ServiceA serviceA;

    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
        System.out.println("B 里面设置了A");
    }
}

public class ClientSet {
    public static void main(String[] args) {

        //创建serviceA
        ServiceA serviceA = new ServiceA();

        //创建serviceB
        ServiceB serviceB = new ServiceB();

        //将serviceA注入到serviceB中
        serviceB.setServiceA(serviceA);

        //将serviceB注入到serviceA中
        serviceA.setServiceB(serviceB);

    }
}

code-java基础编码

public class A {
    private B b;

    public B getB(){
        return b;
    }

    public void setB(B b){
        this.b = b;
    }

    public A(){
        System.out.println("---A created success");
    }
}

public class B {
    private A a;

    public A getA(){
        return a;
    }

    public void setA(A a){
        this.a = a;
    }


    public B(){
        System.out.println("---B created success");
      
    }
}
 
public class ClientCode {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();

        a.setB(b);
        b.setA(a);
    }
}

默认的单例(singleton)的场景是支持循环依赖的,不报错

原型(Prototype)的场景是不支持循环依赖的,报错

spring内部通过3级缓存来解决循环依赖

DefaultSingletonBeanRegistry

只有单例的Bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所有非单例的bean是没有缓存的,不会将其放到三级缓存中。

第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象
第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
第三级缓存: Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂
image-20210122145721952.png

所谓的三级缓存其实就是spring容器内部用来解决循环依赖问题的三个map

1.2.4 循环依赖Debug

  • 实例化 堆内存中申请一块内存空间
  • 初始化 属性填充 完成属性的各种赋值

3大Map和四大方法,总体相关对象

image-20210122145958153.png
三级缓存+四大方法
                                                             1.getSingleton:希望从容器里面获得单例的bean,没有的话
                                                             2.doCreateBean: 没有就创建bean
                                                             3.populateBean: 创建完了以后,要填充属性
                                                             4.addSingleton: 填充完了以后,再添加到容器进行使用
                                                             
第一层singletonObjects存放的是已经初始化好了的Bean,
第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean,
第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean
image-20210122150056564.png

A/B两对象在三级缓存中的迁移说明

1 A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
 
2 B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A
然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
 
3 B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

@FunctionalInterface
public interface ObjectFactory<T> {

    /**
     * Return an instance (possibly shared or independent)
     * of the object managed by this factory.
     * @return the resulting instance
     * @throws BeansException in case of creation errors
     */
    T getObject() throws BeansException;

}

1.2.5 总结

Spring创建bean主要分为两个步骤,创建原始bean对象,接着去填充对象属性和初始化

每次创建bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个

当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完 beanB填充属性时又发现它依赖了beanA又是同样的流程,
不同的是:
这时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入beanB,完成beanB的创建

既然 beanB创建好了,所以beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成
image-20210122150710112.png
Spring解决循环依赖依靠的是Bean的“中间态"这个概念,而这个中间态指的是已经实例化但还没初始化的状态……>半成品。
实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。
 
Spring为了解决单例的循环依赖问题,使用了三级缓存
其中一级缓存为单例池〈 singletonObjects)
二级缓存为提前曝光对象( earlySingletonObjects)
三级缓存为提前曝光对象工厂( singletonFactories)。
 
假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A,没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化,这时候B完成后,就去完成剩下的A的步骤,如果有AOP代理,就进行AOP处理获取代理后的对象A,注入B,走剩下的流程。

spring解决循环依赖的整个流程图

Debug的步骤---->Spring解决循环依赖过程

1 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
2 在getSingleton()方法中,从一级缓存中查找,没有,返回null
3 doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
4 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法
5 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断。是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中〈即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中
6 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB
7 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性
8 此时beanB依赖于beanA,调用getsingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
9 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中
10 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容