每一位看到本篇文章的小伙伴,你们好,我是黄俊懿。
上一篇文章介绍了关于配置解析与BeanDefinition加载注册的源码解析,最后留下了ConfigurationClassPostProcessor的具体逻辑没有介绍。
人人都能看懂的Spring底层原理,看完绝对不会懵逼
简单易懂的Spring扩展点详细解析,看不懂你来打我
人人都能看懂的Spring源码解析,配置解析与BeanDefinition加载注册
本篇文件将会接着上一篇文章,对ConfigurationClassPostProcessor里面的具体逻辑进行详细解析,还是尽量做到对新手友好,尽量以图解的形式进行解析。
ConfigurationClassPostProcessor的主要作用就是解析@Configuration注解修饰的配置类里面的配置信息(比如@Bean),生成BeanDefinition,注册到容器中。
ConfigurationClassPostProcessor对于被@Configuration注解修饰的配置类、会解析以下几种配置信息,都是以注解的方式进行配置:
本篇文章主要介绍ConfigurationClassPostProcessor对 @ComponentScan、@Import、@Bean这三个注解的处理流程。
首先,ConfigurationClassPostProcessor会从容器中取出所有BeanDefinition,然后过滤出被@Configuration注解修饰的类的BeanDefinition,放到一个集合中。
该集合会交给ConfigurationClassParser进行解析,ConfigurationClassParser顾名思义,就是配置类解析器,是真实负责配置类解析。
解析结果会生成BeanDefinition注册到容器中,然后在此从容器中取出所有的BeanDefinition,看是否有新添加的BeanDefinition它的类是被@Configuration修饰的,如果有,要进行一轮的解析。
接下来就是ConfigurationClassParser里面的解析逻辑。
对于 @ComponentScan 的处理,会进行包扫描,把扫描到的被@Configuration注解和@Component注解修饰的类,解析生成BeanDefinition,注册到容器中。
从扫描生成的BeanDefinition中,过滤出被@Configuration注解修饰的类的BeanDefinition,递归进行解析。
对于@Import注解的处理,会判断导入的是什么类型:
对于 @Bean 注解的处理,会把@Bean注解修饰的方法和方法所在的类的Class对象,封装成一个BeanMethod对象,也是放入到ConfigurationClass对象中,待一轮解析完毕后,会将其解析成一个BeanDefinition注册到容器中。
上面已经对ConfigurationClassPostProcessor的原理进行讲解,下面通过代码走读进行验证。
判断该BeanDefinition对应的类上,是否被@Configuration注解修饰,如果是的话,则放入到configCandidates集合中
代码流程图:
创建一个ConfigurationClassParser对象,然后把configCandidates集合copy到candidates集合中,然后调用ConfigurationClassParser的parse方法,解析candidates里面的BeanDefinition。
代码流程图:
进入到ConfigurationClassParser的doProcessConfigurationClass方法,里面是真正处理配置类解析的逻辑。
可以看到里面会对@ComponentScan、@Import、@Bean等注解的处理,当然还有其他的,截图没有截全。
代码流程图:
获取当类上所有的@ComponentScans和@ComponentScan注解,封装成一个个的AnnotationAttributes对象,放入到一个Set集合中,一个AnnotationAttributes对象包含了一个@ComponentScans注解或@ComponentScan注解的注解属性,比如包扫描路径。
然后循环遍历AnnotationAttributes对象集合,调用componentScanParser的parse方法。
componentScanParser是ConfigurationClassParser的成员变量,类型是ComponentScanAnnotationParser,是ConfigurationClassParser专门用于处理@ComponentScan注解的解析。
componentScanParser的parse方法,里面会创建一个ClassPathBeanDefinitionScanner对象,该对象专门用于包扫描。获取注解上的包扫描路径,调用ClassPathBeanDefinitionScanner的doScan方法,真正进行包扫描。
findCandidateComponents(basePackage)方法里面进行包扫描,扫描结果返回一个BeanDefinition集合,然后遍历该集合的每一个BeanDefinition,注册到容器中。
返回到doProcessConfigurationClass方法,获取到包扫描返回的BeanDefinition集合,检查如果有@Configuration注解修饰的,会递归掉ConfigurationClassParser的parse方法。
代码流程图:
@Import注解的处理逻辑,在processImports方法里面。
如果导入的是ImportSelector类型,会回调selectImports方法,返回一个类全限定名数组importClassNames,然后asSourceClasses方法会反射获取Class对象并封装为SourceClass对象,返回一个SourceClass类型的集合,然后递归调用processImports方法,再次进入@Import的处理逻辑。
代码流程图:
如果是ImportBeanDefinitionRegistrar类型,则把它放到ConfigurationClass对象里面,在这一轮的ConfigurationClassParser的parse方法结束,会统一进行回调。
回到ConfigurationClassPostProcessor的processConfigBeanDefinitions方法,可以看到,在这一轮的ConfigurationClassParser的parse方法结束后,从this.reader.loadBeanDefinitions(configClasses) 这一行代码进去,最终会看到回调ConfigurationClass里面暂存的ImportBeanDefinitionRegistrar的registerBeanDefinitions方法,然后进入到我们自定义的注册BeanDefinition的逻辑里面。
代码流程图:
回到processImports方法,看完@Import注解处理的最后一个分支。
如果是@Configuration注解修饰的配置类,则递归调用ConfigurationClassParser的processConfigurationClass方法,再次进入配置类的解析逻辑。
代码流程图:
回到ConfigurationClassParser的doProcessConfigurationClass方法。
retrieveBeanMethodMetadata(sourceClass) 里面,反射获取Method对象,然后判断是否被@Method注解修饰,返回一个MethodMetadata类型的集合。然后再封装为BeanMethod对象,放入到ConfigurationClass对象中。
也是等这一轮的ConfigurationClassParser的parse方法结束,会统一进行注册。
ConfigurationClassParser的parse方法结束后,也是从this.reader.loadBeanDefinitions(configClasses)进入到刚刚对ImportBeanDefinitionRegistrar进行回调的处理的地方。可以看到上面就是对BeanMethod的处理,对@Bean注解修饰的方法进行注册。
生成了一个ConfigurationClassBeanDefinition类型的BeanDefinition,然后给该BeanDefinition配置了factoryBeanName属性和factoryMethodName属性,表示把@Bean注解修饰的方法作为工厂方法bean,注册到容器中。
代码流程图:
把已经解析过的配置类,加入到alreadyParsed(已解析集合) 中,然后清空candidates(待解析集合),然后检查容器中是否还有被@Configuration注解修饰然后又不在已解析集合中的BeanDefinition(也就是这一轮解析新添加到容器中的配置类),如果有,则放入待解析集合中,只要待解析集合不为空,就继续进行下一轮解析。
代码流程图:
至此,整个ConfigurationClassPostProcessor的核心逻辑就基本结束了。
以上就是ConfigurationClassPostProcessor的核心逻辑,下面用一张图做个总结。