【死磕 Spring】----- IOC 之分析各 scope 的 bean 创建

简介:
在 Spring 中存在着不同的 scope,默认是 singleton ,还有 prototype、request 等等其他的 scope,他们的初始化步骤是怎样的呢?这个答案在这篇博客中给出。

singleton

Spring 的 scope 默认为 singleton,其初始化的代码如下:

 
  1. if (mbd.isSingleton()) {

  2. sharedInstance = getSingleton(beanName, () -> {

  3. try {

  4. return createBean(beanName, mbd, args);

  5. }

  6. catch (BeansException ex) {

  7. destroySingleton(beanName);

  8. throw ex;

  9. }

  10. });

  11. bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

  12. }

第一部分分析了从缓存中获取单例模式的 bean,但是如果缓存中不存在呢?则需要从头开始加载 bean,这个过程由 getSingleton() 实现。

 
  1. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {

  2. Assert.notNull(beanName, "Bean name must not be null");


  3. // 全局加锁

  4. synchronized (this.singletonObjects) {

  5. // 从缓存中检查一遍

  6. // 因为 singleton 模式其实就是复用已经创建的 bean 所以这步骤必须检查

  7. Object singletonObject = this.singletonObjects.get(beanName);

  8. // 为空,开始加载过程

  9. if (singletonObject == null) {

  10. // 省略 部分代码


  11. // 加载前置处理

  12. beforeSingletonCreation(beanName);

  13. boolean newSingleton = false;

  14. // 省略代码

  15. try {

  16. // 初始化 bean

  17. // 这个过程其实是调用 createBean() 方法

  18. singletonObject = singletonFactory.getObject();

  19. newSingleton = true;

  20. }

  21. // 省略 catch 部分

  22. }

  23. finally {

  24. // 后置处理

  25. afterSingletonCreation(beanName);

  26. }

  27. // 加入缓存中

  28. if (newSingleton) {

  29. addSingleton(beanName, singletonObject);

  30. }

  31. }

  32. // 直接返回

  33. return singletonObject;

  34. }

  35. }

其实这个过程并没有真正创建 bean,仅仅只是做了一部分准备和预处理步骤,真正获取单例 bean 的方法其实是由 singletonFactory.getObject() 这部分实现,而 singletonFactory 由回调方法产生。那么这个方法做了哪些准备呢?

  1. 再次检查缓存是否已经加载过,如果已经加载了则直接返回,否则开始加载过程。

  2. 调用 beforeSingletonCreation() 记录加载单例 bean 之前的加载状态,即前置处理。

  3. 调用参数传递的 ObjectFactory 的 getObject() 实例化 bean。

  4. 调用 afterSingletonCreation() 进行加载单例后的后置处理。

  5. 将结果记录并加入值缓存中,同时删除加载 bean 过程中所记录的一些辅助状态。

流程中涉及的三个方法 beforeSingletonCreation()afterSingletonCreation() 在博客 【死磕 Spring】----- 加载 bean 之 缓存中获取单例 bean 中分析过了,所以这里不再阐述了,我们看另外一个方法 addSingleton()

 
  1. protected void addSingleton(String beanName, Object singletonObject) {

  2. synchronized (this.singletonObjects) {

  3. this.singletonObjects.put(beanName, singletonObject);

  4. this.singletonFactories.remove(beanName);

  5. this.earlySingletonObjects.remove(beanName);

  6. this.registeredSingletons.add(beanName);

  7. }

  8. }

一个 put、一个 add、两个 remove。singletonObjects 单例 bean 的缓存,singletonFactories 单例 bean Factory 的缓存,earlySingletonObjects “早期”创建的单例 bean 的缓存,registeredSingletons 已经注册的单例缓存。

加载了单例 bean 后,调用 getObjectForBeanInstance() 从 bean 实例中获取对象。该方法已经在 【死磕 Spring】----- 加载 bean 之 缓存中获取单例 bean 详细分析了。

原型模式

 
  1. else if (mbd.isPrototype()) {

  2. Object prototypeInstance = null;

  3. try {

  4. beforePrototypeCreation(beanName);

  5. prototypeInstance = createBean(beanName, mbd, args);

  6. }

  7. finally {

  8. afterPrototypeCreation(beanName);

  9. }

  10. bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);

  11. }

原型模式的初始化过程很简单:直接创建一个新的实例就可以了。过程如下:

  1. 调用 beforeSingletonCreation() 记录加载原型模式 bean 之前的加载状态,即前置处理。

  2. 调用 createBean() 创建一个 bean 实例对象。

  3. 调用 afterSingletonCreation() 进行加载原型模式 bean 后的后置处理。

  4. 调用 getObjectForBeanInstance() 从 bean 实例中获取对象。

其他作用域

 
  1. String scopeName = mbd.getScope();

  2. final Scope scope = this.scopes.get(scopeName);

  3. if (scope == null) {

  4. throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");

  5. }

  6. try {

  7. Object scopedInstance = scope.get(beanName, () -> {

  8. beforePrototypeCreation(beanName);

  9. try {

  10. return createBean(beanName, mbd, args);

  11. }

  12. finally {

  13. afterPrototypeCreation(beanName);

  14. }

  15. });

  16. bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);

  17. }

  18. catch (IllegalStateException ex) {

  19. throw new BeanCreationException(beanName,

  20. "Scope '" + scopeName + "' is not active for the current thread; consider " +

  21. "defining a scoped proxy for this bean if you intend to refer to it from a singleton",

  22. ex);

  23. }

核心流程和原型模式一样,只不过获取 bean 实例是由 scope.get() 实现,如下:

 
  1. public Object get(String name, ObjectFactory<?> objectFactory) {

  2. // 获取 scope 缓存

  3. Map<String, Object> scope = this.threadScope.get();

  4. Object scopedObject = scope.get(name);

  5. if (scopedObject == null) {

  6. scopedObject = objectFactory.getObject();

  7. // 加入缓存

  8. scope.put(name, scopedObject);

  9. }

  10. return scopedObject;

  11. }

对于上面三个模块,其中最重要的有两个方法,一个是 createBean()、一个是 getObjectForBeanInstance()。这两个方法在上面三个模块都有调用, createBean() 后续详细说明, getObjectForBeanInstance() 在博客 【死磕 Spring】----- 加载 bean 之 缓存中获取单例 bean 中有详细讲解,这里再次阐述下(此段内容来自《Spring 源码深度解析》):这个方法主要是验证以下我们得到的 bean 的正确性,其实就是检测当前 bean 是否是 FactoryBean 类型的 bean,如果是,那么需要调用该 bean 对应的 FactoryBean 实例的 getObject() 作为返回值。无论是从缓存中获得到的 bean 还是通过不同的 scope 策略加载的 bean 都只是最原始的 bean 状态,并不一定就是我们最终想要的 bean。举个例子,加入我们需要对工厂 bean 进行处理,那么这里得到的其实是工厂 bean 的初始状态,但是我们真正需要的是工厂 bean 中定义 factory-method 方法中返回的 bean,而 getObjectForBeanInstance() 就是完成这个工作的。

至此,Spring 加载 bean 的三个部分(LZ自己划分的)已经分析完毕了。


原文发布时间为:2018-10-24
本文作者:Java技术驿站
本文来自云栖社区合作伙伴“Java技术驿站”,了解相关信息可以关注“Java技术驿站”。

相关文章
|
1天前
|
XML Java API
IoC 之 Spring 统一资源加载策略【Spring源码】
IoC 之 Spring 统一资源加载策略【Spring源码】
11 2
|
1天前
|
XML Java API
IoC 之 Spring 统一资源加载策略
IoC 之 Spring 统一资源加载策略
11 2
|
1天前
|
存储 Java Spring
Spring IOC 源码分析之深入理解 IOC
Spring IOC 源码分析之深入理解 IOC
10 2
|
2天前
|
XML Java 数据格式
Spring--两大核心之一--IOC
Spring--两大核心之一--IOC
|
3天前
|
Java Spring
解决 Spring 中 Prototype Bean 注入后被固定的问题
【6月更文挑战第8天】学习 Spring 框架内不原理的意义就是,当遇到问题时,分析出原因,就可以从多个切入点,利用 Spring 的特性,来解决问题。
14 2
|
8天前
|
Oracle Java 关系型数据库
|
8天前
|
XML Java 数据格式
|
8天前
|
druid Java 关系型数据库
|
8天前
|
Java Spring 容器
|
10天前
|
存储 Java C++
理解SpringIOC和DI第一课(Spring的特点),IOC对应五大注解,ApplicationContext vs BeanFactory
理解SpringIOC和DI第一课(Spring的特点),IOC对应五大注解,ApplicationContext vs BeanFactory