Spring把「手动」的复杂裹成了「自动」的温柔

案例

案例一:@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 定义则是在 ConfigurationClassPostProcessorprocessConfigBeanDefinitions() 中调用 ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForBeanMethod() 方法实现的。代码如下:

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());      // 省略代码 } 

然后在 ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForBeanMethod() 方法中从 @Bean 注解中获取 initMethoddestroyMethod 这些信息,然后注册 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 接口,如果是则会调用 DeferredImportSelectorHandlerhandle() 方法进行处理。代码如下:

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();         }     } } 

DeferredImportSelectorHandlerhandle 方法只是先把当前类加入到自己的 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);         }     } } 

最后在 ConfigurationClassParserparse() 方法最后调用它的 process() 方法。在 DeferredImportSelectorHandlerprocess() 方法中又调用了 DeferredImportSelectorHolderprocessGroupImport() 方法。代码如下:

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);                 }                 // 省略代码             });         }     } } 

然后调用到了 AutoConfigurationGroupprocess() 方法,在该方法中会调用 AutoConfigurationImportSelectorgetAutoConfigurationEntry() 方法,这个里这个类就是通过 @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);         }     } } 

AutoConfigurationImportSelectorgetAutoConfigurationEntry() 方法调用 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 文件中内容如下:
Spring把「手动」的复杂裹成了「自动」的温柔

这里以 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 {} 

发表评论

评论已关闭。

相关文章