04_2、【java面试 - 框架篇】2. Spring bean

130 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

一. Spring bean 生命周期

要求

  • 掌握 Spring bean 的生命周期

bean 生命周期 概述

bean 的生命周期从调用 beanFactory 的 getBean 开始,到这个 bean 被销毁,可以总结为以下七个阶段:

  1. 处理名称,检查缓存

  2. 处理父子容器

  3. 处理 dependsOn

  4. 选择 scope 策略

  5. 创建 bean

  6. 类型转换处理

  7. 销毁 bean

****** **注意 ****

  • 划分的阶段和名称并不重要,重要的是理解整个过程中做了哪些事情

1. 处理名称,检查缓存

  • 这一步会处理别名,将别名解析为实际名称

  • 对 FactoryBean 也会特殊处理,如果以 & 开头表示要获取 FactoryBean 本身,否则表示要获取其产品

  • 这里针对单例对象会检查一级、二级、三级缓存

  * singletonFactories 三级缓存,存放单例工厂对象

  * earlySingletonObjects 二级缓存,存放单例工厂的产品对象

    * 如果发生循环依赖,产品是代理;无循环依赖,产品是原始对象

  * singletonObjects 一级缓存,存放单例成品对象

2. 处理父子容器

  • 如果当前容器根据名字找不到这个 bean,此时若父容器存在,则执行父容器的 getBean 流程

  • 父子容器的 bean 名称可以重复

3. 处理 dependsOn

  • 如果当前 bean 有通过 dependsOn 指定了非显式依赖的 bean,这一步会提前创建这些 dependsOn 的 bean 

  • 所谓非显式依赖,就是指两个 bean 之间不存在直接依赖关系,但需要控制它们的创建先后顺序

4. 选择 scope 策略

  • 对于 singleton scope,首先到单例池去获取 bean,如果有则直接返回,没有再进入创建流程

  • 对于 prototype scope,每次都会进入创建流程

  • 对于自定义 scope,例如 request,首先到 request 域获取 bean,如果有则直接返回,没有再进入创建流程

5.1 创建 bean - 创建 bean 实例

| 要点                             | 总结                                                     |

| ------------------------------------ | ------------------------------------------------------------ |

| 有自定义 TargetSource 的情况         | 由 AnnotationAwareAspectJAutoProxyCreator 创建代理返回       |

| Supplier 方式创建 bean 实例          | 为 Spring 5.0 新增功能,方便编程方式创建  bean  实例         |

| FactoryMethod 方式  创建 bean  实例  | ① 分成静态工厂与实例工厂;② 工厂方法若有参数,需要对工厂方法参数进行解析,利用  resolveDependency;③ 如果有多个工厂方法候选者,还要进一步按权重筛选 |

| AutowiredAnnotationBeanPostProcessor | ① 优先选择带  @Autowired  注解的构造;② 若有唯一的带参构造,也会入选 |

| mbd.getPreferredConstructors         | 选择所有公共构造,这些构造之间按权重筛选                     |

| 采用默认构造                         | 如果上面的后处理器和 BeanDefiniation 都没找到构造,采用默认构造,即使是私有的 |

5.2 创建 bean - 依赖注入

| 要点                             | 总结                                                     |

| ------------------------------------ | ------------------------------------------------------------ |

| AutowiredAnnotationBeanPostProcessor | 识别   @Autowired  及 @Value  标注的成员,封装为  InjectionMetadata 进行依赖注入 |

| CommonAnnotationBeanPostProcessor    | 识别   @Resource  标注的成员,封装为  InjectionMetadata 进行依赖注入 |

| resolveDependency                    | 用来查找要装配的值,可以识别:① Optional;② ObjectFactory 及 ObjectProvider;③ @Lazy  注解;④ @Value  注解(${  }, #{ }, 类型转换);⑤ 集合类型(Collection,Map,数组等);⑥ 泛型和  @Qualifier(用来区分类型歧义);⑦ primary  及名字匹配(用来区分类型歧义) |

| AUTOWIRE_BY_NAME                     | 根据成员名字找 bean 对象,修改 mbd 的 propertyValues,不会考虑简单类型的成员 |

| AUTOWIRE_BY_TYPE                     | 根据成员类型执行 resolveDependency 找到依赖注入的值,修改  mbd 的 propertyValues |

| applyPropertyValues                  | 根据 mbd 的 propertyValues 进行依赖注入(即xml中 <property name ref|value/>) |

5.3 创建 bean - 初始化

| 要点              | 总结                                                     |

| --------------------- | ------------------------------------------------------------ |

| 内置 Aware 接口的装配 | 包括 BeanNameAware,BeanFactoryAware 等                      |

| 扩展 Aware 接口的装配 | 由 ApplicationContextAwareProcessor 解析,执行时机在  postProcessBeforeInitialization |

| @PostConstruct        | 由 CommonAnnotationBeanPostProcessor 解析,执行时机在  postProcessBeforeInitialization |

| InitializingBean      | 通过接口回调执行初始化                                       |

| initMethod            | 根据 BeanDefinition 得到的初始化方法执行初始化,即 <bean init-method> 或 @Bean(initMethod) |

| 创建 aop 代理         | 由 AnnotationAwareAspectJAutoProxyCreator 创建,执行时机在  postProcessAfterInitialization |

5.4 创建 bean - 注册可销毁 bean

在这一步判断并登记可销毁 bean

  • 判断依据

  * 如果实现了 DisposableBean 或 AutoCloseable 接口,则为可销毁 bean

  * 如果自定义了 destroyMethod,则为可销毁 bean

  * 如果采用 @Bean 没有指定 destroyMethod,则采用自动推断方式获取销毁方法名(close,shutdown)

  * 如果有 @PreDestroy 标注的方法

  • 存储位置

  * singleton scope 的可销毁 bean 会存储于 beanFactory 的成员当中

  * 自定义 scope 的可销毁 bean 会存储于对应的域对象当中

  * prototype scope 不会存储,需要自己找到此对象销毁

  • 存储时都会封装为 DisposableBeanAdapter 类型对销毁方法的调用进行适配

6. 类型转换处理

  • 如果 getBean 的 requiredType 参数与实际得到的对象类型不同,会尝试进行类型转换

7. 销毁 bean

  • 销毁时机

  * singleton bean 的销毁在 ApplicationContext.close 时,此时会找到所有 DisposableBean 的名字,逐一销毁

  * 自定义 scope bean 的销毁在作用域对象生命周期结束时

  * prototype bean 的销毁可以通过自己手动调用 AutowireCapableBeanFactory.destroyBean 方法执行销毁

  • 同一 bean 中不同形式销毁方法的调用次序

  * 优先后处理器销毁,即 @PreDestroy

  * 其次 DisposableBean 接口销毁

  * 最后 destroyMethod 销毁(包括自定义名称,推断名称,AutoCloseable 接口 多选一)

二. Spring bean 循环依赖

要求

  • 掌握单例 set 方式循环依赖的原理

  • 掌握其它循环依赖的解决方法

循环依赖的产生

  • 首先要明白,bean 的创建要遵循一定的步骤,必须是创建、注入、初始化三步,这些顺序不能乱

image-20210903085238916.png

  • set 方法(包括成员变量)的循环依赖如图所示

  * 可以在【a 创建】和【a set 注入 b】之间加入 b 的整个流程来解决

  * 【b set 注入 a】 时可以成功,因为之前 a 的实例已经创建完毕

  * a 的顺序,及 b 的顺序都能得到保障

image-20210903085454603.png

  • 构造方法的循环依赖如图所示,显然无法用前面的方法解决

image-20210903085906315.png

构造循环依赖的解决

  • 思路1

  * a 注入 b 的代理对象,这样能够保证 a 的流程走通

  * 后续需要用到 b 的真实对象时,可以通过代理间接访问

image-20210903091627659.png

  • 思路2

  * a 注入 b 的工厂对象,让 b 的实例创建被推迟,这样能够保证 a 的流程先走通

  * 后续需要用到 b 的真实对象时,再通过 ObjectFactory 工厂间接访问

image-20210903091743366.png

  • 示例1:用 @Lazy 为构造方法参数生成代理

public class App60_1 {




    static class A {

        private static final Logger log = LoggerFactory.getLogger("A");

        private B b;




        public A(@Lazy B b) {

            log.debug("A(B b) {}", b.getClass());

            this.b = b;

        }




        @PostConstruct

        public void init() {

            log.debug("init()");

        }

    }




    static class B {

        private static final Logger log = LoggerFactory.getLogger("B");

        private A a;




        public B(A a) {

            log.debug("B({})", a);

            this.a = a;

        }




        @PostConstruct

        public void init() {

            log.debug("init()");

        }

    }




    public static void main(String[] args) {

        GenericApplicationContext context = new GenericApplicationContext();

        context.registerBean("a", A.class);

        context.registerBean("b", B.class);

        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());

        context.refresh();

        System.out.println();

    }

}

  • 示例2:用 ObjectProvider 延迟依赖对象的创建

public class App60_2 {




    static class A {

        private static final Logger log = LoggerFactory.getLogger("A");

        private ObjectProvider<B> b;




        public A(ObjectProvider<B> b) {

            log.debug("A({})", b);

            this.b = b;

        }




        @PostConstruct

        public void init() {

            log.debug("init()");

        }

    }




    static class B {

        private static final Logger log = LoggerFactory.getLogger("B");

        private A a;




        public B(A a) {

            log.debug("B({})", a);

            this.a = a;

        }




        @PostConstruct

        public void init() {

            log.debug("init()");

        }

    }




    public static void main(String[] args) {

        GenericApplicationContext context = new GenericApplicationContext();

        context.registerBean("a", A.class);

        context.registerBean("b", B.class);

        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());

        context.refresh();




        System.out.println(context.getBean(A.class).b.getObject());

        System.out.println(context.getBean(B.class));

    }

}

  • 示例3:用 @Scope 产生代理

public class App60_3 {




    public static void main(String[] args) {

        GenericApplicationContext context = new GenericApplicationContext();

        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context.getDefaultListableBeanFactory());

        scanner.scan("com.itheima.app60.sub");

        context.refresh();

        System.out.println();

    }

}


@Component

class A {

    private static final Logger log = LoggerFactory.getLogger("A");

    private B b;




    public A(B b) {

        log.debug("A(B b) {}", b.getClass());

        this.b = b;

    }




    @PostConstruct

    public void init() {

        log.debug("init()");

    }

}


@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)

@Component

class B {

    private static final Logger log = LoggerFactory.getLogger("B");

    private A a;




    public B(A a) {

        log.debug("B({})", a);

        this.a = a;

    }




    @PostConstruct

    public void init() {

        log.debug("init()");

    }

}

  • 示例4:用 Provider 接口解决,原理上与 ObjectProvider 一样,Provider 接口是独立的 jar 包,需要加入依赖

<dependency>

    <groupId>javax.inject</groupId>

    <artifactId>javax.inject</artifactId>

    <version>1</version>

</dependency>


public class App60_4 {




    static class A {

        private static final Logger log = LoggerFactory.getLogger("A");

        private Provider<B> b;




        public A(Provider<B> b) {

            log.debug("A({}})", b);

            this.b = b;

        }




        @PostConstruct

        public void init() {

            log.debug("init()");

        }

    }




    static class B {

        private static final Logger log = LoggerFactory.getLogger("B");

        private A a;




        public B(A a) {

            log.debug("B({}})", a);

            this.a = a;

        }




        @PostConstruct

        public void init() {

            log.debug("init()");

        }

    }




    public static void main(String[] args) {

        GenericApplicationContext context = new GenericApplicationContext();

        context.registerBean("a", A.class);

        context.registerBean("b", B.class);

        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());

        context.refresh();




        System.out.println(context.getBean(A.class).b.get());

        System.out.println(context.getBean(B.class));

    }

}

解决 set 循环依赖的原理

一级缓存

image-20210903100752165.png

作用是保证单例对象仅被创建一次

  • 第一次走 getBean("a") 流程后,最后会将成品 a 放入 singletonObjects 一级缓存

  • 后续再走 getBean("a") 流程时,先从一级缓存中找,这时已经有成品 a,就无需再次创建

一级缓存与循环依赖

image-20210903100914140.png

一级缓存无法解决循环依赖问题,分析如下

  • 无论是获取 bean a 还是获取 bean b,走的方法都是同一个 getBean 方法,假设先走 getBean("a")

  • 当 a 的实例对象创建,接下来执行 a.setB() 时,需要走 getBean("b") 流程,红色箭头 1

  • 当 b 的实例对象创建,接下来执行 b.setA() 时,又回到了 getBean("a") 的流程,红色箭头 2

  • 但此时 singletonObjects 一级缓存内没有成品的 a,陷入了死循环

二级缓存

image-20210903101849924.png

解决思路如下:

  • 再增加一个 singletonFactories 缓存

  • 在依赖注入前,即 a.setB() 以及 b.setA() 将 a 及 b 的半成品对象(未完成依赖注入和初始化)放入此缓存

  • 执行依赖注入时,先看看 singletonFactories 缓存中是否有半成品的对象,如果有拿来注入,顺利走完流程

对于上面的图

  • a = new A() 执行之后就会把这个半成品的 a 放入 singletonFactories 缓存,即 factories.put(a)

  • 接下来执行 a.setB(),走入 getBean("b") 流程,红色箭头 3

  • 这回再执行到 b.setA() 时,需要一个 a 对象,有没有呢?有!

  • factories.get() 在 singletonFactories  缓存中就可以找到,红色箭头 4 和 5

  • b 的流程能够顺利走完,将 b 成品放入 singletonObject 一级缓存,返回到 a 的依赖注入流程,红色箭头 6

二级缓存与创建代理

image-20210903103030877.png

二级缓存无法正确处理循环依赖并且包含有代理创建的场景,分析如下

  • spring 默认要求,在 a.init 完成之后才能创建代理 pa = proxy(a)

  • 由于 a 的代理创建时机靠后,在执行 factories.put(a) 向 singletonFactories 中放入的还是原始对象

  • 接下来箭头 3、4、5 这几步 b 对象拿到和注入的都是原始对象

三级缓存

image-20210903103628639.png

简单分析的话,只需要将代理的创建时机放在依赖注入之前即可,但 spring 仍然希望代理的创建时机在 init 之后,只有出现循环依赖时,才会将代理的创建时机提前。所以解决思路稍显复杂:

  • 图中 factories.put(fa) 放入的既不是原始对象,也不是代理对象而是工厂对象 fa

  • 当检查出发生循环依赖时,fa 的产品就是代理 pa,没有发生循环依赖,fa 的产品是原始对象 a

  • 假设出现了循环依赖,拿到了 singletonFactories 中的工厂对象,通过在依赖注入前获得了 pa,红色箭头 5

  • 这回 b.setA() 注入的就是代理对象,保证了正确性,红色箭头 7

  • 还需要把 pa 存入新加的 earlySingletonObjects 缓存,红色箭头 6

  • a.init 完成后,无需二次创建代理,从哪儿找到 pa 呢?earlySingletonObjects 已经缓存,蓝色箭头 9

当成品对象产生,放入 singletonObject 后,singletonFactories 和 earlySingletonObjects 就中的对象就没有用处,清除即可