概述
Spring执行流程图如下:
如果图片显示不清楚可以访问如下链接查看高清大图:
Spring执行流程图
这个流程图会随着我们的学习不断的变得越来越详细,也会越来越复杂,希望在这个过程中我们都能朝着精通Spring的目标不断前进!
我们已经知道了Spring中的第一行代码其实就是创建了一个AnnotatedBeanDefinitionReader对象,这个对象的主要作用就是注册bd(BeanDefinition)到容器中。并且在创建这个对象的过程中,Spring还为容器注册了开天辟地的几个bd,包括ConfigurationClassPostProcessor,AutowiredAnnotationBeanPostProcessor等等。
this.scanner = new ClassPathBeanDefinitionScanner(this);
上面流程图上已经标注。
只是简单地创建了一个ClassPathBeanDefinitionScanner对象。那么这个ClassPathBeanDefinitionScanner有什么作用呢?从名字上来看好像就是这个对象来完成Spring中的扫描的,真的是这样吗?希望同学们能带着这两个问题往下看
ClassPathBeanDefinitionScanner源码分析
这个类名直译过来就是:类路径下的BeanDefinition的扫描器,所以我们就直接关注其扫描相关的方法,就是其中的doScan方法。其代码如下:
上面这段代码主要做了四件事
通过findCandidateComponents方法完成扫描判断扫描出来的bd是否是一个AbstractBeanDefinition,如果是的话执行postProcessBeanDefinition方法判断扫描出来的bd是否是一个AnnotatedBeanDefinition,如果是的话执行processCommonDefinitionAnnotations方法检查容器中是否已经有这个bd了,如果有就不进行注册了接下来我们就一步步分析这个方法,搞明白ClassPathBeanDefinitionScanner到底能起到什么作用
1、通过findCandidateComponents方法完成扫描
findCandidateComponents方法源码如下:
addCandidateComponentsFromIndex不用过多关注这个方法。正常情况下Spring都是采用扫描classpath下的class文件来完成扫描,但是虽然基于classpath扫描速度非常快,但通过在编译时创建候选静态列表,可以提高大型应用程序的启动性能。在这种模式下,应用程序的所有模块都必须使用这种机制,因为当 ApplicationContext检测到这样的索引时,它将自动使用它而不是扫描类路径。要生成索引,只需向包含组件扫描指令目标组件的每个模块添加附加依赖项即可
scanCandidateComponents(basePackage)正常情况下我们的应用都是通过这个方法完成扫描的,其代码如下:
从上图中可以看出,java class + configuration metadata 最终会转换为一个BenaDefinition,结合我们上面的代码分析可以知道,java class + configuration metadata实际上就是一个MetadataReader对象,而转换成一个BenaDefinition则是指通过这个MetadataReader对象创建一个ScannedGenericBeanDefinition。
2、执行postProcessBeanDefinition方法
可以看出,postProcessBeanDefinition方法最主要的功能就是给扫描出来的bd设置默认值,进一步填充bd中的属性
3、执行processCommonDefinitionAnnotations方法
这句代码将进一步解析class上的注解信息,Spring在创建这个abd的信息时候就已经将当前的class放入其中了,所有这行代码主要做的就是通过class对象获取到上面的注解(包括@Lazy,@Primary,@DependsOn注解等等),然后将得到注解中对应的配置信息并放入到bd中的属性中
4、注册BeanDefinition
通过上面的分析,我们已经知道了ClassPathBeanDefinitionScanner的作用,毋庸置疑,Spring肯定是通过这个类来完成扫描的,但是问题是,Spring是通过第二步创建的这个对象来完成扫描的吗?我们再来看看这个ClassPathBeanDefinitionScanner的创建过程:
在这个ClassPathBeanDefinitionScanner的创建过程中我们全程无法干涉,不能对这个ClassPathBeanDefinitionScanner进行任何配置。而我们在配置类上明明是可以对扫描的规则进行配置的,例如:
@ComponentScan(value = "com.spring.study.springfx.aop.service", useDefaultFilters = true, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {IndexService.class}))
所以Spring中肯定不是使用在这里创建的这个ClassPathBeanDefinitionScanner对象。
实际上真正完成扫描的时机是在我们流程图中的3-5-1步。完成扫描这个功能的类就是我们在上篇文章中所提到的ConfigurationClassPostProcessor。接下来我们就通过这个类,看看Spring到底是如何完成的扫描,这也是本文重点想要说明的问题
Spring是怎么解析配置类的?
1、解析时机分析
解析前Spring做了什么?
注册配置类
在分析扫描时机之前我们先回顾下之前的代码,整个程序的入口如下:
其中在this()空参构造中Spring实例化了两个对象,一个是AnnotatedBeanDefinitionReader,在上篇文章中已经介绍过了,另外一个是ClassPathBeanDefinitionScanner,在前文中也进行了详细的分析。
在完成这两个对象的创建后,Spring紧接着就利用第一步中创建的AnnotatedBeanDefinitionReader去将配置类注册到了容器中。看到这里不知道大家有没有一个疑问,既然Spring是直接通过这种方式来注册配置类,为什么我们还非要在配置类上添加@Configuration注解呢?按照这个代码的话,我不在配置类上添加任何注解,也能将配置类注册到容器中,例如下面这样:
大家仔细想想我这个问题,不妨带着这些疑问继续往下看。
调用refresh方法
在将配置类注册到容器中后,Spring紧接着又调用了refresh方法,其源码如下:
大部分的代码都写了很详细的注释,对于其中两个比较复杂的方法我们单独分析prepareBeanFactoryinvokeBeanFactoryPostProcessors
prepareBeanFactory做了什么?
上面这段代码整体来说还是非常简单的,逻辑也很清晰,就是在为beanFactory做一些配置,我们需要注意的是跟后置处理器相关的内容,可以看到在这一步一共注册了两个后置处理器
ApplicationContextAwareProcessor,用于执行xxxAware接口中的方法ApplicationListenerDetector,保证监听器被添加到容器中invokeBeanFactoryPostProcessors做了什么?
整的来说,它就是将容器中已经注册的bean工厂的后置处理器按照一定的顺序进行执行。
那么到这一步为止,容器中已经有哪些bean工厂的后置处理器呢?
还记得我们在上篇文章中提到的ConfigurationClassPostProcessor吗?在创建AnnotatedBeanDefinitionReader的过程中它对应的BeanDefinition就被注册到容器中了。接下来我们就来分析ConfigurationClassPostProcessor这个类的源码
ConfigurationClassPostProcessor源码分析
它实现了BeanDefinitionRegistryPostProcessor,所以首先执行它的postProcessBeanDefinitionRegistry方法,其源码如下
processConfigBeanDefinitions方法的代码很长,我们拆分一段段分析,先看第一段
第一段
上面这段代码有这么几个问题:
当前容器中有哪些BeanDefinition如果你看过上篇文章的话应该知道,在创建AnnotatedBeanDefinitionReader对象的时候Spring已经往容器中注册了5个BeanDefinition,再加上注册的配置类,那么此时容器中应该存在6个BeanDefinition,我们可以打个断点验证
不出所料,确实是6个
checkConfigurationClassCandidate代码如下:
第二段
这段代码核心目的就是为了创建一个ConfigurationClassParser,这个类主要用于后续的配置类的解析。
第三段
2、解析源码分析
在上面的源码分析中,我们已经能够确定了Spring是通过ConfigurationClassParser的parse方法来完成对配置类的解析的。Spring对类的取名可以说是很讲究了,ConfigurationClassParser直译过来就是配置类解析器。接着我们就来看看它的源码
2.1、parse方法
2.2、processConfigurationClass方法
2.3、doProcessConfigurationClass方法
可以看到,在doProcessConfigurationClass真正完成了对配置类的解析,一共做了下面几件事
解析配置类中的内部类,看内部类中是否有配置类,如果有进行递归处理
处理配置类上的@PropertySources跟@PropertySource注解
处理@ComponentScan,@ComponentScans注解
处理@Import注解
处理@ImportResource注解
处理@Bean注解
处理接口中的default方法
返回父类,让外部的循环继续处理当前配置类的父类
我们逐一进行分析
2.4、处理配置类中的内部类
这段代码非常简单,限于篇幅原因我这里就不再专门分析了,就是获取到当前配置类中的所有内部类,然后遍历所有的内部类,判断是否是一个配置类,如果是配置类的话就递归进行解析
2.5、处理@PropertySource注解
代码也非常简单,根据注解中的信息加载对应的属性文件然后添加到容器中
2.6、处理@ComponentScan注解
这个段我们就需要看一看了,Spring在这里完成的扫描,我们直接查看其核心方法,org.springframework.context.annotation.ComponentScanAnnotationParser#parse
看到了吧,第一步就创建了一个ClassPathBeanDefinitionScanner,紧接着通过解析注解,对这个扫描器进行了各种配置,然后调用doScan方法完成了扫描。
2.7、处理@Import注解
2.8、处理@ImportResource注解
代码也很简单,在指定的位置加载资源,然后添加到configClass中。一般情况下,我们通过@ImportResource注解导入的就是一个XML配置文件。将这个Resource添加到configClass后,Spring会在后文中解析这个XML配置文件然后将其中的bd注册到容器中,可以参考org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions方法
2.9、处理@Bean注解
将配置类中所有的被@Bean标注的方法添加到configClass的BeanMethod集合中
2.10、处理接口中的default方法
代码也很简单,Java8中接口能定义default方法,这里就是处理接口中的default方法,看其是否有@Bean标注的方法
到此为止,我们分析完了整个解析的过程。可以发现Spring将所有解析到的配置信息都存储在了ConfigurationClass类中,但是到目前为止这些存储的信息都没有进行使用。那么Spring是在哪里使用的这些信息呢?回到我们的第三段代码,其中有一行代码如图所示:
也就是在这里Spring完成了对解析好的配置类的信息处理。
2.11、加载解析完成的配置信息
这段代码阅读起来还是非常简单的,这里我就跟大家一起看下BeanMethod的相关代码,主要是为了让大家对BeanDefinition的理解能够更加深入,其源码如下:
上面这个方法的主要目的就是将@Bean标注的方法解析成BeandDefinition然后注册到容器中。关于这个方法我们可以对比下之前分析过的org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean方法。对比我们可以发现,这两个方法最大的不同在于一个是基于Class对象的,而另一个则是基于Method对象的。
正因为如此,所有它们有一个很大的不同点在于BeanDefinition中BeanClasss属性的设置。可以看到,对于@Bean形式创建的Bean其BeanDefinition中是没有设置BeanClasss属性的,但是额外设置了其它的属性
静态方法下,设置了BeanClassName以及FactoryMethodName属性,其中的BeanClassName是静态方法所在类的类名,FactoryMethodName是静态方法的方法名实例方法下,设置了FactoryBeanName以及FactoryMethodName属性,其中FactoryBeanName是实例对应的Bean的名称,而FactoryMethodName是实例中对应的方法名之所以不用设置BeanClasss属性是因为,通过指定的静态方法或者指定的实例中的方法也能唯一确定一个Bean。
除此之外,注册@Bean形式得到的BeanDefinition时,还进行了一个isOverriddenByExistingDefinition(beanMethod, beanName)方法的判断,这个方法的主要作用是判断当前要注册的bean是否被之前已经存在的Bean覆盖了。
但是在直接通过AnnotatedBeanDefinitionReader#doRegisterBean方法注册Bean时是没有进行这个判断的,如果存在就直接覆盖了,而不会用之前的bd来覆盖现在要注册的bd。这是为什么呢?据笔者自己的理解,是因为Spring将Bean也是分成了三六九等的,通过@Bean方式得到的bd可以覆盖扫描出来的普通bd(ScannedGenericBeanDefinition),但是不能覆盖配置类,所以当已经存在的bd是一个ScannedGenericBeanDefinition时,那么直接进行覆盖,但是当已经存在的bd是一个配置类时,就不能进行覆盖了,要使用已经存在的bd来覆盖本次要注册的bd。
到此为止,我们就完成了Spring中的整个配置类解析、注册的相关源码分析,不过还没完,我们还得解决一个问题,就是为什么要在配置类上添加@Configuration注解,在之前的源码分析中我们知道,添加@Configuration注解的作用就是讲配置类标志成了一个full configurationClass,这个的目的是什么呢?本来是打算一篇文章写完的,不过实在是太长了,接近6w字,所以还是拆成了两篇,预知后事如何,请看下文:配置类为什么要添加@Configuration注解呢?
总结
清晰的知道了执行的流程,我们再来回想下postProcessBeanDefinitionRegistry做了什么。
码字不易,对你有帮助的话记得点个赞,关注一波哦,万分感谢!
最后
以上就是老迟到刺猬为你收集整理的java什么是设置类_你知道Spring是怎么解析配置类的吗?的全部内容,希望文章能够帮你解决java什么是设置类_你知道Spring是怎么解析配置类的吗?所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复