本文,咱们来分享 Spring Boot 自动配置的实现源码。在故事的开始,咱们先来讲两个事情:html
在这篇文章的开始,艿艿是有点混淆自动配置和自动装配的概念,后来通过 Google 以后,发现二者是截然不如同的:java
spring-boot-starter-web
以后,就自动引入了 Spring MVC 相关的 jar 包,从而自动配置 Spring MVC 。因此,不要和艿艿同样愚蠢的搞错落。git
胖友能够直接看 《详解 Spring Boot 自动配置机制》 文章的 「2、Spring Boot 自动配置」 小节,艿艿以为写的挺清晰的。github
下面,咱们即开始正式撸具体的代码实现了。web
org.springframework.boot.autoconfigure.@SpringBootApplication
注解,基本咱们的 Spring Boot 应用,必定会去有这样一个注解。而且,经过使用它,不只仅能标记这是一个 Spring Boot 应用,并且可以开启自动配置的功能。这是为何呢?spring
😈
@SpringBootApplication
注解,它在spring-boot-autoconfigure
模块中。因此,咱们使用 Spring Boot 项目时,若是不想使用自动配置功能,就不用引入它。固然,咱们貌似不太会存在这样的需求,是吧~数组
@SpringBootApplication
是一个组合注解。代码以下:网络
// SpringBootApplication.java |
下面,咱们来逐个看 @SpringBootApplication
上的每一个注解。app
Java 自带的注解。ide
java.lang.annotation.@Inherited
注解,使用此注解声明出来的自定义注解,在使用此自定义注解时,若是注解在类上面时,子类会自动继承此注解,不然的话,子类不会继承此注解。
这里必定要记住,使用 @Inherited
声明出来的注解,只有在类上使用时才会有效,对方法,属性等其余无效。
不了解的胖友,能够看看 《关于 Java 注解中元注解 Inherited 的使用详解》 文章。
Spring Boot 自定义的注解
org.springframework.boot.@SpringBootConfiguration
注解,标记这是一个 Spring Boot 配置类。代码以下:
// SpringBootConfiguration.java |
@Configuration
注解,因此二者功能也一致,能够将当前类内声明的一个或多个以 @Bean
注解标记的方法的实例归入到 Srping 容器中,而且实例名就是方法名。Spring 自定义的注解
org.springframework.context.annotation.@ComponentScan
注解,扫描指定路径下的 Component(@Componment
、@Configuration
、@Service
等等)。
不了解的胖友,能够看看 《Spring:@ComponentScan 使用》 文章。
Spring Boot 自定义的注解
org.springframework.boot.autoconfigure.@EnableAutoConfiguration
注解,用于开启自动配置功能,是 spring-boot-autoconfigure
项目最核心的注解。代码以下:
// EnableAutoConfiguration.java |
org.springframework.boot.autoconfigure.@AutoConfigurationPackage
注解,主要功能自动配置包,它会获取主程序类所在的包路径,并将包路径(包括子包)下的全部组件注册到 Spring IOC 容器中。代码以下:
// AutoConfigurationPackage.java |
org.springframework.context.annotation.@Import
注解,可用于资源的导入。状况比较多,能够看看 《六、@Import 注解——导入资源》 文章。@Import(AutoConfigurationImportSelector.class)
注解部分,是重头戏的开始。
org.springframework.context.annotation.@Import
注解,可用于资源的导入。状况比较多,能够看看 《六、@Import 注解——导入资源》 文章。org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
,实现 DeferredImportSelector、BeanClassLoaderAware、ResourceLoaderAware、BeanFactoryAware、EnvironmentAware、Ordered 接口,处理 @EnableAutoConfiguration
注解的资源导入。
#getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法,得到符合条件的配置类的数组。代码以下:
// AutoConfigurationImportSelector.java |
<1>
处,调用 #getSpringFactoriesLoaderFactoryClass()
方法,得到要从 META-INF/spring.factories
加载的指定类型为 EnableAutoConfiguration 类。代码以下:
// AutoConfigurationImportSelector.java |
<1>
处,调用 SpringFactoriesLoader#loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader)
方法,加载指定类型 EnableAutoConfiguration 对应的,在 META-INF/spring.factories
里的类名的数组。看看下图,胖友相信就明白了:
通常来讲,和网络上 Spring Boot 勇于这块的源码解析,咱们就能够结束了。若是单纯是为了了解原理 Spring Boot 自动配置的原理,这里结束也是没问题的。由于,拿到 Configuration 配置类后,后面的就是 Spring Java Config 的事情了。不了解的胖友,能够看看 《Spring 教程 —— 基于 Java 的配置》 文章。
😜 可是(“可是”同窗,你赶忙坐下),具备倒腾精神的艿艿,以为仍是继续瞅瞅 #getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法是怎么被调用的。因此,咱们来看看调用它的方法调用链,以下图所示:
#getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法被调用。③ 处,那么此处,就是问题的关键。代码以下:
艿艿:由于我还没特别完整的撸完 Spring Java Annotations 相关的源码,因此下面的部分,咱们更可能是看整个调用过程。😈 刚好,胖友也没看过,哈哈哈哈。
// ConfigurationClassParser#DeferredImportSelectorGroupingHandler.java |
<1>
处,调用 DeferredImportSelector.Group#process(AnnotationMetadata metadata, DeferredImportSelector selector)
方法,处理被 @Import
注解的注解。<2>
处,调用 DeferredImportSelector.Group#this.group.selectImports()
方法,选择须要导入的。例如:<1>
和 <2>
处,在 「5.3 AutoConfigurationGroup」 详细解析。#getImportGroup()
方法,得到对应的 Group 实现类。代码以下:
// AutoConfigurationImportSelector.java |
艿艿:注意,从这里开始后,东西会比较难。由于,涉及的东西会比较多。
AutoConfigurationGroup ,是 AutoConfigurationImportSelector 的内部类,实现 DeferredImportSelector.Group、BeanClassLoaderAware、BeanFactoryAware、ResourceLoaderAware 接口,自动配置的 Group 实现类。
// AutoConfigurationImportSelector#AutoConfigurationGroup.java |
entries
属性,AnnotationMetadata 的映射。其中,KEY 为 配置类的全类名。在后续咱们将看到的 AutoConfigurationGroup#process(...)
方法中,被进行赋值。例如:autoConfigurationEntries
属性,AutoConfigurationEntry 的数组。
其中,AutoConfigurationEntry 是 AutoConfigurationImportSelector 的内部类,自动配置的条目。代码以下:
// AutoConfigurationImportSelector#AutoConfigurationEntry.java |
AutoConfigurationGroup#process(...)
方法中,被进行赋值。例如:autoConfigurationMetadata
属性,自动配置的元数据(Metadata)。
经过 #getAutoConfigurationMetadata()
方法,会初始化该属性。代码以下:
// AutoConfigurationImportSelector#AutoConfigurationGroup.java |
@ConditionalOnClass({ Bucket.class, ReactiveCouchbaseRepository.class, Flux.class })
注解部分。autoConfigurationMetadata
属性,用途就是制定配置类(Configuration)的生效条件(Condition)。#process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector)
方法,进行处理。代码以下:
// AutoConfigurationImportSelector#AutoConfigurationGroup.java |
annotationMetadata
参数,通常来讲是被 @SpringBootApplication
注解的元数据。由于,@SpringBootApplication
组合了 @EnableAutoConfiguration
注解。deferredImportSelector
参数,@EnableAutoConfiguration
注解的定义的 @Import
的类,即 AutoConfigurationImportSelector 对象。<1>
处,调用 AutoConfigurationImportSelector#getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata)
方法,得到 AutoConfigurationEntry 对象。详细解析,见 「5.4 AutoConfigurationEntry」 。由于这块比较重要,因此先跳过去瞅瞅。<2>
处,添加到 autoConfigurationEntries
中。<3>
处,添加到 entries
中。#selectImports()
方法,得到要引入的配置类。代码以下:
// AutoConfigurationImportSelector#AutoConfigurationGroup.java |
<1>
处,若是为空,则返回空数组。<2.1>
、<2.2>
、<2.3>
处,得到要引入的配置类集合。😈 比较奇怪的是,上面已经作过一次移除的处理,这里又作一次。不过,没多大关系,能够先无视。<3>
处,处理,返回结果。
<3.1>
处,调用 #sortAutoConfigurations(Set<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata)
方法,排序。代码以下:
// AutoConfigurationImportSelector#AutoConfigurationGroup.java |
@Order
注解。<3.2>
处,建立 Entry 对象。<3.3>
处,转换成 List 。结果以下图:艿艿:略微有点艰难的过程。不过回过头来,其实也没啥特别复杂的逻辑。是不,胖友~
艿艿:这是一个关键方法。由于会调用到,咱们会在 「5.1 getCandidateConfigurations」 的方法。
#getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata)
方法,得到 AutoConfigurationEntry 对象。代码以下:
// AutoConfigurationImportSelector.java |
这里每一步都是细节的方法,因此会每个方法,都会是引导到对应的小节的方法。
虽然有点长,可是很不复杂。简单的来讲,加载符合条件的配置类们,而后移除须要排除(exclusion)的。
<1>
处,调用 #isEnabled(AnnotationMetadata metadata)
方法,判断是否开启。如未开启,返回空数组。详细解析,见 「5.4.1 isEnabled」 。<2>
处,调用 #getAttributes(AnnotationMetadata metadata)
方法,得到注解的属性。详细解析,见 「5.4.2 getAttributes」 。【重要】<3>
处,调用 #getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法,得到符合条件的配置类的数组。
嘻嘻,到达此书以后,整个细节是否是就串起来了!
<3.1>
处,调用 #removeDuplicates(List<T> list)
方法,移除重复的配置类。代码以下:
// AutoConfigurationImportSelector.java |
<4>
处,调用 #getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法,得到须要排除的配置类。详细解析,见 「5.4.3 getExclusions」 。
<4.1>
处,调用 #checkExcludedClasses(List<String> configurations, Set<String> exclusions)
方法,校验排除的配置类是否合法。详细解析,见 「5.4.4 checkExcludedClasses」 。<4.2>
处,从 configurations
中,移除须要排除的配置类。<5>
处,调用 #filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata)
方法,根据条件(Condition),过滤掉不符合条件的配置类。详细解析,见 《精尽 Spring Boot 源码分析 —— Condition》 文章。
<6>
处,调用 #fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions)
方法,触发自动配置类引入完成的事件。详细解析,见 「5.4.5 fireAutoConfigurationImportEvents」 。<7>
处,建立 AutoConfigurationEntry 对象。整个 「5.4 getAutoConfigurationEntry」 看完后,胖友请跳回 「5.3.3 selectImports」 。
#isEnabled(AnnotationMetadata metadata)
方法,判断是否开启自动配置。代码以下:
// AutoConfigurationImportSelector.java |
#getAttributes(AnnotationMetadata metadata)
方法,得到注解的属性。代码以下:
// AutoConfigurationImportSelector.java |
getAnnotationClass().getName()
返回的是 @EnableAutoConfiguration
注解,因此这里返回的注解属性,只能是 exclude
和 excludeName
这两个。举个例子,假设 Spring 应用上的注解以下:
|
#getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法,得到须要排除的配置类。代码以下:
// AutoConfigurationImportSelector.java |
该方法会调用以下的方法,比较简单,胖友本身瞅瞅。
// AutoConfigurationImportSelector.java |
#checkExcludedClasses(List<String> configurations, Set<String> exclusions)
方法,校验排除的配置类是否合法。代码以下:
// AutoConfigurationImportSelector.java |
exclusions
存在于 classpath 中,可是不存在 configurations
。这样作的目的是,若是不存在的,就不要去排除啦!#fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions)
方法,触发自动配置类引入完成的事件。代码以下:
// AutoConfigurationImportSelector.java |
<1>
处,调用 #getAutoConfigurationImportListeners()
方法,加载指定类型 AutoConfigurationImportListener 对应的,在 META-INF/spring.factories
里的类名的数组。例如:<2>
处,建立 AutoConfigurationImportEvent 事件。<3>
处,遍历 AutoConfigurationImportListener 监听器们,逐个通知。
<3.1>
处,调用 #invokeAwareMethods(Object instance)
方法,设置 AutoConfigurationImportListener 的属性。代码以下:
// AutoConfigurationImportSelector.java |
<3.2>
处,调用 AutoConfigurationImportListener#onAutoConfigurationImportEvent(event)
方法,通知监听器。目前只有一个 ConditionEvaluationReportAutoConfigurationImportListener 监听器,没啥逻辑,有兴趣本身看哈。
org.springframework.boot.autoconfigure.AutoConfigurationPackages
,自动配置所在的包名。可能这么解释有点怪怪的,咱们来看下官方注释:
Class for storing auto-configuration packages for reference later (e.g. by JPA entity scanner). |
@AutoConfigurationPackage
注解的类所在的包(package
),注册成一个 Spring IoC 容器中的 Bean 。酱紫,后续有其它模块须要使用,就能够经过得到该 Bean ,从而得到所在的包。例如说,JPA 模块,须要使用到。是否是有点神奇,艿艿也以为。
Registrar ,是 AutoConfigurationPackages 的内部类,实现 ImportBeanDefinitionRegistrar、DeterminableImports 接口,注册器,用于处理 @AutoConfigurationPackage
注解。代码以下:
// AutoConfigurationPackages#Registrar.java |
PackageImport 是 AutoConfigurationPackages 的内部类,用于得到包名。代码以下:
// AutoConfigurationPackages#Registrar.java |
<X>
处,调用 #register(BeanDefinitionRegistry registry, String... packageNames)
方法,注册一个用于存储报名(package
)的 Bean 到 Spring IoC 容器中。详细解析,见 「6.2 register」 。#register(BeanDefinitionRegistry registry, String... packageNames)
方法,注册一个用于存储报名(package
)的 Bean 到 Spring IoC 容器中。代码以下:
// AutoConfigurationPackages.java |
注册的 BEAN
的类型,为 BasePackages 类型。它是 AutoConfigurationPackages 的内部类。代码以下:
// AutoConfigurationPackages#BasePackages.java |
packages
属性的封装类。<1>
处,若是已经存在该 BEAN
,则修改其包(package
)属性。而合并 package
的逻辑,经过 #addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames)
方法,进行实现。代码以下:
// AutoConfigurationPackages.java |
<2>
处,若是不存在该 BEAN
,则建立一个 Bean
,并进行注册。#has(BeanFactory beanFactory)
方法,判断是否存在该 BEAN
在传入的容器中。代码以下:
// AutoConfigurationPackages.java |
#get(BeanFactory beanFactory)
方法,得到 BEAN
。代码以下:
// AutoConfigurationPackages.java |