创作不易,转载请篇首注明 作者:掘金@小希子 + 来源链接~
如果想了解更多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⁼³₌₃⁼³₌₃⁼³₌₃嘟啦啦啦啦。。。
这里是新人博主小希子,大佬们都看到这了,左上角点个赞再走吧~~
本文转载自: 掘金