avatar

目录
Spring Boot 的自动配置原理

从 Spring 4.0时代开始,伴随着 Spring Boot 的崛起,依赖着其提供的自动装配功能,人们编写 web application 的时候终于可以摆脱那一大堆的 XML 配置文件了,从而真正意义上做到 Spring 应用的开箱即用了。也有人说,Spring Boot 就是 Spring Framework + AutoConfiguration. 本文就来探究一下 Spring Boot 自动装配的实现原理。

Spring Boot 的自动装配,说白了就是 Spring 容器在应用启动时会在应用上下文(ApplicationContext)当中自动注册并暴露一些 bean 供应用在运行时使用,而这些 bean 并不需要我们手动的去写 XML 配置文件或者通过提供注解的方式事先声明,Spring 容易自然而然就帮用户把 bean 创建好了并放置在了容器当中。当客户端需要用到时,通过类似 @Autowired 注解的方式注入进来就可以直接使用了。究其原因,就是自动装配,Spring 非常贴心的在背后默默地做了很多工作。

既然是 Spring Boot 在启动时做的工作,那我们就从应用程序的入口处开始我们的探索之旅,按图索骥,一步步地看看到底是怎么一回事。

java
1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class})
public class DatasourceDemoApplication {

public static void main(String[] args) {
SpringApplication.run(DatasourceDemoApplication.class, args);
}
}

一般 Spring Boot 应用的启动类都长成上面这个样子,关键就在于 @SpringBootApplication 这个注解。注解的源码如下;

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@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 {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};

@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "nameGenerator"
)
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}

可以看到,这个注解上面也标注了很多注解。前面几个就不用多说了,是编写注解类的固定写法。由于我们要找跟自动装配相关的内容,所以关键在于 @EnableAutoConfiguration 注解。再点进来看看:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

Class<?>[] exclude() default {};

String[] excludeName() default {};
}

重点在于 @Import 注解,@Import 注解的作用在其官方 API 文档中是这么描述的:

Indicates one or more component classes to import — typically @Configuration classes.

Provides functionality equivalent to the <import/> element in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes (as of 4.2; analogous to AnnotationConfigApplicationContext.register(java.lang.Class...)

就是说,@Import 注解是用来导入一些组件类以供容器使用的,这个注解的作用等同于在 XML 配置中写上 <import resource="xxx" /> 这么一段。常见的可导入的类有如 一些标注了 @Configuration 注解的类,或者是一些 XXXImportSelector 这样的类。那么这里显然,我们关注的重点就在于被 Import 进来的 AutoConfigurationImportSelector.class这个类了。

AutoConfigurationImportSelector.class这个类中,有这么一段方法:

java
1
2
3
4
5
6
7
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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;
}

这个方法会使用 SpringFactoriesLoader 去加载 classpath 当中位于 /META-INF/spring.factories文件中的所有类,并返回所有被自动装配的类的名称集合。spring.factories 文件中的内容大概就是如下这样:

properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
...

可以看到这个文件中罗列了这么多的类可供自动装配,这些类来自于不同的 spring-boot-starter-xxx.jar, 那么问题就来了:启动时是不是会将它们全部装载呢?

答案显然是否定的,看到getCandidateConfigurations() 方法的调用处,也就是getAutoConfigurationEntry() 方法内,该方法源码如下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

其中有两处处方法 getExclusions()configurations.removeAll(exclusions), 即获取并排除掉那些我们不希望 Spring 框架帮我们自动装配的类,可以通过在 @SpringBootApplication 注解上加上 exclude={XXX.class} 来排除某些类 。

经过 exclude 的处理过后,那么是否自动装配进来的类就会全部生效呢?

哈哈,显然答案也是否定的!别忘了 Spring 还为我们提供了一系列的 @Conditional 注解。常用的诸如有以下这么一些:

  • @ConditionalOnBean 当容器中有指定的 bean 的情况下
  • @ConditionalOnMissingBean 当容器中没有指定的 bean 的情况下
  • @ConditionalOnClass 当类路径下有指定的类的情况下
  • @ConditionalOnProperty 当某个属性配置满足指定条件值的情况下
  • @ConditionalOnExpression 当 SpEL 表达式作为判断条件且的值为真的情况下才进行实例化

当不满足上述这些注解的条件时,Spring 会自动地帮我们过滤过那些自动装配类,剩下的才是 Spring 最终会帮我们装载到容器中的类。

在应用启动前修改其日志级别为 debug, 可以在日志中看到 Spring 在启动时自动装载了哪些类。 就拿文章开头的 DatasourceDemoApplication 这个类的启动日志输出来举例好了,它在控制台的日志打印如下:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:
-----------------

AopAutoConfiguration matched:
- @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)

AopAutoConfiguration.ClassProxyingConfiguration matched:
- @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
- @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)

......

Negative matches:
-----------------

ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)

AopAutoConfiguration.AspectJAutoProxyingConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'org.aspectj.weaver.Advice' (OnClassCondition)

AppOpticsMetricsExportAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'io.micrometer.appoptics.AppOpticsMeterRegistry' (OnClassCondition)
......

Exclusions:
-----------

org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration

org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration

Unconditional classes:
----------------------

org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration

org.springframework.boot.actuate.autoconfigure.availability.AvailabilityHealthContributorAutoConfiguration

org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration

org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration

org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration

org.springframework.boot.actuate.autoconfigure.web.mappings.MappingsEndpointAutoConfiguration

org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration

org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration

org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration

org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration

拢共分为四类:

  • Positive matches:@Conditional 条件为 true, 容器自动装配
  • Negative matches: @Conditional 条件为 false, 不装配
  • Exclusions: 特别指定的不要自动装配的类
  • Unconditional classes: 无条件地一定会进行自动装配

在网上还找到了一篇写 Spring Boot 的文章,里面有一张图形象地说了自动装配的过程,这里就直接贴上原文链接了:Spring Boot Rock’n’Roll


最后说一下一点感想。知道了自动装配功能是由应用启动时 AutoConfigurationImportSelector.class这个类来完成过后,心中肯定会有这么一个疑问:启动时,它是在什么时候被调用到的呢?于是乎我通过一顿操作一顿苦寻,花费将近一个小时,终于让我发现了它的来龙去脉!下面贴出我通过 debug 追寻这个调用链路所经过的大致的类和方法:

Code
1
2
3
4
5
6
7
8
9
10
11
12
SpringApplication.run()
--> SpringApplication.refreshContext()
--> AbstractApplicationContext.refresh()
--> AbstractApplicationContext.invokeBeanFactoryPostProcessors()
--> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
--> PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors()
--> ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()
--> ConfigurationClassPostProcessor.processConfigBeanDefinitions
--> ConfigurationClassParser.parse()
--> ConfigurationClassParser.process()
--> ConfigurationClassParser.processGroupImports()
--> AutoConfigurationImportSelector.getCandidateConfigurations()

不难看出,这个调用链路真的是相当的漫长… 说实话,通过 debug 方式追寻源码这种活很考验细心和耐心,在这个过程中,很多时候

会有一种 “山重水复疑无路” 的感觉,唯有细心和耐心以及不懈的坚持,不断地抽丝剥茧,最终才能 “柳暗花明又一村” 啊!

文章作者: JanGin
文章链接: http://jangin.github.io/2021/06/22/auto-configuration-in-spring-boot/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 JanGin's BLOG

评论