找回密码
 立即注册
首页 业界区 业界 Spring把「手动」的复杂裹成了「自动」的温柔 ...

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

谭皎洁 3 天前
案例

案例一:@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 注解修饰。代码如下:
  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Documented
  4. @Import(DelegatingWebMvcConfiguration.class)
  5. public @interface EnableWebMvc {
  6. }
  7. @Configuration(proxyBeanMethods = false)  
  8. public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
  9. }
复制代码
在 DelegatingWebMvcConfiguration 这个配置类的父类 WebMvcConfigurationSupport 中定义了很多由 @Bean 注解修饰的方法,这些就是 Spring 会注册的 Spring MVC 组件类。代码如下:
  1. public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
  2.     // 定义HandlerMapping组件Bean
  3.     @Bean
  4.     @SuppressWarnings("deprecation")
  5.     public RequestMappingHandlerMapping requestMappingHandlerMapping(
  6.             @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
  7.             @Qualifier("mvcConversionService") FormattingConversionService conversionService,
  8.             @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
  9.         // 省略代码
  10.     }
  11.     // 定义HandlerAdapter组件Bean
  12.     @Bean
  13.     public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
  14.             @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
  15.             @Qualifier("mvcConversionService") FormattingConversionService conversionService,
  16.             @Qualifier("mvcValidator") Validator validator) {
  17.         // 省略代码
  18.     }
  19.     // 定义ViewResolver组件Bean
  20.     @Bean
  21.     public ViewResolver mvcViewResolver(
  22.             @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
  23.         
  24.         // 省略代码
  25.     }
  26. }
复制代码
在3 个案例看透 Spring @Component 扫描:从普通应用到 Spring Boot文章中介绍了 Spring 中如何从 @Configuration 注解修饰的配置类的包扫描路径取扫描 Bean 的。主要是在ConfigurationClassParser 的 doProcessConfigurationClass() 方法中实现的,而对 @Import 注解引用的类也是在该方法中实现的。代码如下:
  1. @Nullable
  2. protected final SourceClass doProcessConfigurationClass(
  3.     ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
  4.     throws IOException {
  5.     // 省略代码
  6.         // 这里处理@Import注解
  7.     // Process any @Import annotations
  8.     processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
  9.     // 省略代码
  10.         // 这里处理@Bean注解修饰的方法
  11.     // Process individual @Bean methods
  12.     Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
  13.     for (MethodMetadata methodMetadata : beanMethods) {
  14.         if (methodMetadata.isAnnotated("kotlin.jvm.JvmStatic") && !methodMetadata.isStatic()) {
  15.             continue;
  16.         }
  17.         // 添加到配置类中
  18.         configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
  19.     }
  20.     // 省略代码
  21.     // No superclass -> processing is complete
  22.     return null;
  23. }
复制代码
在处理 @Import 注解引用的且是 @Configuration 注解修饰的类时,把它当作配置类,递归调用解析配置类的方法 processConfigurationClass(),然后又进入到 doProcessConfigurationClass() 中,解析该类上 @Bean 注解修饰的方法添加到配置类中 。代码如下:
  1. private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
  2.     Collection<SourceClass> importCandidates, Predicate<String> filter, boolean checkForCircularImports) {
  3.     // 省略代码
  4.     if (checkForCircularImports && isChainedImportOnStack(configClass)) {
  5.         // 省略代码
  6.     }
  7.     else {
  8.         this.importStack.push(configClass);
  9.         try {
  10.             for (SourceClass candidate : importCandidates) {
  11.                                 // 省略代码
  12.                 // 处理@Configuration注解修饰的类,就是去把它当作配置类继续解析它的配置
  13.                 this.importStack.registerImport(
  14.                             currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
  15.                 processConfigurationClass(candidate.asConfigClass(configClass), filter);
  16.             }
  17.         } finally {
  18.             this.importStack.pop();
  19.         }
  20.     }
  21. }
复制代码
具体把配置类中的 Bean 方法解析为 Bean 定义则是在 ConfigurationClassPostProcessor 的 processConfigBeanDefinitions() 中调用 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForBeanMethod() 方法实现的。代码如下:
  1. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
  2.     // 省略代码
  3.     // Parse each @Configuration class
  4.     ConfigurationClassParser parser = new ConfigurationClassParser(
  5.             this.metadataReaderFactory, this.problemReporter, this.environment,
  6.             this.resourceLoader, this.componentScanBeanNameGenerator, registry);
  7.     Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
  8.     Set<ConfigurationClass> alreadyParsed = CollectionUtils.newHashSet(configCandidates.size());
  9.     do {
  10.             // 省略代码
  11.         parser.parse(candidates);
  12.         parser.validate();
  13.                 // 省略代码
  14.         // 这里调用loadBeanDefinitionsForBeanMethod()解析并注册Bean定义
  15.         this.reader.loadBeanDefinitions(configClasses);
  16.         
  17.         // 省略代码
  18.     }
  19.     while (!candidates.isEmpty());
  20.     // 省略代码
  21. }
复制代码
然后在 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForBeanMethod() 方法中从 @Bean 注解中获取 initMethod,destroyMethod 这些信息,然后注册 Bean 定义。代码如下:
  1. private void loadBeanDefinitionsForConfigurationClass(
  2.     ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
  3.     // 省略代码
  4.    
  5.     for (BeanMethod beanMethod : configClass.getBeanMethods()) {
  6.         loadBeanDefinitionsForBeanMethod(beanMethod);
  7.     }
  8.    
  9.     // 省略代码
  10. }
  11. private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
  12.     ConfigurationClass configClass = beanMethod.getConfigurationClass();
  13.     MethodMetadata metadata = beanMethod.getMetadata();
  14.     String methodName = metadata.getMethodName();
  15.     // 省略代码
  16.     ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
  17.     String initMethodName = bean.getString("initMethod");
  18.     if (StringUtils.hasText(initMethodName)) {
  19.         beanDef.setInitMethodName(initMethodName);
  20.     }
  21.     String destroyMethodName = bean.getString("destroyMethod");
  22.     beanDef.setDestroyMethodName(destroyMethodName);
  23.         // 注册Bean定义
  24.     this.registry.registerBeanDefinition(beanName, beanDefToRegister);
  25. }
复制代码
Spring Boot 自动配置原理

对于一个 Spring Boot 应用上的 @SpringBootApplication 注解是一个组合注解,它上面有 @EnableAutoConfiguration 注解修饰,而这个注解则是实现自动配置的关键。代码如下:
  1. @Target(ElementType.TYPE)  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. @Documented  
  4. @Inherited  
  5. @SpringBootConfiguration  
  6. @EnableAutoConfiguration  
  7. @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),  
  8.        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })  
  9. public @interface SpringBootApplication {}
复制代码
@EnableAutoConfiguration 注解和上面的 @EnableWebMvc 注解类似也是通过 @Import 注解引入了一个类 AutoConfigurationImportSelector,但是这个类却没有 @Configuration 注解修饰,而是实现了 ImportSelector 接口。代码如下:
  1. @Target(ElementType.TYPE)  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. @Documented  
  4. @Inherited  
  5. @AutoConfigurationPackage  
  6. @Import(AutoConfigurationImportSelector.class)  
  7. public @interface EnableAutoConfiguration {}
  8. public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,  
  9.        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}
复制代码
在上面的 ConfigurationClassParser 类的 processImports() 方法中有一个分支就是判断 @Import 注解引入的类是不是 DeferredImportSelector 接口,如果是则会调用 DeferredImportSelectorHandler 的 handle() 方法进行处理。代码如下:
  1. private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
  2.     Collection<SourceClass> importCandidates, Predicate<String> filter, boolean checkForCircularImports) {
  3.     if (importCandidates.isEmpty()) {
  4.         return;
  5.     }
  6.     if (checkForCircularImports && isChainedImportOnStack(configClass)) {
  7.         this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
  8.     }
  9.     else {
  10.         this.importStack.push(configClass);
  11.         try {
  12.             for (SourceClass candidate : importCandidates) {
  13.                 if (selector instanceof DeferredImportSelector deferredImportSelector) {  
  14.                                             // 调用deferredImportSelectorHandler的handle()方法
  15.                                             this.deferredImportSelectorHandler.handle(configClass, deferredImportSelector);  
  16.                                         }
  17.             }
  18.         } finally {
  19.             this.importStack.pop();
  20.         }
  21.     }
  22. }
复制代码
而 DeferredImportSelectorHandler 的 handle 方法只是先把当前类加入到自己的 deferredImportSelectors 属性中。代码如下:
  1. private class DeferredImportSelectorHandler {
  2.     @Nullable
  3.     private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();
  4.     void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
  5.         DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
  6.         if (this.deferredImportSelectors == null) {
  7.             DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
  8.             handler.register(holder);
  9.             handler.processGroupImports();
  10.         }
  11.         else {
  12.             this.deferredImportSelectors.add(holder);
  13.         }
  14.     }
  15. }
复制代码
最后在 ConfigurationClassParser 的 parse() 方法最后调用它的 process() 方法。在 DeferredImportSelectorHandler 的 process() 方法中又调用了 DeferredImportSelectorHolder 的 processGroupImport() 方法。代码如下:
  1. public void parse(Set<BeanDefinitionHolder> configCandidates) {
  2.     // 省略代码
  3.     this.deferredImportSelectorHandler.process();
  4. }
  5. void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
  6.     DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
  7.     if (this.deferredImportSelectors == null) {
  8.         DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
  9.         handler.register(holder);
  10.         handler.processGroupImports();
  11.     }
  12.     else {
  13.         this.deferredImportSelectors.add(holder);
  14.     }
  15. }
  16. private class DeferredImportSelectorGroupingHandler {
  17.     @SuppressWarnings("NullAway")
  18.     void processGroupImports() {
  19.         for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
  20.             Predicate<String> filter = grouping.getCandidateFilter();
  21.             // 调用getImports()方法获取到配置类,然后在递归调用processImports()方法
  22.             grouping.getImports().forEach(entry -> {
  23.                 ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
  24.                 try {
  25.                     processImports(configurationClass, asSourceClass(configurationClass, filter),
  26.                             Collections.singleton(asSourceClass(entry.getImportClassName(), filter)),
  27.                             filter, false);
  28.                 }
  29.                 // 省略代码
  30.             });
  31.         }
  32.     }
  33. }
复制代码
然后调用到了 AutoConfigurationGroup 的 process() 方法,在该方法中会调用 AutoConfigurationImportSelector 的 getAutoConfigurationEntry() 方法,这个里这个类就是通过 @EnableAutoConfiguration 引入的类了。代码如下:
  1. private static final class AutoConfigurationGroup
  2.     implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
  3.     @Override
  4.     public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
  5.         // 省略代码
  6.         
  7.         // 调用AutoConfigurationImportSelector的getAutoConfigurationEntry()方法
  8.         AutoConfigurationEntry autoConfigurationEntry = autoConfigurationImportSelector
  9.             .getAutoConfigurationEntry(annotationMetadata);
  10.             
  11.         this.autoConfigurationEntries.add(autoConfigurationEntry);
  12.         for (String importClassName : autoConfigurationEntry.getConfigurations()) {
  13.             this.entries.putIfAbsent(importClassName, annotationMetadata);
  14.         }
  15.     }
  16. }
复制代码
在 AutoConfigurationImportSelector 的 getAutoConfigurationEntry() 方法调用 ImportCandidates 读取默认值为 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中列举的配置类,然后过滤掉不满足条件的配置类,过滤的方式可以是判断 CLASSPATH 路径下某些类是否存在。代码如下:
  1. AutoConfigurationImportSelector{
  2.         protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  3.         // 省略代码
  4.         
  5.         // 获取所有配置类
  6.         List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  7.         
  8.         // 过滤掉不满足条件的配置类
  9.         configurations = getConfigurationClassFilter().filter(configurations);
  10.         fireAutoConfigurationImportEvents(configurations, exclusions);
  11.         return new AutoConfigurationEntry(configurations, exclusions);
  12.     }
  13.     protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  14.         ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation,
  15.                 getBeanClassLoader());
  16.         return configurations;
  17.     }
  18. }
  19. public final class ImportCandidates implements Iterable<String> {
  20.     private static final String LOCATION = "META-INF/spring/%s.imports";
  21.    
  22.     public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
  23.         ClassLoader classLoaderToUse = decideClassloader(classLoader);
  24.         // 这里就是配置类所在文件,默认是org.springframework.boot.autoconfigure.AutoConfiguration.imports
  25.         String location = String.format(LOCATION, annotation.getName());
  26.         Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
  27.         List<String> importCandidates = new ArrayList<>();
  28.         while (urls.hasMoreElements()) {
  29.             URL url = urls.nextElement();
  30.             importCandidates.addAll(readCandidateConfigurations(url));
  31.         }
  32.         return new ImportCandidates(importCandidates);
  33.     }
  34. }
复制代码
org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中内容如下:
1.png

这里以 WebMvcAutoConfiguration 配置类为例,它要不被过滤掉的条件是 CLASSPATH 路径下存在 Servlet, DispatcherServlet, WebMvcConfigurer 这些类,即这些类存在则会解析 WebMvcAutoConfiguration 配置类配置的 Bean,从而实现 Spring MVC 组件的 Bean 的注册。代码如下:
  1. @AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,  
  2.        ValidationAutoConfiguration.class })  
  3. @ConditionalOnWebApplication(type = Type.SERVLET)  
  4. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })  
  5. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)  
  6. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)  
  7. @ImportRuntimeHints(WebResourcesRuntimeHints.class)  
  8. public class WebMvcAutoConfiguration {}
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册