从 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 在启动时做的工作,那我们就从应用程序的入口处开始我们的探索之旅,按图索骥,一步步地看看到底是怎么一回事。
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 这个注解。注解的源码如下;
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 注解。再点进来看看:
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
这个类中,有这么一段方法:
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
文件中的内容大概就是如下这样:
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 org.springframework.context.ApplicationContextInitializer =\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener org.springframework.context.ApplicationListener =\ org.springframework.boot.autoconfigure.BackgroundPreinitializer org.springframework.boot.autoconfigure.AutoConfigurationImportListener =\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener org.springframework.boot.autoconfigure.AutoConfigurationImportFilter =\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition 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()
方法内,该方法源码如下:
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
这个类的启动日志输出来举例好了,它在控制台的日志打印如下:
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
追寻这个调用链路所经过的大致的类和方法:
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 方式追寻源码这种活很考验细心和耐心,在这个过程中,很多时候
会有一种 “山重水复疑无路” 的感觉,唯有细心和耐心以及不懈的坚持,不断地抽丝剥茧,最终才能 “柳暗花明又一村” 啊!