创作不易,转载请篇首注明 作者:掘金@小希子 + 来源链接~
如果想了解更多Spring源码知识,点击前往其余逐行解析Spring系列
一、前言
这些天一直在琢磨bean
生命周期这一块应该怎么写,因为这一块的流程实在比较长,而且涉及到很多beanPostProcessor
的埋点,很多我们常见的功能都是通过这些埋点来做的。
最终,我决定先用一篇博文,把bean
生命周期的主流程较为**粗略(相对)**的讲一下。之后,会通过一系列博文对主流程中的一些细节、和一些常见的功能是怎么通过spring
预留的beanPostProcessor
埋点来实现的。感兴趣的同学可以自己选择查看。
(由于掘金对文章字数的限制,这篇博文被迫分为上下两篇,点击前往下一篇)
二、Spring
容器的启动
发现其实到现在,我的这一系列spring
博文,都没有好好讲过spring
容器启动的过程(第一篇中也是直接给定位到了refresh
方法)。正好上一篇讲的纯注解启动类AnnotationConfigApplicationContext
,这里我们再回顾一下:
1 | java复制代码@Test |
我们看一下这个容器启动的核心方法refresh
,这个方法的逻辑是在AbstractApplicationContext
类中的,也是一个典型的模板方法:
1 | java复制代码public void refresh() throws BeansException, IllegalStateException { |
其实说白了,我们spring
容器的启动,主要就是要把那些非懒加载的单例bean
给实例化,并且管理起来。
三、bean
实例化
1. 哪些bean
需要在启动的时候实例化?
刚刚refresh
方法中,我们有看到finishBeanFactoryInitialization
方法是用来实例化bean
的,并且源码中的英文也说明了,说是要实例化所以剩余的非懒加载的单例bean
,那么实际情况真的如此么?我们跟源码看一下:
1 | java复制代码protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { |
可以看到,原来是非抽象(xml
有一个abstract
属性,而不是说这个类不是一个抽象类)、单例的、非懒加载的bean
才会在spring
容器启动的时候实例化,spring
老哥们,你们的注释打错了呀(抠一下字眼就很开心)~
2. 使用getBean
从beanFactory
获取bean
刚刚有说到,调用getBean
方法的时候,会先尝试中spring
容器中获取这个bean
,获取不到的时候则会创建一个,现在我们就来梳理一下这个流程:
1 | java复制代码public Object getBean(String name) throws BeansException { |
getBean->doGetBean
是我们beanFactory
对外提供的获取bean
的接口,只是说我们初始化spring
容器的时候会为所有单例的beanDefinition
调用getBean
方法实例化它们定义的bean
而已,所以它的的逻辑并不仅仅是为spring
容器初始化定义的,我们也需要带着这个思维去看这个方法:
1 | java复制代码protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, |
我们继续看一下单例bean
的创建逻辑,即:
1 | java复制代码if (mbd.isSingleton()) { |
我们看一下这个getSingleton
方法,需要注意的是,这个方法在DefaultSingletonBeanRegistry
类中:
1 | java复制代码/** Cache of singleton objects: bean name to bean instance. */ |
可以看到,这个getSingleton
方法就是先从singletonObjects
获取bean
实例,获取不到就创建一个,其中还加了一些循环依赖的检测逻辑。
3. createBean
,真正的bean
初始化逻辑
我们说createBean
方法是真正的bean
初始化逻辑,但是这个初始化不仅仅是说创建一个实例就好了,还涉及到一些校验,以及类里的依赖注入、初始化方法调用等逻辑,我们现在就一起来简单看一下:
1 | java复制代码protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) |
3.0. doCreateBean
是如何实例化一个bean
的?
刚刚有说到,doCreateBean
是我们spring
真正的实例化bean
的逻辑,那我们一起来看一下:
1 | java复制代码protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) |
可以看到,我们的doCreateBean
大致做了5件事:
- 创建
bean
实例 - 调用
beanPostProcessor
的埋点方法 - 注入当前类依赖的
bean
- 调用当前
bean
的初始化方法 - 注册当前
bean
的销毁逻辑
接下来我们来详细看一下这些流程
3.1. createBeanInstance
创建bean
实例
大家平常是怎么实例化一个类呢?是直接使用构造器new
出来一个?还是使用工厂方法获取?
很显然,spring
也是支持这两种方式的,如果同学们还记得bean标签的解析的话,那应该还会记得spring
除了有提供使用构造器实例化bean
的constructor-arg
标签外,还提供了factory-bean
和factory-method
属性来配置使用工厂方法来实例化bean
。
并且之前在讲ConfigurationClassPostProcessor
的时候,我们讲到@bean
标签的时候,也有看到,对于@bean
标签的处理,就是新建一个beanDefinition
,并把当前的配置类和@Bean
修饰的方法分别塞入了这个beanDefinition
的factoryBeanName
和factoryMethodName
属性(可以空降ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod
)。
接下来我们就来看一下createBeanInstance
的代码:
1 | java复制代码protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { |
具体的instantiateUsingFactoryMethod
、autowireConstructo
方法这边就不带同学们看了,因为里面涉及到的一些参数注入的逻辑比较复杂,之后会单独开一篇博客来讲。
而拿到具体的参数之后,其实不管是构造器还是工厂方法实例化,都是很清晰的,直接反射调用就好了。
instantiateBean
就是获取无参构造器然后反射实例化的一个逻辑,逻辑比较简单,这边也不跟了。
3.1.1. 通过determineConstructorsFromBeanPostProcessors
方法选择构造器
这边主要带大家跟一下determineConstructorsFromBeanPostProcessors
这个方法,因为我们现在大部分都是使用注解来声明bean
的,而如果大家在使用注解的时候也是使用构造器的方式注入的话,那么是通过这个方法来拿到相应的构造器的。
1 | java复制代码protected Constructor<?>[] determineConstructorsFromBeanPostProcessors(@Nullable Class<?> beanClass, String beanName) |
可以看到,还是通过beanPostProcessor
的埋点来做的,这里是调用的SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors
,这里也不给大家卖关子了,我们真正支撑注解方式,选择构造器的逻辑在AutowiredAnnotationBeanPostProcessor
中,有没有感觉这个类好像也有点熟悉?
1 | java复制代码public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( |
也是有在调用AnnotationConfigUtils#registerAnnotationConfigProcessors
方法的时候有注入哦~
从名称可以看到,这个beanPostProcessor
是应该是用来处理@Autowired
注解的,有同学要说了,这不是属性注入的注解么,跟构造器有什么关系?那我们已一个构造器注入的bean来举例:
1 | java复制代码@Service |
大部分同学可能忘了,@Autowired
是可以用来修饰构造器的,被@Autowired
修饰的构造器的参数也将会中spring
容器中获取(这么说可能不太准确,大家明白我的意思就好,就是说构造器注入的意思…)。
不过,其实我们平常即使使用构造器注入也不打@Autowired
注解也是没问题的,这其实也是AutowiredAnnotationBeanPostProcessor
获取构造器时的一个容错逻辑,我们一起看一下代码就知道了:
1 | java复制代码public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) |
如果能找到合适的构造器的话,就可以直接通过反射实例化对象了~
3.2. 通过beanPostProcessor
埋点来收集注解信息
通过createBeanInstance
创建完类的实例之后,注入属性之前,我们有一个beanPostProcessor
的埋点方法的调用:
1 | java复制代码synchronized (mbd.postProcessingLock) { |
由于这个埋点中有一部分对注解进行支撑的逻辑还挺重要的,所以这里单独拿出来讲一下。
3.2.1. CommonAnnotationBeanPostProcessor
收集@PostConstruct
、@PreDestroy
、@Resource
信息
CommonAnnotationBeanPostProcessor
也是AnnotationConfigUtils#registerAnnotationConfigProcessors
方法注入的,这里我就不带大家再看了。由于CommonAnnotationBeanPostProcessor
实现了MergedBeanDefinitionPostProcessor
接口,所以在这个埋点中也会被调用到,我们来看一下这个逻辑:
1 | java复制代码public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor |
3.2.1.1. 生命周期注解@PostConstruct
、@PreDestroy
信息收集
我们先看一下父类收集生命周期注解的实现:
1 | java复制代码public class InitDestroyAnnotationBeanPostProcessor |
看一下这个生命周期元数据LifecycleMetadata
的结构:
1 | java复制代码private class LifecycleMetadata { |
到这里为止,其实我们CommonAnnotationBeanPostProcessor
对生命周期注解的收集过程就完成了,其实主要是通过父类的模本方法,把被@PostConstruct
、@PreDestroy
修饰的方法的信息封装到了LifecycleMetadata
。看完InitDestroyAnnotationBeanPostProcessor
的逻辑之后,同学们会不会有实现一套自己的生命周期注解的冲动呢?毕竟写一个类继承一下然后在自己的类构造器中set
一下initAnnotationType
、destroyAnnotationType
就可以了!
3.2.1.2. 依赖注入注解@Resource
信息收集
刚刚有说道我们的findResourceMetadata
方法是用来收集@Resource
注解信息的,我们现在来看一下这里的逻辑:
1 | java复制代码private InjectionMetadata findResourceMetadata(String beanName, final Class<?> clazz, @Nullable PropertyValues pvs) { |
可以看到,其实跟生命周期那一块差不多,也是收集注解信息然后封装,只是这个注入元素的收集要同时收集属性和(set
)方法而已,我们还是照常瞄一下这个数据结构:
1 | java复制代码public class InjectionMetadata { |
获取到InjectionMetadata
之后的metadata.checkConfigMembers
逻辑,和生命周期那一块是一模一样的,这边就不跟了。
那么到这里为止我们CommonAnnotationBeanPostProcessor
类在bean实例创建之后的埋点的逻辑就分析完了。
3.2.2. AutowiredAnnotationBeanPostProcessor
收集@Autowired
、@Value
信息
AutowiredAnnotationBeanPostProcessor
这个类的注册时机已经讲过很多遍了,也是AnnotationConfigUtils#registerAnnotationConfigProcessors
方法注入的,这边我们直接看一下它的postProcessMergedBeanDefinition
方法是如何收集注解信息的:
1 | java复制代码public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter |
啊,索然无味,这个逻辑简直跟@Resource
的处理一模一样的,同学们有没有一丢丢疑惑–这样如此雷同的方法,spring
为什么要写两遍呢?这是不是违反了DRY
原则呢?同学们可以思考一下这个问题。
我倒是认为没有违反
DRY
原则,但是对于javax.inject.Inject
注解的划分还是不太合适,应该划分到common
的;而common
中对各种类型的注解的处理(@EJB
、@Resource
、@WebServiceRef
)使用if-else
也不太优雅,完全可以使用一个小小的策略模式的。不过这东西每个人看法也不一样的,同学们有兴趣也可以在评论区探讨一下~
3.2.3. 总结CommonAnnotationBeanPostProcessor
和AutowiredAnnotationBeanPostProcessor
这个埋点基本上就这两个beanPostProcessor
做了事情了,而且也与我们平常的开发息息相关,这边简单总结一下。
3.2.3.1 职能划分
这两个beanPostProcessor
的职能上是有划分的:
CommonAnnotationBeanPostProcessor
主要处理jdk
相关的规范的注解,@Resource
、@PostConstruct
等注解都是jdk
的规范中定义的。
* 收集生命周期相关的`@PostConstruct`、`@PreDestroy`注解信息封装成`LifecycleMetadata`
* 收集资源注入注解(我们主要关注`@Resource`)信息封装成`InjectionMetadata`
AutowiredAnnotationBeanPostProcessor
主要处理spring
定义的@Autowired
相关的功能
* 这里不得不说一下我觉得这个类也用来处理`javax.inject.Inject`不合理
* 收集自动注入相关的注解`@Autowired`、`@Value`信息封装成`InjectionMetadata`
3.2.3.2 使用@Resouce
还是@Autowired
?
那么日常我们开发过程中,究竟推荐使用@Resouce
还是@Autowired
呢?这个问题我认为仁者见仁智者见智,我这边只稍微列一下使用这两个注解时需要注意的问题:
@Resouce
和@Autowired
都不能用来注入静态属性(通过在静态属性上使用注解和静态方法上使用注解)- 使用
@Resouce
注入静态属性时,会直接抛出IllegalStateException
导致当前实例初始化流程失败 - 而使用
@Autowired
注入静态属性时,只会忽略当前属性,不注入了,不会导致实例初始化流程失败 - 使用
@Resouce
修饰方法时,方法只能有一个入参,而@Autowired
没有限制 @Resouce
属于jdk
的规范,可以认为对项目零入侵;@Autowired
属于spring
的规范,使用了@Autowired
的话就不能替换成别的IOC
框架了(这个我确实也没替换过…)
(由于掘金对文章字数的限制,这篇博文被迫分为上下两篇,点击前往下一篇)
创作不易,转载请篇首注明 作者:掘金@小希子 + 来源链接~
如果想了解更多Spring源码知识,点击前往其余逐行解析Spring系列
٩(* ఠO ఠ)=3⁼³₌₃⁼³₌₃⁼³₌₃嘟啦啦啦啦。。。
这里是新人博主小希子,大佬们都看到这了,左上角点个赞再走吧~~
本文转载自: 掘金