案例
案例一:@EnableXXX注解使用
在一个 Spring MVC 项目,通过给配置类加上一个 @EnableWebMvc
注解,加上之后 Spring 就会注册 Spring MVC 的一系列组件,包括:HandlerMapping,HandlerAdapter,ViewResolver 等。
案例二:Spring Boot自动配置
在一个 Spring Boot应用中会有 @SpringBootApplication
注解修饰启动类,当引入 spring-boot-starter-web 依赖之后,Spring 也会自动地注册 Spring MVC 的一系列组件。
那 Spring 中是如何实现自动注册的能力的呢?先说结论:
Spring 中提供了 @Import
注解可以引入一个配置类或者是配置类的选择器。
当使用一般的 @EnableXXX
注解时实际上是通过 @Import
注解引入了预先定义好的配置类,它会配置一些指定的 Bean 来实现对应的功能。
当使用 Spring Boot 的自动配置功能时实际上是通过 @Import
注解引入了一个配置类的选择器,它会读取配置文件中配置的所有配置类,然后判断该配置类的条件是否满足,如果满足,则引入,否则,则不引入,从而实现自动配置某些功能。
源码分析
@EnableXXX注解实现原理
先看一下 @EnableWebMvc
注解,该注解上通过 @Import
注解引用了一个 DelegatingWebMvcConfiguration
配置类,它上面有 @Configuration
注解修饰。代码如下:
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}
在 DelegatingWebMvcConfiguration
这个配置类的父类 WebMvcConfigurationSupport
中定义了很多由 @Bean
注解修饰的方法,这些就是 Spring 会注册的 Spring MVC 组件类。代码如下:
java
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
// 定义HandlerMapping组件Bean
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// 省略代码
}
// 定义HandlerAdapter组件Bean
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
// 省略代码
}
// 定义ViewResolver组件Bean
@Bean
public ViewResolver mvcViewResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
// 省略代码
}
}
在3 个案例看透 Spring @Component 扫描:从普通应用到 Spring Boot文章中介绍了 Spring 中如何从 @Configuration
注解修饰的配置类的包扫描路径取扫描 Bean 的。主要是在ConfigurationClassParser
的 doProcessConfigurationClass()
方法中实现的,而对 @Import
注解引用的类也是在该方法中实现的。代码如下:
java
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// 省略代码
// 这里处理@Import注解
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// 省略代码
// 这里处理@Bean注解修饰的方法
// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
if (methodMetadata.isAnnotated("kotlin.jvm.JvmStatic") && !methodMetadata.isStatic()) {
continue;
}
// 添加到配置类中
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 省略代码
// No superclass -> processing is complete
return null;
}
在处理 @Import
注解引用的且是 @Configuration
注解修饰的类时,把它当作配置类,递归调用解析配置类的方法 processConfigurationClass()
,然后又进入到 doProcessConfigurationClass()
中,解析该类上 @Bean
注解修饰的方法添加到配置类中 。代码如下:
java
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> filter, boolean checkForCircularImports) {
// 省略代码
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
// 省略代码
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
// 省略代码
// 处理@Configuration注解修饰的类,就是去把它当作配置类继续解析它的配置
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), filter);
}
} finally {
this.importStack.pop();
}
}
}
具体把配置类中的 Bean 方法解析为 Bean 定义则是在 ConfigurationClassPostProcessor
的 processConfigBeanDefinitions()
中调用 ConfigurationClassBeanDefinitionReader
的 loadBeanDefinitionsForBeanMethod()
方法实现的。代码如下:
java
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 省略代码
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = CollectionUtils.newHashSet(configCandidates.size());
do {
// 省略代码
parser.parse(candidates);
parser.validate();
// 省略代码
// 这里调用loadBeanDefinitionsForBeanMethod()解析并注册Bean定义
this.reader.loadBeanDefinitions(configClasses);
// 省略代码
}
while (!candidates.isEmpty());
// 省略代码
}
然后在 ConfigurationClassBeanDefinitionReader
的 loadBeanDefinitionsForBeanMethod()
方法中从 @Bean
注解中获取 initMethod
,destroyMethod
这些信息,然后注册 Bean 定义。代码如下:
java
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// 省略代码
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// 省略代码
}
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
// 省略代码
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
String initMethodName = bean.getString("initMethod");
if (StringUtils.hasText(initMethodName)) {
beanDef.setInitMethodName(initMethodName);
}
String destroyMethodName = bean.getString("destroyMethod");
beanDef.setDestroyMethodName(destroyMethodName);
// 注册Bean定义
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
Spring Boot 自动配置原理
对于一个 Spring Boot 应用上的 @SpringBootApplication
注解是一个组合注解,它上面有 @EnableAutoConfiguration
注解修饰,而这个注解则是实现自动配置的关键。代码如下:
java
@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
注解和上面的 @EnableWebMvc
注解类似也是通过 @Import
注解引入了一个类 AutoConfigurationImportSelector
,但是这个类却没有 @Configuration
注解修饰,而是实现了 ImportSelector
接口。代码如下:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}
在上面的 ConfigurationClassParser
类的 processImports()
方法中有一个分支就是判断 @Import
注解引入的类是不是 DeferredImportSelector
接口,如果是则会调用 DeferredImportSelectorHandler
的 handle()
方法进行处理。代码如下:
java
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> filter, boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (selector instanceof DeferredImportSelector deferredImportSelector) {
// 调用deferredImportSelectorHandler的handle()方法
this.deferredImportSelectorHandler.handle(configClass, deferredImportSelector);
}
}
} finally {
this.importStack.pop();
}
}
}
而 DeferredImportSelectorHandler
的 handle
方法只是先把当前类加入到自己的 deferredImportSelectors
属性中。代码如下:
java
private class DeferredImportSelectorHandler {
@Nullable
private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();
void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
if (this.deferredImportSelectors == null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
handler.register(holder);
handler.processGroupImports();
}
else {
this.deferredImportSelectors.add(holder);
}
}
}
最后在 ConfigurationClassParser
的 parse()
方法最后调用它的 process()
方法。在 DeferredImportSelectorHandler
的 process()
方法中又调用了 DeferredImportSelectorHolder
的 processGroupImport()
方法。代码如下:
java
public void parse(Set<BeanDefinitionHolder> configCandidates) {
// 省略代码
this.deferredImportSelectorHandler.process();
}
void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
if (this.deferredImportSelectors == null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
handler.register(holder);
handler.processGroupImports();
}
else {
this.deferredImportSelectors.add(holder);
}
}
private class DeferredImportSelectorGroupingHandler {
@SuppressWarnings("NullAway")
void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> filter = grouping.getCandidateFilter();
// 调用getImports()方法获取到配置类,然后在递归调用processImports()方法
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass, filter),
Collections.singleton(asSourceClass(entry.getImportClassName(), filter)),
filter, false);
}
// 省略代码
});
}
}
}
然后调用到了 AutoConfigurationGroup
的 process()
方法,在该方法中会调用 AutoConfigurationImportSelector
的 getAutoConfigurationEntry()
方法,这个里这个类就是通过 @EnableAutoConfiguration
引入的类了。代码如下:
java
private static final class AutoConfigurationGroup
implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
// 省略代码
// 调用AutoConfigurationImportSelector的getAutoConfigurationEntry()方法
AutoConfigurationEntry autoConfigurationEntry = autoConfigurationImportSelector
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
}
在 AutoConfigurationImportSelector
的 getAutoConfigurationEntry()
方法调用 ImportCandidates
读取默认值为 org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中列举的配置类,然后过滤掉不满足条件的配置类,过滤的方式可以是判断 CLASSPATH 路径下某些类是否存在。代码如下:
java
AutoConfigurationImportSelector{
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 省略代码
// 获取所有配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 过滤掉不满足条件的配置类
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation,
getBeanClassLoader());
return configurations;
}
}
public final class ImportCandidates implements Iterable<String> {
private static final String LOCATION = "META-INF/spring/%s.imports";
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
ClassLoader classLoaderToUse = decideClassloader(classLoader);
// 这里就是配置类所在文件,默认是org.springframework.boot.autoconfigure.AutoConfiguration.imports
String location = String.format(LOCATION, annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
List<String> importCandidates = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
importCandidates.addAll(readCandidateConfigurations(url));
}
return new ImportCandidates(importCandidates);
}
}
org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中内容如下:
这里以 WebMvcAutoConfiguration
配置类为例,它要不被过滤掉的条件是 CLASSPATH 路径下存在 Servlet
, DispatcherServlet
, WebMvcConfigurer
这些类,即这些类存在则会解析 WebMvcAutoConfiguration
配置类配置的 Bean,从而实现 Spring MVC 组件的 Bean 的注册。代码如下:
java
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {}