一 加载配置文件
xml方式注入
ClassPathXmlApplicationContext(“xxx.xml”)
构造函数重点源码
- 构造器(最终会执行该构造器)
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh(); // 重点方法
}
}
- 暂停
有空看一看
bean的创建流程
1. 概述
Spring 作为 Ioc 框架,实现了依赖注入,由一个中心化的 Bean 工厂来负责各个 Bean 的实例化和依赖管理。各个 Bean 可以不需要关心各自的复杂的创建过程,达到了很好的解耦效果
两大环节
- 解析,读取xml配置,扫描类文件,从配置或者注解中获取Bean的定义信息,注册一些扩展功能
- 加载,通过解析完的定义信息获取Bean实例
两个概念
- 作用域,单例作用域或者原型作用域,单例的话需要全局实例化一次,原型每次创建都需要重新实例化。
- 依赖关系,一个 Bean 如果有依赖,我们需要初始化依赖,然后进行关联。如果多个 Bean 之间存在着循环依赖,A 依赖 B,B 依赖 C,C 又依赖 A,需要解这种循环依赖问题。
2. 加载过程简介
- 获取 BeanName,对传入的name进行解析,转化为可以从Map中获取到BeanDefinition的bean name
- 合并 Bean 定义,对父类的定义进行合并和覆盖,如果父类还有父类,会进行递归合并,以后去完整的Bean定义信息
- 实例化,使用构造或者工厂方法创建Bean实例
- 属性填充,寻找并且注入依赖,依赖的Bean还会递归调用
getBean
方法获取 - 初始化,调用自定义的初始化方法
- 获取最终的Bean,如果是FactoryBean需要调用getObject方法,如果需要类型转换调用TypeConverter进行转化
3. 细节分析
3.1 转化 BeanName
- 解析完配置后会创建Map,用beanName作为key,BeanDefinition作为value
-
BeanFactor.getBean
中传入的name,有可能是以下几种情况
- bean name:可以直接获取到定义BeanDefinition
- alias name:别名需要转化
- factorybean name:带&前缀,所以需要去除&前缀
3.2 合并 RootBeanDefinition
- 从配置文件读取到的 BeanDefinition 是 GenericBeanDefinition。它记录了一些当前类声明的属性或构造参数,但是对于父类只用一个
parentName
来记录 - 而在实例化的时候。使用的BeanDefinition是RootBeanDefinition,而非GenericBeanDefinition
- 如果不存在继承关系,GenericBeanDefinition 存储的信息是完整的,可直接转换为RootBeanDefinition
- 如果存在继承关系,GenericBeanDefintion存储的是增量信息而不是全量信息
- 为了能够正确初始化对象,需要完整的信息才行。需要
递归
合并父类的定义
3.3 处理循环依赖
循环依赖根据注入的实际分成两种类型
- 构造器循环依赖,依赖的对象是通过构造器注入的,发生在实例化Bean的时候
- 设值循环依赖,依赖的对象是通过setter方法传入的,对象已经实例化,发生在属性填充和依赖注入的时候
- 如果是构造器循环依赖,本质上是无法解决的
- 如果是设置循环依赖,Spring框架只支持单例下的设置循环依赖。Spring通过对还在创建过程中的单例,缓存并提前暴露该单例,使得其他实例可以引用该依赖。
使用了一个 ThreadLocal 变量 prototypesCurrentlyInCreation 来记录当前线程正在创建中的 Bean 对象
/** Names of beans that are currently in creation. */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<>("Prototype beans currently in creation");
3.3.1 原型模式的循环依赖(prototypes)
Spring 不支持原型模式的任何循环依赖。检测到循环依赖会直接抛出BeanCurrentlyInCreationException
异常
- 无论是构造函数的循环依赖还是设置循环依赖,在需要注入依赖的对象时,会继续调用
beanFactory.getBean
去加载对象,形成一个递归操作 - 而每次调用 beanFactory.getBean 进行实例化前后,都使用了 prototypesCurrentlyInCreation 这个变量做记录。按照这里的思路走,整体效果等同于 建立依赖对象的构造链。
- prototypesCurrentlyInCreation 中的值的变化如下:
- 在原型模式下,构造函数循环依赖和设值循环依赖,本质上使用同一种方式检测出来。Spring 无法解决,直接抛出 BeanCurrentlyInCreationException 异常。
3.3.2 单例模式的构造循环依赖
Spring 也不支持单例模式的构造循环依赖,检测到构造循环依赖也会抛出BeanCurrentlyInCreationException
异常
- 加载 A 的单例,和原型模式类似,单例模式也会调用匹配到要使用的构造函数,发现构造函数有参数 B,然后使用
BeanDefinitionValueResolver
来检索 B 的实例,根据上面的分析,继续调用beanFactory.getBean
方法 - 拿 A,B,C 的例子来举例 singletonsCurrentlyInCreation 的变化,这里可以看到和原型模式的循环依赖判断方式的算法是一样
- 加载A,记录 singletonsCurrentlyInCreation = [a],构造依赖 B,开始加载 B
- 加载 B,记录 singletonsCurrentlyInCreation = [a, b],构造依赖 C,开始加载 C
- 加载 C,记录 singletonsCurrentlyInCreation = [a, b, c],构造依赖 A,又开始加载 A
- 加载 A,执行到
DefaultSingletonBeanRegistry.beforeSingletonCreation
,singletonsCurrentlyInCreation 中 a 已经存在了,检测到构造循环依赖,直接抛出异常结束操作
3.3.3 单例模式的设置循环依赖
单例模式下,构造函数的循环依赖无法解决,但设值循环依赖是可以解决的。
- 此处有个重要的设计:
提前暴露创建中的单例
- A 创建 -> A 构造完成,开始注入属性,发现依赖 B,启动 B 的实例化
- B 创建 -> B 构造完成,开始注入属性,发现依赖 C,启动 C 的实例化
- C 创建 -> C 构造完成,开始注入属性,发现依赖 A(假设)
此时在阶段1中,A已经构造完成,Bean对象在堆中也分配好内存了,即使后续往A中填充属性(比如填充依赖的B对象),也不会修改到A的引用地址
,所以这个时候,可以先提前拿A实例的引用来先注入到C
,先完成C的实例化
- C 创建 -> C 构造完成,开始注入依赖,发现依赖 A,发现 A 已经构造完成,直接引用,完成 C 的实例化
- C 完成实例化后,B 注入 C 也完成实例化,A 注入 B 也完成实例化
3.3.4实现单例的提前暴露:三级缓存
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
singletonObjects :单例缓存,存储已经实例化完成的单例
singletonFactories :生产单例的工厂的缓存,存储工厂
earlySingletonObjects:提前暴露的单例缓存,这时候的单例刚刚创建完,但还没有注入依赖
- 先尝试从
singletonObjects
或者singletonFactories
读取,没有数据,然后尝试singletonFactories
读取singletonFactory
,执行getEarlyBeanReference
获取到引用后,存储到earlySingletonObjects
中 - 这个
earlySingletonObjects
的好处是,如果此时又有其他地方尝试获取未初始化的单例,可从earlySingletonObjects
直接取出而不需要再调用getEarlyBeanReference
3.4 创建实例
获取到完整的 RootBeanDefintion 后,就可以拿这份定义信息来实例具体的 Bean
具体实例创建见AbstractAutowireCapableBeanFactory.createBeanInstance
,返回Bean的包装类BeanWrapper,一共有三种策略:
- 使用工厂方法创建,
instantiateUsingFactoryMethod
- 使用有参构造函数构建,
autowireConstructor
- 使用无参构造函数创建,
instantiateBean
- 使用工厂方法创建,会先使用getBean获取工厂类,然后通过参数找到匹配的工厂方法,调用实例化方法实现实例化,具体见
ConstructorResolver.instantiateUsingFactoryMethod
- 使用有参构造函数创建,整个过程比较复杂,涉及到参数和构造器的匹配,为了找到匹配的构造器,Spring花了大量的工作,具体见
ConstructorResolver.autowireConstructor
- 使用无参构造函数创建时最简单的方式,见
AbstractAutowireCapableBeanFactory.instantiateBean
这三个实例化方式,最后都会走 getInstantiationStrategy().instantiate(...)
,可见实现类SimpleInstantiationStrategy.instantiate
- 虽然拿到了构造函数,但并没有立即实例化,因为用户使用了replace和lookup的配置方法,用到了动态代理加入对应的逻辑。如果没有的话,直接使用反射来创建实例
- 创建实例后,就可以开始注入属性和初始化等操作
- 这里的Bean还不是最终的Bean,返回给调用方时,如果是FactoryBean的话需要使用getObject方法来创建实例,见
AbstractBeanFactory.getObjectFromBeanInstance
,会执行到doGetObjectFromFactoryBean
3.5 注入属性
实例创建完后开始进行属性的注入,如果涉及到外部以来的实例,会自动检索并关联到该当前实例
此处主要的环节是:
- 应用 InstantiationAwareBeanPostProcessor处理器,在属性注入前后进行处理。假设使用了@Autowire注解,这里会调用到AutowiredAnnotationBeanPostProcessor来对依赖的实例进行检索和注入,它是InstantiationAwareBeanPostProcessor的子类
- 根据名称或者类型进行自动注入,存储结果到PropertyValues中。
- 应用PropertyValues,填充到BeanWrapper。这里检索依赖实例的引用的时候,会递归调用
BeanFactory.getBean
来获得
3.6 初始化
3.6.1 触发Aware
- 有时候Bean需要用到容器的一些资源 ,如BeanFactory、ApplicationContext,Spring提供了
Aware系列接口
来解决这些问题
- BeanFactoryAware:用来获取BeanFactory
- ApplicationContextAware:用来获取ApplicationContext
- ResourceLoaderAware:用来获取ResourceLoadAware
- ServletContextAware:用来获取ServletContext
- Spring在初始化阶段,如果判断Bean实现了这几个接口之一,就会往Bean中注入它关心的资源
3.6.2 触发 BeanPostProcessor(重点)
- 在Bean的初始化前或者初始化后,需要进行一些增强操作,如打日志,做校验,属性修改,耗时检测等。
- Spring框架提供了BeanPostProcessor来达成这个目标,比如用@Autowire来声明依赖,就是使用
AutowiredAnnotationBeanPostProcessor
来实现依赖的查询和注入的,接口定义如下:
public interface BeanPostProcessor {
// 初始化前调用
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
// 初始化后调用
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
- 实现该接口的Bean都会被Spring注册到
beanPostProcessors
中,见AbstractBeanFactory
:
/** BeanPostProcessors to apply in createBean */
private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<BeanPostProcessor>();
只要 Bean 实现了 BeanPostProcessor 接口,加载的时候会被 Spring 自动识别这些 Bean,自动注册,然后在Bean实例化前后,Spring会去调用我们注册的beanPostProcessors把处理器都执行一遍
public abstract class AbstractAutowireCapableBeanFactory ... {
...
@Override
public Object applyBeanPostProcessorsBeforeInitialization ... {
Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
result = beanProcessor.postProcessBeforeInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}
@Override
public Object applyBeanPostProcessorsAfterInitialization ... {
Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
result = beanProcessor.postProcessAfterInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}
...
}
这里使用了责任链模式,Bean会在处理器链中进行传递和处理。当我们调用BeanFactory.getBean
后,执行到Bean的初始化方法AbstractAutowireCapableBeanFactory.initializeBean
会启动这些处理器
protected Object initializeBean ... {
...
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
...
// 触发自定义 init 方法
invokeInitMethods(beanName, wrappedBean, mbd);
...
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
...
}
3.6.3 触发自定义 init
自定义初始化有两种方式可以选择:
- 实现 InitializingBean。在属性设置完成后再加入自己的初始化逻辑
- 定义init方法。自定义初始化逻辑
3.7 类型转换
Bean已经加载完成、属性也填充好了,初始化也完成了,在返回给调用者之前,还留有一个机会对Bean实例进行类型的转换。见AbstractBeanFactory.doGetBean
:
protected <T> T doGetBean ... {
...
if (requiredType != null && bean != null && !requiredType.isInstance(bean)) {
...
return getTypeConverter().convertIfNecessary(bean, requiredType);
...
}
return (T) bean;
}
4. 小总结
抛开一些细节处理和扩展功能,一个Bean的创建过程无非是:
获取完整定义 -> 实例化 -> 依赖注入 -> 初始化 -> 类型转换
其他
@Resource @Autowired @Autowired
@Autowired 构造器注入和set方法注入区别
- @autowired可以写在变量和构造器上,注入bean,但是有的时候写在变量上会报空指针异常,然后通过写在构造器上就解决了此问题。
public class UserController {
@Autowired
public UserService userService;
List<User> users = userService.findAll();
UserController() {}
}
这个例子会出现空指针异常,但是换成以下写法就无问题
public class UserController {
public UserService userService;
List<User> users = userService.findAll();
@Autowired
UserController(UserService userService) {
this.userService = userService;
}
}
原因:
- 其实这两种方式都可以使用,但报错的原因是
加载顺序
的问题,@autowired写在变量上的注入要等到类完全加载完,才会将相应的bean注入
,而users变量是在加载类的时候按照相应顺序加载的,所以users变量的加载要早于@autowired变量的加载,那么在类初始化时获取调用userService的findAll()方法,此时他是还没有被注入的,所以报空指针,而使用构造器就在加载类的时候将userService加载了
,这样在内部使用userService给user变量赋值就完全没有问题。 - 如果不使用构造器注入,那么也可以不给users赋值,而是在接下来的代码使用的地方,通过userService.findAll()进行赋值,这时的对userService的使用是在类完全加载之后,即userService被注入了,所以也是可以的。
- 总之,@Autowired一定要等本类构造完成后,才能从外部引用设置进来。所以@Autowired的注入时间一定会晚于构造函数的执行时间。但在初始化变量的时候就使用了还没注入的bean,所以导致了NPE。若果在初始化其它变量时不使用这个要注入的bean,而是在以后的方法调用的时候去赋值,是可以使用这个bean的,因为那时类已初始化好,即已注入好了。
AA