做Java都有很多年了,一直有一个疑惑: Spring 如何初始化bean,怎么调用反射实例化对象的,自己动手来解除这个疑惑。
过去我认为spring bean对象实例化一直都是由BeanPostProcessor
接口实现类去做的,我就是不知道具体那个实现类,下面就去验证下这个猜想。
三级缓存
为什么面试官特别喜欢问创建bean的三级缓存,主要是因为bean创建都是伴随着三级缓存之间的转换完成的,对象不同状态分别存在不同缓存中,下面我会在分析代码时,顺便支持对象如何在缓存中流转的。
先了解下spring 三级缓存。
1 | /** 一级缓存 用于存放完全可以使用单例bean,也就是初始化完成并且注入所有依赖 */ |
三级缓存主要作用: 创建对象ObjectFactory首先放入三级换缓存中,当调用getObject 创建实例时,会将创建好对象加入二级缓存中,并且删除三级中缓存,当对象已经完成初始化方法和属性注入,再将缓存添加到一级缓存中,并且删除二级缓存。
doGetBean
从源头开始找,所有spring bean 初始化都是由AbstractBeanFactory.doGetBean
方法实现的。下面我将源码减除臃肿部分,贴出来。
1 | protected <T> T doGetBean( |
大概总结一下上面代码流程:
- 先从三级缓存中获取,如果缓存中都没有。再去判断是否存在父容器,从父容器中获取。没有正式进入bean 初始化流程,先根据beanName 获取到RootBeanDefinition,bean类元信息、先处理dependsOn中bean,保证bean依赖的创建顺序,下面会说明
org.springframework.context.annotation.@DependsOn
这个注解。下一步按照不同scope 进行bean 对象初始化。初始化流程就是这样,我们将目光放在单例bean 如何实例化,集中关注AbstractAutowireCapableBeanFactory.createBean
获取注册一个单例对象
@DependsOn
注解意思是实例化某个对象依赖于某一个实例化,但是不需要持有这个实例对象。比如bean A上 需要依赖bean b才能实例化,但是bean b 不需要作为他的属性,常常用于不同实例实例化顺序标记。
看下getSingleton方法
1 | public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { |
添加到一级缓存则说明bean已经完成实例化,可以正常使用了。下面看下如何进行实例化和属性注入的。
createBean
下面进入AbstractAutowireCapableBeanFactory.createBean
1 | protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) |
这里逻辑就比较简单了 ,克隆一份RootBeanDefinition用于初始化对象,resolveBeforeInstantiation 主要用于初始化代理对象情况,主要使用BeanPostProcessor子类InstantiationAwareBeanPostProcessor实现方法去实现对象初始化,并且在实例化成功后在调用后置方法进行对象依赖注入,这里可以看见此方法返回对象直接跳出方法栈,这里可以看出单例和代理对象还是有区别的。单例对象初始化就在doCreateBean 实现了
doCreateBean
下面就是AbstractAutowireCapableBeanFactory.doCreateBean
非常接近对象如何实例化的了
1 | protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) |
这里代码主要分成三部分
- 初始化实例,创建对象完成,并且添加到3级缓存。第3级缓存常常用于存储代理对象,因为有些类需要动态代理方法,需要生成代理对象,会委派给第三级缓存方法ObjectFactroy去实现的,普通对象如果不需要会直接返回。
- 对实例化bean进行属性注入
- 执行初始化方法,DisposableBean接口加入到disposableBeans容器中
instantiateBean
1 | protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { |
实例化方法instantiateBean最终会调用SimpleInstantiationStrategy.instantiate 进行实例化
instantiate
1 | public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { |
instantiateClass
1 |
|
这里要注意下先判断bean是否有方法重写的,没有则使用反射生成的构造器,有就使用gclib方式创建代理对象,具体实现方式就在org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate
,有兴趣同学可以去学习下。
到此一个简单bean实例化完成了。
注入
下面进入IOC另一个特点,bean注入,先从AbstractAutowireCapableBeanFactory.populateBean
方法开始
1 | protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { |
小知识点:
AutowireCapableBeanFactory.AUTOWIRE_NO 表明不会对当前Bean进行外部类的注入,常规使用@Autowire、@Resource 都是这类型
剩下三种都是通过xml 或者 AutowireCapableBeanFactory.autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck) 进行设置autowireMode 。
根据上面代码可以知道主流程bean注入都是由InstantiationAwareBeanPostProcessor 进行处理的,简单说明接口方法
方法 | 描述 |
---|---|
postProcessBeforeInitialization | 方法是最 先执行的方法,它在目标对象实例化之前调用,该方法的返回值类型是Object,我们可以返回任何类型的值。由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成的目标对象的实例(比如代理对象)。如果该方法的返回值代替原本该生成的目标对象,后续只有postProcessAfterInitialization方法会调用,其它方法不再调用;否则按照正常的流程走 |
postProcessAfterInitialization | 方法在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置,都是null。因为它的返回值是决定要不要调用postProcessPropertyValues方法的其中一个因素(因为还有一个因素是mbd.getDependencyCheck());如果该方法返回false,并且不需要check,那么postProcessPropertyValues就会被忽略不执行;如果返回true,postProcessPropertyValues就会被执行 |
postProcessPropertyValues | 对bean属性值赋值后调用,对属性值的修改。如果postProcessAfterInstantiation方法返回false,该方法可能不会被调用。可以在该方法内对属性值进行修改 |
postProcessProperties | Bean属性赋值就是调用这个方法的 |
InstantiationAwareBeanPostProcessor 接口实现类主要分3个
- ConfigurationClassPostProcessor:看类名就知道处理@Configuration实例化,并没有属性注入逻辑,不详讲略过。
- CommonAnnotationBeanPostProcessor:这个类就是实现bean注入,但是是实现JSR-250 注解、@Resource,@EJB、@WebServiceRef,@WebServiceContext,@PostConstrusct、@PreDestory这些注解实现。
- AutowiredAnnotationBeanPostProcessor:实现 @Autowired、@Value注入,并且支持JSR-330’s @Inject,主要分析这个类就可以知道bean 注入的。
AutowiredAnnotationBeanPostProcessor分析
1 | private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4); |
在初始化时就将支持注解加入集合中,再使用扫描器去扫描方法、构造器、字段,如果有这些注解就进行注入。
看下怎么判断是否需要注入的
1 |
|
AccessibleObject 是Method、Field、Constructor 父类。
postProcessProperties 如何实现bean注入的
1 | public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { |
- InjectionMetadata 主要是集合bean需要被注入类型,因为已经解析过bean Class信息了,相当于解析结果装起来
看下如何去扫描方法、字段的
1 | private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) { |
逻辑非常简单,就是根据给定注解去class获取指定的注解,从而获取到需要注入类型,但是几行简单的代码可以看出强大编码能力,学习了👍。
现在需要注入对象已经获取到,看如何注入吧
1 | public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { |
主要核心是如从缓存获取到需要注入类型实例在beanFactory.resolveDependency
中
进入DefaultListableBeanFactory看下
1 | public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, |
@Lazy 使用注解修饰bean 或者Class,在容器初始化化时不会立刻创建,只要需要使用bean才会创建的。
根据类型Optional、ObjectFactory、Provider,还有懒加载情景不同的处理,这些处理本质都是要调用doResolveDependency方法初始化对象,无论那种对象都要 获取原始对象然后再交给这些接口去包装增强。
1 | public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, |
这个方法简单做个总结,先是处理 @Value 情况,然后通过findAutowireCandidates 通过类型去容器中获取实例,如何实例还没有初始化,就会调用上面那个初始化过程,将初始化对象返回。根据注入类型进行相应处理,像stream、Collection,这些混合类型都是直接添加进去。如果出现了一个类型多个bean情况,这时就是就是@Primary、@Priority这些注解来判断或者根据属性名去和beanName匹配,最后将bean对象返回。
这里就简单看完一个bean初始化流程了。
总结
现在知道了Bean实例化是由一个策略模式,使用反射攻击类创建的,和BeanPostProcessor其实并没有太多关系的。像我刚开始学spring时,老师就说@Autowired 和@Resources向比较,基于类型和beanName进行注入的,这样说不完全正确的。他是通过类型去获取bean,如果出现一个类型有多个beanName,才通过bean和属性名进行注入。使用这么多年Spring了,从来没有使用过@DependsOn、@Primary、@Priority、@Lookup如果不看源码还不知道有这个特性呢。看完整个源码,对bean生命周期有了比较清晰 bean实例化-> 属性注入-> 执行初始化方法-> 加入spring容器