• ImportSelector 与 DeferredImportSelector 的区别(spring4)


    欢迎访问我的 GitHub

    这里分类和汇总了欣宸的全部原创(含配套源码):
    https://github.com/zq2599/blog_demos

    • 在使用 @Import 注解来注册 bean 的时候,Import 注解的值可以是 ImportSelector 或者 DeferredImportSelector 的实现类,spring 容器会实例化这个实现类,并执行其 selectImports 方法,那么问题来了: ImportSelector 和 DeferredImportSelector 的区别在哪里,我们自定义 Imort 逻辑的时候该选择哪个呢? 本文通过分析相关的 spring 源码来查找答案;

    全文概览

    • 本文由以下几部分组成:
    1. 看官方文档;
    2. 分析 spring 源码中对这两个接口的处理;
    3. 实战验证;

    看官方文档

    • 先看官方文档看起,我选择了 4.3.9 版本在线文档(这是个 Release 版),地址:https://docs.spring.io/spring/docs/4.3.19.RELEASE/javadoc-api/
    • 原文:A variation of ImportSelector that runs after all @Configuration beans have been processed. This type of selector can be particularly useful when the selected imports are @Conditional.Implementations can also extend the Ordered interface or use the Order annotation to indicate a precedence against other DeferredImportSelectors.
    • 我的理解:
    1. DeferredImportSelector 是 ImportSelector 的一个扩展;
    2. ImportSelector 实例的 selectImports 方法的执行时机,是在 @Configguration 注解中的其他逻辑被处理 之前 ,所谓的其他逻辑,包括对 @ImportResource、@Bean 这些注解的处理(注意,这里只是对 @Bean 修饰的方法的处理,并不是立即调用 @Bean 修饰的方法,这个区别很重要!);
    3. DeferredImportSelector 实例的 selectImports 方法的执行时机,是在 @Configguration 注解中的其他逻辑被处理 完毕之后 ,所谓的其他逻辑,包括对 @ImportResource、@Bean 这些注解的处理;
    4. DeferredImportSelector 的实现类可以用 Order 注解,或者实现 Ordered 接口来对 selectImports 的执行顺序排序;

    分析 spring 源码中对这两个接口的处理

    • 接下来看看源码:
    • 在 spring-framework-4.1.8.RELEASE 工程中找到类 ConfigurationClassParser.java,这里面有处理配置类的主要逻辑;
    • 找到方法 parse(Set<BeanDefinitionHolder> configCandidates):
    1. public void parse(Set<BeanDefinitionHolder> configCandidates) {
    2. this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
    3. //检查每个bean的定义
    4. for (BeanDefinitionHolder holder : configCandidates) {
    5. BeanDefinition bd = holder.getBeanDefinition();
    6. try {
    7. if (bd instanceof AnnotatedBeanDefinition) {
    8. //对于每个有注解的类,都执行方法parse(AnnotationMetadata metadata, String beanName)
    9. parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
    10. }
    11. else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
    12. parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
    13. }
    14. else {
    15. parse(bd.getBeanClassName(), holder.getBeanName());
    16. }
    17. }
    18. catch (BeanDefinitionStoreException ex) {
    19. throw ex;
    20. }
    21. catch (Exception ex) {
    22. throw new BeanDefinitionStoreException(
    23. "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
    24. }
    25. }
    26. //最后再处理DeferredImportSelector的实现类
    27. processDeferredImportSelectors();
    28. }

    复制代码

    • 由以上代码可以大致看出 DeferredImportSelector 的实现类被最后放在 processDeferredImportSelectors 方法中处理,那么前面的 parse(AnnotationMetadata metadata, String beanName)做了些什么呢?继续看;
    • 展开方法 parse(AnnotationMetadata metadata, String beanName)里面,是执行 processConfigurationClass 方法;
    • 再展开 processConfigurationClass 方法,看到核心逻辑是调用 doProcessConfigurationClass 方法,展开看看:
    1. protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
    2. //为了聚焦Import相关处理,此处略去部分不相关代码,不在这里展示了
    3. ...
    4. ...
    5. // 处理@Import注解
    6. processImports(configClass, sourceClass, getImports(sourceClass), true);
    7. // 处理@ImportResource注解
    8. if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
    9. AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    10. String[] resources = importResource.getStringArray("value");
    11. Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
    12. for (String resource : resources) {
    13. String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
    14. configClass.addImportedResource(resolvedResource, readerClass);
    15. }
    16. }
    17. // 处理@Bean注解,注意是处理注解,不是执行@Bean修饰的方法
    18. Set<MethodMetadata> beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());
    19. for (MethodMetadata methodMetadata : beanMethods) {
    20. configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    21. }
    22. // 处理Configuration类的父类,外面在调用doProcessConfigurationClass方法的时有迭代处理,确保所有父类的注解都会被处理
    23. if (sourceClass.getMetadata().hasSuperClass()) {
    24. String superclass = sourceClass.getMetadata().getSuperClassName();
    25. if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
    26. this.knownSuperclasses.put(superclass, configClass);
    27. // Superclass found, return its annotation metadata and recurse
    28. return sourceClass.getSuperClass();
    29. }
    30. }
    31. // 再也没有父类了,返回null表示当前Configuration处理完毕
    32. return null;
    33. }

    复制代码

    • 根据上述代码分析,可以梳理出下图中的逻辑:

    • 现在需要再看看 processImports 和 processDeferredImportSelectors 这两个方法的具体代码;
    • 先看 processImports 方法:
    1. private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
    2. Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
    3. if (importCandidates.isEmpty()) {
    4. return;
    5. }
    6. if (checkForCircularImports && this.importStack.contains(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. //如果是ImportSelector接口的实现类,就在此处理
    14. if (candidate.isAssignable(ImportSelector.class)) {
    15. // Candidate class is an ImportSelector -> delegate to it to determine imports
    16. Class<?> candidateClass = candidate.loadClass();
    17. //实例化这些ImportSelector的实现类
    18. ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
    19. //如果这实现类还实现了BeanFactoryAware、EnvironmentAware这些接口,就要先执行这些接口中声明的方法
    20. invokeAwareMethods(selector);
    21. //如果这个实现类也实现了DeferredImportSelector接口,就被加入到集合deferredImportSelectors中
    22. if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
    23. this.deferredImportSelectors.add(
    24. new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
    25. }
    26. else {
    27. //注意,这一行是关键代码!!!执行实现类的selectImports方法
    28. String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
    29. Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
    30. processImports(configClass, currentSourceClass, importSourceClasses, false);
    31. }
    32. }
    33. //此处略去的和ImportSelector不相关的逻辑代码
    34. ...
    35. ...
    36. ...
    37. }
    38. }
    39. catch (BeanDefinitionStoreException ex) {
    40. throw ex;
    41. }
    42. catch (Exception ex) {
    43. throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
    44. configClass.getMetadata().getClassName() + "]", ex);
    45. }
    46. finally {
    47. this.importStack.pop();
    48. }
    49. }
    50. }

    复制代码

    • 以上代码有两个关键点:
    • 第一、当前被处理的类,如果实现了 DeferredImportSelector 接口,就被加入到集合 deferredImportSelectors 中;
    • 第二、当前被处理的类,如果没有实现 DeferredImportSelector 接口,但是实现了 ImportSelector 接口,就被执行 selectImports 方法;
    • 接下来看看 processDeferredImportSelectors 方法的源码,提前推测应该是处理集合 deferredImportSelectors 中的所有类,这些类都实现了 DeferredImportSelector 接口:
    1. private void processDeferredImportSelectors() {
    2. List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    3. this.deferredImportSelectors = null;
    4. //按照Order注解或者Ordered接口进行排序
    5. Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
    6. for (DeferredImportSelectorHolder deferredImport : deferredImports) {
    7. ConfigurationClass configClass = deferredImport.getConfigurationClass();
    8. try {
    9. //此处是关键代码,执行DeferredImportSelector实现类的selectImports方法
    10. String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
    11. processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
    12. }
    13. catch (BeanDefinitionStoreException ex) {
    14. throw ex;
    15. }
    16. catch (Exception ex) {
    17. throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
    18. configClass.getMetadata().getClassName() + "]", ex);
    19. }
    20. }
    21. }

    复制代码

    • 至此,源码分析完毕了,从代码可以很清晰的看出 ImportSelector 与 DeferredImportSelector 的区别,就是 selectImports 方法执行时机有差别,这个差别期间,spring 容器对此 Configguration 类做了些其他的逻辑:包括对 @ImportResource、@Bean 这些注解的处理(注意,这里只是对 @Bean 修饰的方法的处理,并不是立即调用 @Bean 修饰的方法,这个区别很重要!);

    实战验证

    • 接下来到了实战验证的环节了,本次实战的内容是创建一个 springboot 工程,在里面自定义三个 ImportSelector 接口的实现类,如果您不想敲代码,也可以去 github 下载源码,地址和链接信息如下表所示:

    • 这个 git 项目中有多个文件夹,本章源码在文件夹 customizeimportselector 下,如下图红框所示:

    • 开始编码吧:
    • 我们创建三个 ImportSelector 的实现类来检查其先后顺序,三个 Selector 类简介如下表,有两个是 DeferredImportSelector 的实现类,一个是 ImportSelector 的实现类,每个 Selector 负责向 spring 容器注册一种实例:

    • 基于 maven 创建 springboot 框架的 web 工程,pom.xml 内容如下:
    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    4. <modelVersion>4.0.0</modelVersion>
    5. <groupId>com.bolingcavalry</groupId>
    6. <artifactId>customizeimportselector</artifactId>
    7. <version>0.0.1-SNAPSHOT</version>
    8. <packaging>jar</packaging>
    9. <name>customizeimportselector</name>
    10. <description>Demo project for Spring Boot</description>
    11. <parent>
    12. <groupId>org.springframework.boot</groupId>
    13. <artifactId>spring-boot-starter-parent</artifactId>
    14. <version>1.5.9.RELEASE</version>
    15. <relativePath/> <!-- lookup parent from repository -->
    16. </parent>
    17. <properties>
    18. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    19. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    20. <java.version>1.8</java.version>
    21. </properties>
    22. <dependencies>
    23. <dependency>
    24. <groupId>org.springframework.boot</groupId>
    25. <artifactId>spring-boot-starter-web</artifactId>
    26. </dependency>
    27. <dependency>
    28. <groupId>org.springframework.boot</groupId>
    29. <artifactId>spring-boot-starter-test</artifactId>
    30. <scope>test</scope>
    31. </dependency>
    32. </dependencies>
    33. <build>
    34. <plugins>
    35. <plugin>
    36. <groupId>org.springframework.boot</groupId>
    37. <artifactId>spring-boot-maven-plugin</artifactId>
    38. </plugin>
    39. </plugins>
    40. </build>
    41. </project>

    复制代码

    • 创建三个接口 CustomizeService1、CustomizeService2、CustomizeService3,第一个源码如下,另外两个除了类名,其余部分一样:
    1. package com.bolingcavalry.customizeimportselector.service;
    2. public interface CustomizeService1 {
    3. void execute();
    4. }

    复制代码

    • 创建三个类,分别实现上面的三个接口,也是除了类名其余部分一样:
    1. package com.bolingcavalry.customizeimportselector.service.impl;
    2. import com.bolingcavalry.customizeimportselector.service.CustomizeService1;
    3. public class CustomizeServiceImpl1 implements CustomizeService1 {
    4. public CustomizeServiceImpl1() {
    5. System.out.println("construct : " + this.getClass().getSimpleName());
    6. }
    7. @Override
    8. public void execute() {
    9. System.out.println("execute : " + this.getClass().getSimpleName());
    10. }
    11. }

    复制代码

    • 创建 CustomizeImportSelector1:
    1. package com.bolingcavalry.customizeimportselector.selector;
    2. import org.springframework.context.annotation.DeferredImportSelector;
    3. import org.springframework.context.annotation.ImportSelector;
    4. import org.springframework.core.annotation.Order;
    5. import org.springframework.core.type.AnnotationMetadata;
    6. @Order(102)
    7. public class CustomizeImportSelector1 implements DeferredImportSelector {
    8. @Override
    9. public String[] selectImports(AnnotationMetadata annotationMetadata) {
    10. System.out.println("selectImports : " + this.getClass().getSimpleName());
    11. return new String[]{"com.bolingcavalry.customizeimportselector.service.impl.CustomizeServiceImpl1"};
    12. }
    13. }

    复制代码

    • 创建 CustomizeImportSelector2:
    1. package com.bolingcavalry.customizeimportselector.selector;
    2. import org.springframework.context.annotation.DeferredImportSelector;
    3. import org.springframework.core.annotation.Order;
    4. import org.springframework.core.type.AnnotationMetadata;
    5. @Order(101)
    6. public class CustomizeImportSelector2 implements DeferredImportSelector {
    7. @Override
    8. public String[] selectImports(AnnotationMetadata annotationMetadata) {
    9. System.out.println("selectImports : " + this.getClass().getSimpleName());
    10. return new String[]{"com.bolingcavalry.customizeimportselector.service.impl.CustomizeServiceImpl2"};
    11. }
    12. }

    复制代码

    • 创建 CustomizeImportSelector3,实现的是 ImportSelector 接口:
    1. package com.bolingcavalry.customizeimportselector.selector;
    2. import org.springframework.context.annotation.ImportSelector;
    3. import org.springframework.core.annotation.Order;
    4. import org.springframework.core.type.AnnotationMetadata;
    5. public class CustomizeImportSelector3 implements ImportSelector {
    6. @Override
    7. public String[] selectImports(AnnotationMetadata annotationMetadata) {
    8. System.out.println("selectImports : " + this.getClass().getSimpleName());
    9. return new String[]{"com.bolingcavalry.customizeimportselector.service.impl.CustomizeServiceImpl3"};
    10. }
    11. }

    复制代码

    • 创建配置类,将 CustomizeImportSelector1、CustomizeImportSelector2、CustomizeImportSelector3 全部用 Import 注解引入:
    1. package com.bolingcavalry.customizeimportselector;
    2. import com.bolingcavalry.customizeimportselector.selector.CustomizeImportSelector1;
    3. import com.bolingcavalry.customizeimportselector.selector.CustomizeImportSelector2;
    4. import com.bolingcavalry.customizeimportselector.selector.CustomizeImportSelector3;
    5. import org.springframework.context.annotation.Configuration;
    6. import org.springframework.context.annotation.Import;
    7. @Configuration
    8. @Import({CustomizeImportSelector1.class, CustomizeImportSelector2.class, CustomizeImportSelector3.class})
    9. public class SysConfig {
    10. }

    复制代码

    • 创建启动类 CustomizeimportselectorApplication.java:
    1. package com.bolingcavalry.customizeimportselector;
    2. import org.springframework.boot.SpringApplication;
    3. import org.springframework.boot.autoconfigure.SpringBootApplication;
    4. @SpringBootApplication
    5. public class CustomizeimportselectorApplication {
    6. public static void main(String[] args) {
    7. SpringApplication.run(CustomizeimportselectorApplication.class, args);
    8. }
    9. }

    复制代码

    • 启动应用,可见输入信息如下:
    1. 2018-09-09 15:43:45.790 INFO 15364 --- [ main] c.b.c.CustomizeimportselectorApplication : Starting CustomizeimportselectorApplication on DESKTOP-82CCEBN with PID 15364 (D:\github\blog_demos\customizeimportselector\target\classes started by 12167 in D:\github\blog_demos\customizeimportselector)
    2. 2018-09-09 15:43:45.791 INFO 15364 --- [ main] c.b.c.CustomizeimportselectorApplication : No active profile set, falling back to default profiles: default
    3. 2018-09-09 15:43:45.825 INFO 15364 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@641147d0: startup date [Sun Sep 09 15:43:45 GMT+08:00 2018]; root of context hierarchy
    4. selectImports : CustomizeImportSelector3
    5. selectImports : CustomizeImportSelector2
    6. selectImports : CustomizeImportSelector1
    7. 2018-09-09 15:43:46.425 INFO 15364 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
    8. 2018-09-09 15:43:46.430 INFO 15364 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
    9. 2018-09-09 15:43:46.431 INFO 15364 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.23
    10. 2018-09-09 15:43:46.493 INFO 15364 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
    11. 2018-09-09 15:43:46.493 INFO 15364 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 670 ms
    12. 2018-09-09 15:43:46.569 INFO 15364 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
    13. 2018-09-09 15:43:46.572 INFO 15364 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
    14. 2018-09-09 15:43:46.572 INFO 15364 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
    15. 2018-09-09 15:43:46.572 INFO 15364 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
    16. 2018-09-09 15:43:46.572 INFO 15364 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
    17. construct : CustomizeServiceImpl1
    18. construct : CustomizeServiceImpl2
    19. construct : CustomizeServiceImpl3

    复制代码

    • 从上述信息可以看出:
    • 首先、三个 selector 实现类的 selectImports 方法执行顺序符合预期:先执行 ImportSelector 实现类的,再执行 DeferredImportSelector 实现类的,并且 DeferredImportSelector 实现类的执行顺序会按照 Order 的设置 从小到大 执行;
    • 其次、CustomizeServiceImpl1、CustomizeServiceImpl2、CustomizeServiceImpl3 的实例化顺序并未受到影响;
    • 至此,ImportSelector 与 DeferredImportSelector 的区别已经分析和验证完毕,随着对 Configuration 初始化处理逻辑的深入了解,我们可以定制出更灵活强大的配置逻辑,以符合业务需求;

    欢迎关注 InfoQ:程序员欣宸

    学习路上,你不孤单,欣宸原创一路相伴...

  • 相关阅读:
    【BOOST C++ 20 设计模式】(1)库Boost.Flyweight
    node基础概念
    FRP内网穿透教程
    GBase8s jdbc开启oracle兼容模式说明
    使用Eclipse搭建STM32嵌入式开发环境
    Google guava之Table简介说明
    获取任意时间段内周、季度、半年的二级联动
    Go 函数的健壮性、panic异常处理、defer 机制
    Illustrator 2022 for mac (AI 2022中文版)
    bsdtar 归档程序在保留文件特殊属性上比 GNU tar 更全面和简便
  • 原文地址:https://blog.csdn.net/java_beautiful/article/details/125458282