Spring boot 之 Auto-configuration 工作原理
spring boot 的 auto-configuration 功能会根据你的应用程序所依赖的 pom 来进行自动配置。 例如,我们在 pom 中添加
spring-boot-starter-web
的依赖,spring 就会帮我们自动完成 spring mvc 相关的配置而不需要我们手动来进行。我们只需要将@EnableAutoConfiguration
或者@SpringBootApplication
注解标注在@Configuration
配置类上面即可启用自动装配。
那么 auto-configuration 是如何工作的呢?带着问题,我们通过阅读相关的源代码来一探究竟。
既然开启 auto-configuration 需要通过 @EnableAutoConfiguration
或 @SpringBootApplication
来驱动,那么我们就从这两个注解着手。
注:本文基于 spring boot 版本 1.5.12.RELEASE。
源码解析
-
@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 {
-
// 省略
-
}
-
@SpringBootApplication
注解本身也标注了@EnableAutoConfiguration
注解,所以自动装配最终还是通过@EnableAutoConfiguration
来启用。 -
@SpringBootApplication
还标注了@ComponentScan
和@SpringBootConfiguration
,@SpringBootConfiguration
上又标注@Configuration
注解。
因为 @SpringBootApplication
将上述多个注解集成于一身,所以我们只要在类上标注 @SpringBootApplication
就等价于同时添加了 @EnableAutoConfiguration
、 @ComponentScan
和 @Configuration
。
注:类上标注的注解(direct annotation)和注解上的注解(meta-annotation)之所以都能生效这和 spring 本身对注解的处理有关。
@SpringBootApplication
比较简单,看来再来看看 @EnableAutoConfiguration
。
-
@EnableAutoConfiguration
源码如下:
-
@SuppressWarnings("deprecation")
-
@Target(ElementType.TYPE)
-
@Retention(RetentionPolicy.RUNTIME)
-
@Documented
-
@Inherited
-
@AutoConfigurationPackage
-
@Import(EnableAutoConfigurationImportSelector.class)
-
public @interface EnableAutoConfiguration {
-
-
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
-
-
/**
-
* Exclude specific auto-configuration classes such that they will never be applied.
-
* @return the classes to exclude
-
*/
-
Class<?>[] exclude() default {};
-
-
/**
-
* Exclude specific auto-configuration class names such that they will never be
-
* applied.
-
* @return the class names to exclude
-
* @since 1.3.0
-
*/
-
String[] excludeName() default {};
-
-
}
@EnableAutoConfiguration
上标注了 @Import
,引入了 EnableAutoConfigurationImportSelector
。
EnableAutoConfigurationImportSelector
源码如下:
-
public class EnableAutoConfigurationImportSelector
-
extends AutoConfigurationImportSelector {
-
-
@Override
-
protected boolean isEnabled(AnnotationMetadata metadata) {
-
if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
-
return getEnvironment().getProperty(
-
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
-
true);
-
}
-
return true;
-
}
-
-
}
EnableAutoConfigurationImportSelector
源码并没能给我们提供太多参考信息,只重写了 isEnabled
方法,我们来看看他的基类 AutoConfigurationImportSelector
,源码如下:
-
public class AutoConfigurationImportSelector
-
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
-
BeanFactoryAware, EnvironmentAware, Ordered {
-
// 省略
-
}
我们看到 AutoConfigurationImportSelector
实现了 DeferredImportSelector
接口(从 ImportSelector
派生)。 ImportSelector
源码如下:
-
public interface ImportSelector {
-
-
/**
-
* Select and return the names of which class(es) should be imported based on
-
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
-
*/
-
String[] selectImports(AnnotationMetadata importingClassMetadata);
-
-
}
selectImports
方法中定义了参数importingClassMetadata
,类型是AnnotationMetadata
,这是啥?我们把
importingClassMetadata
这个词组分解一下,importing + class metadata:importing:动名词,强调的是干了 import 这件事的发起者(这里指配置类)
class metadata:类的元信息,什么元信息,注解元信息,即
AnnotationMetadata
。那么
selectImports
方法的逻辑,我们可以这么描述:基于发起 import 操作的配置类(@Configuration class)的元信息进行运算并返回计算结果(class 名称数组)。
那么 spring 对返回结果中的 class 有没有什么特别要求呢?
实际上你可以返回任意的 class 名称,而不至于使程序出错,当然,你肯定不会返回没有任何意义的 class 名称。笔者总结的返回结果分为下面两类:
@Configuration
配置类
ImportSelector
(或DeferredImportSelector
)实现类
ImportSelector
与DeferredImportSelector
的区别?
DeferredImportSelector
是ImportSelector
的变体,二者的触发先后顺序不同,DeferredImportSelector
在所有的@Configuration
bean 都被处理了之后再进行处理,这与它的名称 deferred (推迟)非常贴合。题外话:通过以上内容,我们可以看出 spring 在命名上非常讲究,代码阅读起来比较符合人的逻辑思维。
selectImports
方法的实现由ConfigurationClassParser
类的 parse 方法触发,ConfigurationClassParser
的 parse 方法会被ConfigurationClassPostProcessor
类的 postProcessBeanDefinitionRegistry 方法调用,而触发这一切的最上层是 spring application context 的refresh()
方法。
信息量有点大,我们通过下图来做简单说明:
-
Application 启动
-
⥥ // refresh spring application context
-
AbstractApplicationContext.refresh
-
⥥ // 执行 BeanFactoryPostProcessor 钩子回调
-
AbstractApplicationContext.invokeBeanFactoryPostProcessors
-
⥥ // 委托给 PostProcessorRegistrationDelegate 来执行钩子回调
-
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
-
⥥ // ConfigurationClassPostProcessor 是 BeanFactoryPostProcessor 的实现类,被触发
-
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry
-
⥥ // ConfigurationClassPostProcessor 委托 ConfigurationClassParser 来对配置类进行解析
-
ConfigurationClassParser.parse
-
⥥ // ConfigurationClassParser 处理 DeferredImportSelector
-
ConfigurationClassParser.processDeferredImportSelectors
-
⥥ // 执行 selectImports
-
DeferredImportSelector.selectImports
关于 ConfigurationClassParser
类对 @Configuration
配置类的处理,本文并不打算作过多讲解,笔者会放在后续文章中来和大家进行探讨。
好了,有了对 ImportSelector
的相关了解后,我们来看看 AutoConfigurationImportSelector
的 selectImports
方法的实现,源码如下:
-
@Override
-
public String[] selectImports(AnnotationMetadata annotationMetadata) {
-
// 检查是否启用 auto-configuration,默认为启用
-
// 由子类 EnableAutoConfigurationImportSelector 重写
-
if (!isEnabled(annotationMetadata)) {
-
// 如果不开启 auto-configuration,返回 emtpy array
-
return NO_IMPORTS;
-
}
-
try {
-
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
-
.loadMetadata(this.beanClassLoader);
-
AnnotationAttributes attributes = getAttributes(annotationMetadata);
-
// 通过 SpringFactoriesLoader 找到所有的侯选配置类(auto-configuration 类)
-
List<String> configurations = getCandidateConfigurations(annotationMetadata,
-
attributes);
-
// 去除重复的配置类
-
configurations = removeDuplicates(configurations);
-
// 根据 auto-configuration 的先后配置顺序的要求进行排序
-
// 可通过 @AutoConfigureBefore & @AutoConfigureAfter 来指定
-
configurations = sort(configurations, autoConfigurationMetadata);
-
// 需要被剔除的 auto-configuration 类
-
// 可在 properties 配置文件中通过 spring.autoconfigure.exclude 来指定
-
// 也可通过 @EnableAutoConfiguration 注解的 exclude() & excludeName() 属性来指定
-
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
-
checkExcludedClasses(configurations, exclusions);
-
// 从侯选配置类中剔除需要被排除在外的(auto-configuration 类)
-
configurations.removeAll(exclusions);
-
// 通过 AutoConfigurationImportFilter 来过滤侯选配置类,再次进行剔除
-
// 通过 SpringFactoriesLoader 获取所的 AutoConfigurationImportFilter 实现
-
configurations = filter(configurations, autoConfigurationMetadata);
-
// 发布 AutoConfigurationImportEvent 事件,通知 AutoConfigurationImportListener,
-
// 触发 onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) 方法
-
// 通过 SpringFactoriesLoader 获取所的 AutoConfigurationImportListener 实现
-
fireAutoConfigurationImportEvents(configurations, exclusions);
-
// 返回需要处理的 auto-configuration 类(名称)
-
return configurations.toArray(new String[configurations.size()]);
-
}
-
catch (IOException ex) {
-
throw new IllegalStateException(ex);
-
}
-
}
-
-
protected boolean isEnabled(AnnotationMetadata metadata) {
-
return true;
-
}
相信大家看了笔者所添加的相关注释后对 AutoConfigurationImportSelector
的逻辑已经有了一个大致的了解。
AutoConfigurationImportSelector
的selectImports
方法的主要逻辑就是通过SpringFactoriesLoader
找到所有的 auto-configuration 侯选类,然后在此基础上进行去重、排序和剔除操作,最终得到需要进行 auto-configuration 的所有类的名称。拿到了所的 auto-configuration 类,spring boot 就可以加载这些 class,由于这些类本身标注了
@Configuration
,然后就可以被ConfigurationClassParser
类来进行解析了,最终@Bean
工厂方法就会被调用,完成 bean 的加载。
在笔者的注释中,不止一次提到了 SpringFactoriesLoader
,这个类究竟有何神奇之处?
同样的,为了搞清楚这件事,我们还得看 spring 源码,部分源码如下:
-
public abstract class SpringFactoriesLoader {
-
-
/**
-
* The location to look for factories.
-
* <p>Can be present in multiple JAR files.
-
*/
-
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
-
-
// 通过 ClassLoader 加载所有的所有 META-INF/spring.factories 文件,解析成 Properties 对象,
-
// 根据 key (指定的 class 名称) 来获取所有的配置项 (类名称)
-
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
-
String factoryClassName = factoryClass.getName();
-
try {
-
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
-
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
-
List<String> result = new ArrayList<String>();
-
while (urls.hasMoreElements()) {
-
URL url = urls.nextElement();
-
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
-
String factoryClassNames = properties.getProperty(factoryClassName);
-
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
-
}
-
return result;
-
}
-
catch (IOException ex) {
-
// 省略
-
}
-
}
-
}
原来 spring 使用了一种类似
ServiceLoader
(jdk 1.6 中增加)的处理方式。通过这种约定,我们只需要将要进行处理的目标类名称配置在相对固定的配置文件中,spring 按照统一的方式来读取即可。对我们的应用程序而言并不需要知道具体的实现类是哪些,也不需要知道这些类存在于哪些 jar 中,都可以被轻松获取。相对固定指的是:
相对于 classpath, META-INF/spring.factories 文件的位置固定和名称固定。
同一类别的配置,不同配置文件中 key 名称固定不变。
注:如果你对
ServiceLoader
感到陌生,请查看 jdk api 了解相关内容。
既然了解了 spring 的 spring.factories
的套路,那么我们就来找找看都有哪些 auto-configuration 类,
我们的 key 是 @EnableAutoConfiguration
注解的 class 名称,即 org.springframework.boot.autoconfigure.EnableAutoConfiguration
,展开 spring-boot-autoconfigure-1.5.12.RELEASE.jar 文件,找到 spring.factories
文件,部分配置如下(“……” 表示笔者省略 ):
-
# 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.context.ConfigurationPropertiesAutoConfiguration,\
-
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
-
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
-
# ……
-
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
-
# ……
-
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
-
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
-
# ……
-
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
-
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
-
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
-
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
-
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
-
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
-
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
上述这些 auto-configuration 侯选类名称都会被
AutoConfigurationImportSelector
通过SpringFactoriesLoader
所获取,然后AutoConfigurationImportSelector
就可以进行后续的去重、排序和剔除过滤操作了。
好了,本文至此,你是否已经对 spring boot 的 auto-configuration 工作机制有所了解了呢?
小结
本文,我们主要介绍了 spring boot auto-configuration 的工作机制,提到了几个重要的概念:
@EnableAutoConfiguration
ImportSelector
SpringFactoriesLoader
通过
@EnableAutoConfiguration
注解驱动引入AutoConfigurationImportSelector
,该类通过SpringFactoriesLoader
加载所有的META-INF/spring.factories
文件,获取到所有的 auto-configuration 候选类,然后进行去重、排序和剔除过滤等操作得到待处理的 auto-configuration 类。