目录
对Springboot有所了解的读者,对一些理念想必很熟悉,如Springboot工程会默认加载当前主启动类下的所有类文件到IOC容器,以及Springboot可以通过自动装配的功能,实现对外部功能的无缝引入。
怎么说呢?大的方向是这样,但是如果更近一步,要问一下这些功能具体是如何实现的,只有通过源码才能完整而清晰的了解其每一步的实现意图。
下面我们结合Springboot的启动过程,来直接进入源码,再现Springboot的依托主启动类扫包和自动装配原理。
首先,通过主启动类的main方法进入SpringApplication的run方法:
- public static void main(String[] args) {
- SpringApplication.run(new Class[]{BookstoreApplication.class}, args);
- }
之后再通过如下顺序:
this.refreshContext(context); (SpringApplication) --->>> this.refresh(context); (SpringApplication) --->>> refresh();(AbstractApplicationContext) --->>> this.invokeBeanFactoryPostProcessors(beanFactory);(AbstractApplicationContext)
最终进入类PostProcessorRegistrationDelegate中的BeanFactory后置处理器执行方法invokeBeanFactoryPostProcessors。
在此方法中,第一次执行方法
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
时,会执行一个特殊的BeanDefinitionRegistry后置处理器——ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法。
在此方法中会通过
this.processConfigBeanDefinitions(registry);
下的
parser.parse(candidates);
真正进入主启动类下项目工程类信息注册,以及Spring自动装配功能。
说了这么多,其实是为了让读者可以通过断点,启动任意一个Springboot项目,自己去断点调试一下,验证笔者所说,毕竟眼见为实,耳听为虚,实践才是最好的老师。
有兴趣的读者,可以直接进入ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法,再进入parser.parse(candidates),就可以直接而快速的了解接下来笔者要讲解的源码。
关于bean信息注册的方法,基本都在ConfigurationClassParser这个类中,processConfigBeanDefinitions实现bean信息注册的入口方法在ConfigurationClassParser的parse方法中:
- public void parse(Set
configCandidates) { - //主启动类bean信息集合
- Iterator var2 = configCandidates.iterator();
- while(var2.hasNext()) {
- BeanDefinitionHolder holder = (BeanDefinitionHolder)var2.next();
- BeanDefinition bd = holder.getBeanDefinition();
- try {
- if (bd instanceof AnnotatedBeanDefinition) {
- //注册项目工程类信息
- this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName());
- .......
- }
- //注册Spring提供默认类信息
- this.deferredImportSelectorHandler.process();
- }
this.parse方法主要就是依据其传入的参数,也就是主启动类的bean定义信息BeanDefinition和BeanDefinitionHolder构造一个新的对象ConfigurationClass,再以ConfigurationClass和DEFAULT_EXCLUSION_FILTER,作为参数进入真正的执行方法processConfigurationClass。
- private static final Predicate
DEFAULT_EXCLUSION_FILTER = (className) -> { - return className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype.");
- };
进入processConfigurationClass后,首先会判断当前当前主启动类ConfigurationClass是否需要被拦截,主要是根据@Conditional注解和DEFAULT_EXCLUSION_FILTER的信息来判断:
- protected void processConfigurationClass(ConfigurationClass configClass, Predicate
filter) throws IOException { - if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
在执行完过滤条件后,再以ConfigurationClass和DEFAULT_EXCLUSION_FILTER参数构造一个参数SourceClass,其是ConfigurationClassParser的一个内部类,最终把ConfigurationClass、SourceClass和DEFAULT_EXCLUSION_FILTER三个作为参数,来执行下一步的类信息解析:
- do {
- sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
- } while(sourceClass != null);
有人看到这一步就会很奇怪,configClass和sourceClass,这两参数基本都是一样,只不过另一个又包装了一层,这样构造的意义在哪里呢?其原因主要也是为了框架的需要,这样可以利用递归,把主启动类包下的每一个已经加入IOC容器的类都当作是一个配置类,可以任意向下扩展。
它可以在自身已经被@Component或其他注解形式修饰,在已经加入IOC容器的前提下,通过@ComponentScan注解、@Import注解或者@ImportResource注解,来给IOC容器新增我们需要的指定类。
下面通过doProcessConfigurationClass方法分别介绍:
- protected final ConfigurationClassParser.SourceClass doProcessConfigurationClass(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass, Predicate
filter) throws IOException { - if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
- this.processMemberClasses(configClass, sourceClass, filter);
- }
在介绍这个方法之前,我们先说一个前提,只要满足条件的基础上,方法正常执行,会返回一个configClass,被缓存到当前类的一个Map属性configurationClasses中,后面会被构造成bean的定义信息被注册到IOC容器。
在此基础上,我们会对当前类,有可能出现的扩展情况,进行处理,通俗说,就是在当前类配置了一些属性,需要把新的类信息注入IOC容器。
如果当前类带有@Component注解,我们会对其内部类进行解析,看其内部类是否配置了@Component注解,需要被注入IOC容器:
- private void processMemberClasses(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass, Predicate
filter) throws IOException { - //获取内部类
- Collection
memberClasses = sourceClass.getMemberClasses(); - //如果存在内部类
- if (!memberClasses.isEmpty()) {
- List
candidates = new ArrayList(memberClasses.size()); - Iterator var6 = memberClasses.iterator();
- ConfigurationClassParser.SourceClass candidate;
- while(var6.hasNext()) {
- candidate = (ConfigurationClassParser.SourceClass)var6.next();
- //内部类是否需要被注入IOC容器
- if (ConfigurationClassUtils.isConfigurationCandidate(candidate.getMetadata()) && !candidate.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
- //满足条件注入
- candidates.add(candidate);
- ......
- //递归方法,最终会再调用doProcessConfigurationClass
- this.processConfigurationClass(candidate.asConfigClass(configClass), filter);
- .......
实操用法如下:
- @SpringBootApplication
- public class BookstoreApplication {
- public static void main(String[] args) {
- SpringApplication.run(new Class[]{BookstoreApplication.class, TestApp.class}, args);
- }
- @Component
- class TestClass{
- private final String name = "123";
- TestClass(){
- System.out.println(name);
- }
- }
- }
如上,会通过方法processMemberClasses把TestClass信息注入IOC。
因为第一次进入doProcessConfigurationClass方法,传入的就是已经加入容器内的主启动类,所以会解析主启动类的注解@ComponentScan,大致和前文介绍的一样,如果@ComponentScan的basePackages和basePackageClasses属性都没有定义的话,就加载主启动类当前路径下各级文件夹中所有的class:
- Set
componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); - if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
- Iterator var14 = componentScans.iterator();
- while(var14.hasNext()) {
- AnnotationAttributes componentScan = (AnnotationAttributes)var14.next();
- Set
scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
下一步需要进行判断,这些加载的class是否满足立即被加载进容器的条件,简单来说就是类上是否包含四个注解:
[org.springframework.context.annotation.Import, org.springframework.stereotype.Component, org.springframework.context.annotation.ImportResource, org.springframework.context.annotation.ComponentScan]
如果含有其中之一, 则使用递归调用的方式,重新调用ConfigurationClassParser类的parser方法:
- if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
- this.parse(bdCand.getBeanClassName(), holder.getBeanName());
- }
以这样的方式,把加入容器中的每一个类都当作了潜在的配置类,可以灵活的引用没有配置进容器的类。
实操示例如下:
- @Configuration
- @ComponentScan("test")
- public class TestConfiguration {
- }
当前类在com包下,被扫描进IOC容器,因为使用了@ComponentScan注解,可以加载外部包test下,标注了@Component的类:
- package test;
- import org.springframework.stereotype.Component;
-
- @Component
- public class TestComponentScan{
- TestComponentScan(){
- System.out.println("test----TestComponentScan if exist in IOC");
- }
- }
this.processImports(configClass, sourceClass, this.getImports(sourceClass), filter, true);
把当前类中注解了@Import的类信息注册到容器中,注意这里有一种特殊情形需要注意,如果式主启动类,由于它通过@EnableAutoConfiguration复合注解,组合了@Import({AutoConfigurationImportSelector.class})注解,相当于要加载一些Spring自带的需要加入容器的类,这就属于特殊情形。
Spring框架使AutoConfigurationImportSelector实现了一个特殊接口DeferredImportSelector,把它归属到特殊的一类Import,等当前工程项目中所有的类注册完成后再继续加载:
- if (selector instanceof DeferredImportSelector) {
- this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector);
- }
此时,其他的普通类通过@Import注解依然通过递归调用的方式,可以对@Import其他引入的类进行继续解析:
this.processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
根据注解信息,找到Spring的配置文件路径,之后加载配置信息,把配置的bean信息注册到IOC容器:
importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
具体操作示例如下:
- @SpringBootApplication
- @ImportResource(locations={"classpath:beans.xml"})
- public class BookstoreApplication {
- public static void main(String[] args) {
- SpringApplication.run(BookstoreApplication.class, args);
beans.xml具体信息如下,注册一个不在当前主启动类的bean:
- "1.0" encoding="UTF-8"?>
"http://www.springframework.org/schema/beans" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
-
-
-
"helloService" class="test.HelloService">
在项目启动后,HelloService就可以直接从容器中获取:
- public class HelloService {
- HelloService(){
- System.out.println("基于@ImportResource注解的bean--HelloService注入");
- }
- }
在实际的项目中,还有一种情况用的比较多,比如当前类标注了@Configuration,就代表是一个配置类,之后在方法中可以用@Bean注解的方式来把返回值注入到容器中。
- Set
beanMethods = this.retrieveBeanMethodMetadata(sourceClass); - Iterator var18 = beanMethods.iterator();
- while(var18.hasNext()) {
- MethodMetadata methodMetadata = (MethodMetadata)var18.next();
- configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
- }
具体用法如下:
- @Configuration
- public class TestComponentScan{
- @Bean
- public TestBeanMethod testBeanMethod(){
- return new TestBeanMethod();
- }
- }
主要是针对当前类已经实现的接口中,其方法上,是否有需要注入IOC容器中的类:
this.processInterfaces(configClass, sourceClass);
示例如下:
- public interface InterfaceService {
- @Bean
- default Object interfaceBean(){
- return new Object();
- }
- }
- @Component
- public class CustomApplicationListener implements InterfaceService {
-
- }
CustomApplicationListener 会在自身注入容器的同时,把InterfaceService中注入了@Bean方法interfaceBean也加入进容器。
其代码实现为:
- private void processInterfaces(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass) throws IOException {
- Iterator var3 = sourceClass.getInterfaces().iterator();
- while(var3.hasNext()) {
- ConfigurationClassParser.SourceClass ifc = (ConfigurationClassParser.SourceClass)var3.next();
- Set
beanMethods = this.retrieveBeanMethodMetadata(ifc); - Iterator var6 = beanMethods.iterator();
- while(var6.hasNext()) {
- MethodMetadata methodMetadata = (MethodMetadata)var6.next();
- if (!methodMetadata.isAbstract()) {
- configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
- }
- }
- this.processInterfaces(configClass, ifc);
- }
- }
这些beanMethods的bean定义信息会依附于当前类bean信息而存在,具体表现为存在ConfigurationClass类的Set属性beanMethods中,在需要的时候解析出来。
现在到了最后一步,首先判断这个类是否还有父类,如果没有,直接返回空,如果有,获取父类后,继续判断,如果这个父类不为空、包名开头不是java且不能是已经被解析的父类,否则返回空:
- if (sourceClass.getMetadata().hasSuperClass()) {
- String superclass = sourceClass.getMetadata().getSuperClassName();
- if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
- this.knownSuperclasses.put(superclass, configClass);
- return sourceClass.getSuperClass();
- }
- }
- return null;
取得返回值后,跳出循环,把当前ConfigurationClass返回,缓存到Map中:
- do {
- sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
- } while(sourceClass != null);
- this.configurationClasses.put(configClass, configClass);
经过ConfigurationClassParser类中的parse方法调用AnnotatedBeanDefinition类型的parse方法,我们完成了对当前项目工程下所有类,注册到IOC容器中的操作。
下面需要继续介绍,Spring自带的框架类,如何加入容器。在ConfigurationClassParser类中的执行方法为:
this.deferredImportSelectorHandler.process();
首先会获取到前面我们通过主启动类上的@Import注解导入的自动装配类信息,之后新建一个Group用来对这一类信息归类,把@Import中的信息导入到Group,之后对Group中的信息进行处理:
- public void process() {
- List
deferredImports = this.deferredImportSelectors; - this.deferredImportSelectors = null;
- try {
- if (deferredImports != null) {
- ......
- deferredImports.forEach(handler::register);
- handler.processGroupImports();
- }
- } finally {
- this.deferredImportSelectors = new ArrayList();
- }
- }
- public void processGroupImports() {
- Iterator var1 = this.groupings.values().iterator();
-
- while(var1.hasNext()) {
- ConfigurationClassParser.DeferredImportSelectorGrouping grouping = (ConfigurationClassParser.DeferredImportSelectorGrouping)var1.next();
- Predicate
exclusionFilter = grouping.getCandidateFilter(); - grouping.getImports().forEach((entry) -> {
- ConfigurationClass configurationClass = (ConfigurationClass)this.configurationClasses.get(entry.getMetadata());
最后通过grouping.getImports()方法来调用主启动类导入的类AutoConfigurationImportSelector中的process方法,来进行自动装配:
- public Iterable
getImports() { - Iterator var1 = this.deferredImports.iterator();
-
- while(var1.hasNext()) {
- ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
- this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
- }
进入AutoConfigurationImportSelector:
- public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
- Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
- return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
- });
- AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(this.getAutoConfigurationMetadata(), annotationMetadata);
获取配置实体:
- protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
- if (!this.isEnabled(annotationMetadata)) {
- return EMPTY_ENTRY;
- } else {
- AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
- List
configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
执行扫描方法:
- protected List
getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { - List
configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); - Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
- return configurations;
- }
通过this.getSpringFactoriesLoaderFactoryClass()方法扫描META-INF/spring.factories中key为EnableAutoConfiguration的全类名:
- protected Class> getSpringFactoriesLoaderFactoryClass() {
- return EnableAutoConfiguration.class;
- }
Spring框架的示例配置为:
- # Auto Configure
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
以上就是Spring自动装配的全部内容。
到了这一步,我们才讲完了把所有需要注册进容器的类缓存到一个Map集合,下一步就是取到Map集合的值,然后把值信息作为bean的定义信息,真正注册到容器中:
- Set
configClasses = new LinkedHashSet(parser.getConfigurationClasses()); - configClasses.removeAll(alreadyParsed);
- if (this.reader == null) {
- this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
- }
-
- this.reader.loadBeanDefinitions(configClasses);
这样,就完成了ConfigurationClassPostProcessor类中,第二次BeanDefinitionRegistry后置处理器中指定方法的执行:
- invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
- currentRegistryProcessors.clear();
执行完成后,清空currentRegistryProcessors,为下一次执行BeanDefinitionRegistry后置处理器方法做准备。
想必很多读者对主启动类下包扫描,和Springboot自动装配原理都不陌生,这里只是给大家提供了一个源码级别的验证,让大家能够更清晰而完整的了解Springboot的一些原理。本文也结合了一些具体示例,希望对大家有所帮助。由于笔者水平有限,有不足之处,也望大家多多指教、包涵!