案例
案例一:@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 注解修饰。代码如下:
@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 组件类。代码如下:
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 注解引用的类也是在该方法中实现的。代码如下:
@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 注解修饰的方法添加到配置类中 。代码如下:
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() 方法实现的。代码如下:
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 定义。代码如下:
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 注解修饰,而这个注解则是实现自动配置的关键。代码如下:
@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 接口。代码如下:
@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() 方法进行处理。代码如下:
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 属性中。代码如下:
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() 方法。代码如下:
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 引入的类了。代码如下:
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 路径下某些类是否存在。代码如下:
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 的注册。代码如下:
@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 {}