Spring常见面试题

1.spring循环依赖

1.普通bean生成过程

  • getBean 依次从一级缓存,二级缓存,三级缓存中找。如果一级缓存中没有并且正在被创建(当在缓存中没有找到时会标记为正在被创建)才会去二级缓存中找,如果二级缓存中没有并且bean运行循环引用才会去三级缓存中找。
  • createBean
  • doCreateBean
  • 实例化,将bean的ObjectFactory对象放入三级缓存(如果没有循环依赖,二级缓存和三级缓存都没有作用)
  • 属性注入
  • 后置处理器的前置方法
  • 初始化
  • 后置处理器的后置方法(如果要生成代理,这里会生成代理对象)放入一级缓存singletonObjeacts
  • 销毁
    2.对于存在循环依赖但不存在aop的情况
    假设A中聚合了B对象,B中聚合了A对象
  • getBean(A)
  • createBean(A)
  • doCreateBean(A)
  • 实例化,将bean的ObjectFactory对象放入三级缓存
  • 属性注入发现依赖于B,执行getBean(B)
  • 三级缓存中都没有B,因此执行createBean(B)
  • doCreateBean(B)
  • 属性注入,发现依赖于A,执行getBean(A)
  • 在三级缓存中找到了半成品的A对象,执行三级缓存中存放的lambda表达式,并将返回的bean放入二级缓存,删除三级缓存中的ObjectFactory,并返回
  • 完成B的属性注入,初始化,最终将B放入一级缓存singletoObjects
  • 完成A的属性注入,初始化,最终将A放入一级缓存

2.对于存在循环依赖和aop的情况
假设A中聚合了B对象,B中聚合了A对象,且A对象需要aop

  • getBean(A)
  • createBean(A)
  • doCreateBean(A)
  • 实例化,将bean的ObjectFactory对象(一个lambda表达式)放入三级缓存
  • 属性注入发现依赖于B,执行getBean(B)
  • 三级缓存中都没有B,因此执行createBean(B)
  • doCreateBean(B)
  • 属性注入,发现依赖于A,执行getBean(A)
  • 在三级缓存中找到了半成品的A对象,执行三级缓存中存放的lambda表达式,这里会将原始的A对象放入一个earlyProxyReference的map中,表示里面的bean执行了aop,但里面存放的依然是原始对象,不是执行了aop的对象。并将代理对象放入二级缓存,删除三级缓存中的ObjectFactory,并返回A
  • 完成B的属性注入,初始化,最终将B放入一级缓存singletoObjects
  • 完成A的属性注入,初始化
  • 当A要执行aop的时候,进行判断,如果earlyProxyReference中存在原始对象,就不再执行aop
  • 将A放入一级缓存
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 从一级缓存获取,key=beanName value=bean
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                // 从二级缓存获取,key=beanName value=bean
                singletonObject = this.earlySingletonObjects.get(beanName);
                // 是否允许循环引用
                if (singletonObject == null && allowEarlyReference) {
                    /**
                     * 三级缓存获取,key=beanName value=objectFactory,objectFactory中存储getObject()方法用于获取提前曝光的实例
                     *
                     * 而为什么不直接将实例缓存到二级缓存,而要多此一举将实例先封装到objectFactory中?
                     * 主要关键点在getObject()方法并非直接返回实例,而是对实例又使用
                     * SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法对bean进行处理
                     *
                     * 也就是说,当spring中存在该后置处理器,所有的单例bean在实例化后都会被进行提前曝光到三级缓存中,
                     * 但是并不是所有的bean都存在循环依赖,也就是三级缓存到二级缓存的步骤不一定都会被执行,有可能曝光后直接创建完成,没被提前引用过,
                     * 就直接被加入到一级缓存中。因此可以确保只有提前曝光且被引用的bean才会进行该后置处理
                     */
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        /**
                         * 通过getObject()方法获取bean,通过此方法获取到的实例不单单是提前曝光出来的实例,
                         * 它还经过了SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法处理过。
                         * 这也正是三级缓存存在的意义,可以通过重写该后置处理器对提前曝光的实例,在被提前引用时进行一些操作
                         */
                        singletonObject = singletonFactory.getObject();
                        // 将三级缓存生产的bean放入二级缓存中
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        // 删除三级缓存
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

2.对IOC AOP的理解

IOC

  • IoC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。
  • 当我们需要一个对象时,只要注入进来.否则一个对象可能依赖许多对象,这样创建对象会造成很大的麻烦
  • 依赖倒置 当存在A依赖于B,B依赖于C,C依赖于D 那么D的改动会让依赖于它的类都要改动.而使用IOC只需要将改动后的D放入IOC容器中
  • 降低耦合度 不同IOC就类似于许多耦合在一起的齿轮,而IOC就相当于一个容器,与每个bean只有一条连接线
    AOP
    将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

3.spring bean的生命周期

4.spring IOC

5.spring AOP

使用
  1. 导入spring-aspects依赖,定义业务类和切面类

依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.8</version>
 </dependency>
//其他依赖
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>5.3.8</version>
</dependency>

业务类:

public class MathCalculate {
    public int div(int i,int j){
        System.out.println("div方法执行内部");
        return i/j;
    }
}

切面类:

@Aspect
public class LogAspect {

    //如果在其他类使用该切面,可以加修饰符,再加该函数
    @Pointcut("execution(public int com.atguigu.bean.MathCalculate.*(..))")
    public void pointCut(){
    }


    @Before("execution(public int com.atguigu.bean.MathCalculate.div(int,int))")
    //@Before("pointCut()")
    public void before(){
        System.out.println("执行前。。。");
    }

    @After("pointCut()")
    public void after(){
        System.out.println("执行后。。。");
    }

    @AfterReturning("pointCut()")
    public void afterReturning(){
        System.out.println("执行返回。。。");
    }

    @AfterThrowing("pointCut()")
    public void afterThrowing(){
        System.out.println("执行异常。。。");
    }
}
  1. 抽取切入点,@PointCut,加前置通知、后置通知、返回通知、异常通知
  2. 将业务逻辑类和切面类加入到容器中
  3. 告诉spring哪个是切面类 @Aspect
  4. 开启基于注解版的切面功能@EnableAspectJAutoProxy要标注在配置类,而不是切面类
aop中的一些概念

目标对象target:被增强的对象。

代理类:增强后的对象。

通知Advice:就是增强类,增强代码所在的类。

织入:是个动词,就是把增强的代码织入到被代理对象的过程

连接点:目标对象中的每一个方法都是连接点。

切入点:目标对象中被增强的方法是切入点。

切面:切面就是切入点和通知的组合。

原理
  1. 导入组件
  • @EnableAspectJAutoProxy通过@Import给容器中添加了一个类AspectJAutoProxyRegistrar
  • 该类实现了ImportBeanDefinitionRegistrar接口,该接口的作用也是给容器中添加组件,实际是添加了AnnotationAwareAspectJAutoProxy
  • AnnotationAwareAspectJAutoProxy类实现了BeanFactoryAwareSmartInstantiationAwareBeanPostProcessor接口
  1. 先将AnnotationAwareAspectJAutoProxy加入到容器中
    在spring容器启动过程中,有一个阶段是注册Bean的后置处理器的,即registerBeanPostProcessors(beanFactory).在这个阶段会将完成所有后置处理器的Bean的实例化,初始化,最后放入到IOC容器中
  2. 执行 SmartInstantiationAwareBeanPostProcessor接口的postProcessBeforeInstantiation方法
    在spring容器启动过程中的finishBeanFactoryInitialization(beanFactory)阶段,在Bean被调用doCreateBean之前,会执行该方法。该方法的作用是:
    判断当前bean是否是基础类型 Advice PointCut Advisor AopInfrastructureBean @Aspect 如果是就加入到advisedBean集合中。
  3. 执行 SmartInstantiationAwareBeanPostProcessor接口的postProcessAfterInitialization方法
  • 如果earlyProxyReference中没有原始Bean,则执行AOP,即return wrapIfNecessary
    获取当前bean的增强器(通知方法)
  • 找到所有的增强器
    List<Advisor> candidateAdvisors 找到能在当前bean使用的候选的增强器(找那些方法需要切入当前bean)
  • 找到能在当前Bean使用的增强器
    List<Advisor> eligibleAdvisors获取到能在当前bean使用的增强器(这里根据切入点表达式进行判断)
  • 给增强器排序
  • 保存当前bean在advisedBeans
  • 如果需要增强创建代理对象(即有可以使用的增强器),则执行创建代理的逻辑
  • 将所有增强器保存在代理工厂proxyFactory中,并使用代理工厂创建代理proxyFactory.getProxy 调用 createAopProxy.getProxy 创建代理对象
  • spring自动决定jdk代理对象和cglib代理对象,给容器中返回当前bean的代理对象
  1. 获取拦截器链
    对于需要切入的方法,获取该方法的拦截器链.
    获取拦截器链的过程:
  • 获取5个增强器,一个默认的,其他的都是通知方法
  • 遍历增强器,封装成拦截器
  • 如果增强器是MethodInvocation,直接加入,否则使用AdvisorAdapter转为MethodInterceptor( before 和 afterReturning是需要转的)
    如果没有拦截器链,直接执行目标方法。如果有拦截器链,把需要执行的拦截器链,目标方法,目标对象传入创建CglibMethodInvocation,并调用它的proceed方法
  1. 拦截器链的执行
    首先拦截器链中共有5个拦截器
    拦截器链.png

    执行过程:
    image.png
  • 使用currentInterceptorIndex记录拦截器的索引,初始值为-1,如果拦截器链中拦截器的个数是0,那么会直接执行目标方法
  • 当前长度len -1 = 4 != -1,index++,index = 0执行ExposeInvocationInterceptor的invoke方法
  • ExposeInvocationInterceptor的invoke主要用来存储CglibMethodInvocation到threadlocal里面,以便后续可以取出来,并继续调用CglibMethodInvocation的proceed方法
  • 当前长度len - 1= 4 != 0,index++,index = 1执行异常通知的invoke方法
  • 异常通知的invoke方法先执行mi.proceed,执行过程中有异常,则执行异常通知
  • 当前长度len -1 = 4 != 1,index++,index = 2执行返回通知的invoke方法
  • 异常通知的invoke方法先执行mi.proceed,再执行返回通知
  • 当前长度len -1 = 4 != 2,index++,index = 3执行后置通知的invoke方法
  • 后置通知的invoke方法先执行mi.proceed,再执行后置通知
  • 当前长度len -1 = 4 != 3,index++,index = 4执行前置通知的invoke方法
  • 前置通知的invoke方法先执行前置通知输出,再执行mi.proceed
  • 当前长度len -1 = 4 != 4,执行目标方法
  • 后置通知执行完了mi.proceed,继续执行后置通知
  • 返回通知执行完了mi.proceed,继续执行返回通知
  • 异常通知执行完了mi.proceed,如果存在异常继续执行异常通知
public Object proceed() throws Throwable {
        // We start with an index of -1 and increment early.
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }

        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            // Evaluate dynamic method matcher here: static part will already have
            // been evaluated and found to match.
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
            if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
                return dm.interceptor.invoke(this);
            }
            else {
                // Dynamic matching failed.
                // Skip this interceptor and invoke the next in the chain.
                return proceed();
            }
        }
        else {
            // It's an interceptor, so we just invoke it: The pointcut will have
            // been evaluated statically before this object was constructed.
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }

ExposeInvocationInterceptor的invoke方法

    public Object invoke(MethodInvocation mi) throws Throwable {
        MethodInvocation oldInvocation = invocation.get();
        invocation.set(mi);
        try {
            return mi.proceed();
        }
        finally {
            invocation.set(oldInvocation);
        }
    }

MethodBeforeAdviceInterceptor的invoke方法

    public Object invoke(MethodInvocation mi) throws Throwable {
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        return mi.proceed();
    }

AspectJAfterAdvice的invoke方法

    public Object invoke(MethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        }
        finally {
            invokeAdviceMethod(getJoinPointMatch(), null, null);
        }
    }

AfterReturningAdviceInterceptor的invoke方法

    public Object invoke(MethodInvocation mi) throws Throwable {
        Object retVal = mi.proceed();
        this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
        return retVal;
    }

AspectJAfterThrowingAdvice的invoke方法

    public Object invoke(MethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        }
        catch (Throwable ex) {
            if (shouldInvokeOnThrowing(ex)) {
                invokeAdviceMethod(getJoinPointMatch(), null, ex);
            }
            throw ex;
        }
    }

6.@Transactional

image.png
  1. NEVER
    如果laoda@Transactional,那么直接抛出不允许laoda方法有@Transactional注解的异常。如果如果laoda没有@Transactional,则以非事务方式执行。
    使用propagation = Propagation.NEVER的意义在于如果不标准@Transaction会将xiaodi当作laoda的函数中的一部分,整体放入一个事务中执行。
@Transactional(propagation = Propagation.REQUIRED)
    public void laoda(Money from, Money to, int count){
        from.sub(count);
        try{
            test.xiaodi(to,count);
        }catch (Exception e){
            e.printStackTrace();
        }

        int x = 10;
        if(x == 10)
            throw new RuntimeException("老大异常!");
    }

    @Transactional(propagation = Propagation.NEVER)
    public void xiaodi(Money to,int count){
        to.add(count);
        int x = 10;
        if(x == 10)
            throw new RuntimeException("小弟异常!");
    }

2.NOT_SUPPORTED
laoda没有事务就以非事务执行,有就先将laoda的事务挂起,自己以非事务执行,执行结束后回去执行laoda的事务。下面的例子中,由于xiaodi没有事务,因此转入成功,由于laoda有事务,因此回滚。

@Transactional(propagation = Propagation.REQUIRED)
    public void laoda(Money from, Money to, int count){
        from.sub(count);
        try{
            test.xiaodi(to,count);
        }catch (Exception e){
            e.printStackTrace();
        }

        int x = 10;
        if(x == 10)
            throw new RuntimeException("老大异常!");
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void xiaodi(Money to,int count){
        to.add(count);
        int x = 10;
        if(x == 10)
            throw new RuntimeException("小弟异常!");
    }

3.SUPPORTS
laoda没有事务就以非事务执行,laoda有事务就加入。因此下面代码由于xiaodi有异常造成laodaxiaodi都回滚。

@Transactional(propagation = Propagation.REQUIRED)
    public void laoda(Money from, Money to, int count){
        from.sub(count);
        try{
            test.xiaodi(to,count);
        }catch (Exception e){
            e.printStackTrace();
        }

        int x = 9;
        if(x == 10)
            throw new RuntimeException("老大异常!");
    }

    @Transactional(propagation = Propagation.SUPPORTES)
    public void xiaodi(Money to,int count){
        to.add(count);
        int x = 10;
        if(x == 10)
            throw new RuntimeException("小弟异常!");
    }

4.REQUIRES_NEW
laoda没有新事务,则新建一个事务。laoda有事务,则xiaodi新建一个事务,将laoda的事务挂起,自己执行完后laoda再执行。这种情况是两个事务,回滚与否互不影响。

@Transactional(propagation = Propagation.REQUIRED)
    public void laoda(Money from, Money to, int count){
        from.sub(count);
        try{
            test.xiaodi(to,count);
        }catch (Exception e){
            e.printStackTrace();
        }

        int x = 9;
        if(x == 10)
            throw new RuntimeException("老大异常!");
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void xiaodi(Money to,int count){
        to.add(count);
        int x = 10;
        if(x == 10)
            throw new RuntimeException("小弟异常!");
    }

5.NESTED
如果laoda没有事务,则新建事务。如果laoda有事务,则在laoda嵌套当前事务。即如果外层事务laoda回滚,那么内层事务xiaodi也会回滚,但如果内层事务xiaodi回滚,外层事务不会回滚。

@Transactional(propagation = Propagation.REQUIRED)
    public void laoda(Money from, Money to, int count){
        from.sub(count);
        try{
            test.xiaodi(to,count);
        }catch (Exception e){
            e.printStackTrace();
        }

        int x = 9;
        if(x == 10)
            throw new RuntimeException("老大异常!");
    }

    @Transactional(propagation = Propagation.NESTED)
    public void xiaodi(Money to,int count){
        to.add(count);
        int x = 10;
        if(x == 10)
            throw new RuntimeException("小弟异常!");
    }

6.REQUIRED
没有就新建,有就加入(即在同一个事务中)

@Transactional(propagation = Propagation.REQUIRED)
    public void laoda(Money from, Money to, int count){
        from.sub(count);
        try{
            test.xiaodi(to,count);
        }catch (Exception e){
            e.printStackTrace();
        }

        int x = 9;
        if(x == 10)
            throw new RuntimeException("老大异常!");
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void xiaodi(Money to,int count){
        to.add(count);
        int x = 10;
        if(x == 10)
            throw new RuntimeException("小弟异常!");
    }

7.MANDATORY

@Transactional(propagation = Propagation.REQUIRED)
    public void laoda(Money from, Money to, int count){
        from.sub(count);
        try{
            test.xiaodi(to,count);
        }catch (Exception e){
            e.printStackTrace();
        }

        int x = 9;
        if(x == 10)
            throw new RuntimeException("老大异常!");
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public void xiaodi(Money to,int count){
        to.add(count);
        int x = 10;
        if(x == 10)
            throw new RuntimeException("小弟异常!");
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容