在分析源码之前,我们先了解下自动装配的理论。没有理论的支撑,我们很难了解到源码的精华。
起步依赖
Spring Boot通过起步依赖为项目的依赖管理提供帮助。起步依赖其实就是特殊的Maven依 赖和Gradle依赖,利用了传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制 的依赖。
众多的 起步依赖 均配置在 spring-boot-starter-parent
中:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
复制代码
Spring Boot通过提供众多起步依赖降低项目依赖的复杂度。
自动装配
Spring Boot的 自动配置 是应用程序启动时的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。
下面这些情况都是Spring Boot的自动配置要考虑的。
- Spring的
JdbcTemplate
是不是在Classpath里?如果是,并且有DataSource
的Bean,则自动配置一个JdbcTemplate
的Bean。 -
Spring Security
是不是在Classpath里?如果是,则进行一个非常基本的Web安全设置。
每当应用程序启动的时候,Spring Boot的自动配置都要做将近200个这样的决定。所有这些自动配置就是为了尽量不让我们自己写配置。
下面我们具体分析下 JdbcTemplate
和 Spring Security
自动装配的过程:
JdbcTemplate
Spring Boot的 DataSourceAutoConfiguration
中定义的 JdbcTemplate
Bean就是一个非常简单的例子,演示了 @ConditionalOnMissingBean
如何工作:
@Bean
@ConditionalOnMissingBean(JdbcOperations.class)
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(this.dataSource);
}
复制代码
jdbcTemplate()
方法上添加了 @Bean
注解,在需要时可以配置出一个 JdbcTemplate
Bean。
但它上面还加了 @ConditionalOnMissingBean
注解,要求当前不存在 JdbcOperations
类型的Bean时才生效。
如果当前已经有一个 JdbcOperations
Bean了,条件即不满足,不会执行 jdbcTemplate()
方法。
Spring Security
Spring Boot自动配置的安全配置时,最重要的一个类是 SpringBootWebSecurityConfiguration
。
以下是其中的一个代码片段:
@Configuration @ConditionalOnClass(WebSecurityConfigurerAdapter.class) @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) @ConditionalOnWebApplication(type = Type.SERVLET) public class SpringBootWebSecurityConfiguration { @Configuration @Order(SecurityProperties.BASIC_AUTH_ORDER) static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter { } } 复制代码
首先我们分析下,这些注解。
-
@ConditionalOnClass
的作用为项目中只有引入security相关的包,才会构建这个Bean。 -
@ConditionalOnMissingBean
说明当容器中没有WebSecurityConfigurerAdapter
实例时,将采用默认配置。 -
@ConditionalOnWebApplication
说明这必须是个Web应用,并且类型为servlet
。
自定义配置
虽然Spring Boot提供了一些基本的自动配置,但是很多时候我们还是需要自己覆盖一些配置以满足需要。
以security为例,我们自定义配置类,需要继承抽象类 WebSecurityConfigurerAdapter
,并注入到容器即可
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { } 复制代码
我们测试一下,看是否已经注入到容器
测试类:
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SecurityConfig.class);
System.out.println(applicationContext.getBean(WebSecurityConfigurerAdapter.class));
}
}
复制代码
结果证明已经注入到容器中:

版本
springboot版本:2.3.1.RELEASE
spring版本:5.2.7.RELEASE
源码分析
通过上面理论的描述,接下来我们分析Springboot相关源码,看springboot到底怎么实现的自动装配?
概述
我们先看一张图:

注解:
-
@SpringBootApplication
:这是springboot最核心的注解,当然也是组合注解 -
@EnableAutoConfiguration
是自动装配的总开关。我们将以该注解入手,增强对自动配置的理解。 -
@Import(AutoConfigurationImportSelector.class)
导入自动配置的ImportSelectot
类。
核心类及方法:
-
AutoConfigurationImportSelector
导入需要自动装配的类或Bean。 -
getCandidateConfigurations
获取所有组件的配置类。 -
AutoConfigurationMetadataLoader.loadMetadata
加载所有自动装配组件配置类的条件(@Conditional
过滤条件)。 -
filter.match(candidates, this.autoConfigurationMetadata)
对各组件中全部配置类根据@Conditional
进行条件过滤。
配置文件:
META-INF/spring.factories
存放该组件全部配置类的全路径名。
META-INF/spring-autoconfigure-metadata.properties
存储该组件装载配置类时的全部过滤条件。
源码细节
SpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration // 开启自动装配开关
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
复制代码
EnableAutoConfiguration
该注解起到打开自动装配总开关的作用。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage // 导入selector,识别各组件中的AutoConfigutaion类并装载到容器中 @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { // 自动装配开关,默认true,可在application.properties中设置 String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; // 不需要装载的bean Class<?>[] exclude() default {}; // 不需要装载的bean String[] excludeName() default {}; } 复制代码
重点 AutoConfigurationImportSelector
该类实现 ImportSelector
接口,最重要的是实现 selectImports
方法,该方法的起到的作用是,根据配置文件( spring.factories
),将需要注入到容器的bean注入到容器。
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 判断自动装配开关是否打开 if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } // 获取所有需要装配的bean AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } 复制代码
首先我们看下,怎样判断自动装配开关的:
protected boolean isEnabled(AnnotationMetadata metadata) {
// 判断当前实例的class
if (getClass() == AutoConfigurationImportSelector.class) {
// 返回 spring.boot.enableautoconfiguration 的值,如果为null,返回true
// spring.boot.enableautoconfiguration 可在配置文件中配置,不配则为null
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}
复制代码
接下来,我们看如何获取需要装配的bean:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 检查自动装配开关
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取EnableAutoConfiguration中的参数,exclude()/excludeName()
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取需要自动装配的所有配置类,读取META-INF/spring.factories
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重,List转Set再转List
configurations = removeDuplicates(configurations);
// 从EnableAutoConfiguration的exclude/excludeName属性中获取排除项
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查需要排除的类是否在configurations中,不在报错
checkExcludedClasses(configurations, exclusions);
// 从configurations去除exclusions
configurations.removeAll(exclusions);
// 对configurations进行过滤,剔除掉@Conditional条件不成立的配置类
configurations = getConfigurationClassFilter().filter(configurations);
// 把AutoConfigurationImportEvent绑定在所有AutoConfigurationImportListener子类实例上
fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回(configurations, exclusions)组
return new AutoConfigurationEntry(configurations, exclusions);
}
复制代码
可见 selectImports()
是 AutoConfigurationImportSelector
的 核心方法
该方法的功能主要是以下三点:
- 获取
META-INF/spring.factories
中EnableAutoConfiguration
所对应的Configuration
类列表 - 由
@EnableAutoConfiguration
注解中的exclude/excludeName
参数筛选一遍 - 再由私有内部类
ConfigurationClassFilter
筛选一遍,即不满足@Conditional
的配置类
读取配置文件
META-INF/spring-autoconfigure-metadata.properties
:
在私有静态内部类 ConfigurationClassFilter
的构造器中初始化读取 META-INF/spring-autoconfigure-metadata.properties
,代码如下
ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) { this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader); this.filters = filters; } 复制代码
AutoConfigurationMetadataLoader
:
final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
private AutoConfigurationMetadataLoader() {
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
......
}
复制代码
META-INF/spring.factories
:
通过 getCandidateConfigurations
方法读取 META-INF/spring.factories
中配置类:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 加载所有META-INF/spring.factories中的配置类 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), 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; } 复制代码
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } // 该方法读取META-INF/spring.factories private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // FACTORIES_RESOURCE_LOCATION : META-INF/spring.factories Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } 复制代码
扩展
对于官方组件,是基于condition条件来决定对于类是否要自动装配,对于第三方组件,是采用spi机制 来实现扩展
- 官方包 spring-boot-starter-xxx
- 第三方包 xxx-spring-boot-starter
不论官方组件还是第三方组件都是通过以上机制进行自动装配。
Springboot的自动配置的核心:通过 ImportSelector
实现批量导入各组件的配置类到IOC容器中。

知识延伸:
@Conditional
详解: juejin.im/post/5ef73e…
@Enable*
模块驱动详解: juejin.im/post/5ef73f…
ImportSelector
批量动态导入Bean详解: juejin.im/post/5ef73f…
原文
https://juejin.im/post/5ef740526fb9a07ebd4a59e0
本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » SpringBoot自动装配原理与源码分析