欢迎访问我的 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 configCandidates):

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
    //检查每个bean的定义
    for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      try {
        if (bd instanceof AnnotatedBeanDefinition) {
          //对于每个有注解的类,都执行方法parse(AnnotationMetadata metadata, String beanName)
          parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
        }
        else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
          parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
        }
        else {
          parse(bd.getBeanClassName(), holder.getBeanName());
        }
      }
      catch (BeanDefinitionStoreException ex) {
        throw ex;
      }
      catch (Exception ex) {
        throw new BeanDefinitionStoreException(
            "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
      }
    }
    
    //最后再处理DeferredImportSelector的实现类
    processDeferredImportSelectors();
  }
  • 由以上代码可以大致看出 DeferredImportSelector 的实现类被最后放在 processDeferredImportSelectors 方法中处理,那么前面的 parse(AnnotationMetadata metadata, String beanName)做了些什么呢?继续看;

  • 展开方法 parse(AnnotationMetadata metadata, String beanName)里面,是执行 processConfigurationClass 方法;

  • 再展开 processConfigurationClass 方法,看到核心逻辑是调用 doProcessConfigurationClass 方法,展开看看:

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
    //为了聚焦Import相关处理,此处略去部分不相关代码,不在这里展示了
    ...
    ...
    // 处理@Import注解
    processImports(configClass, sourceClass, getImports(sourceClass), true);

    // 处理@ImportResource注解
    if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
      AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
      String[] resources = importResource.getStringArray("value");
      Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
      for (String resource : resources) {
        String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
        configClass.addImportedResource(resolvedResource, readerClass);
      }
    }

    // 处理@Bean注解,注意是处理注解,不是执行@Bean修饰的方法
    Set<MethodMetadata> beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());
    for (MethodMetadata methodMetadata : beanMethods) {
      configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    // 处理Configuration类的父类,外面在调用doProcessConfigurationClass方法的时有迭代处理,确保所有父类的注解都会被处理
    if (sourceClass.getMetadata().hasSuperClass()) {
      String superclass = sourceClass.getMetadata().getSuperClassName();
      if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
        this.knownSuperclasses.put(superclass, configClass);
        // Superclass found, return its annotation metadata and recurse
        return sourceClass.getSuperClass();
      }
    }

    // 再也没有父类了,返回null表示当前Configuration处理完毕
    return null;
  }
  • 根据上述代码分析,可以梳理出下图中的逻辑:

  • 现在需要再看看 processImports 和 processDeferredImportSelectors 这两个方法的具体代码;

  • 先看 processImports 方法:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {

    if (importCandidates.isEmpty()) {
      return;
    }

    if (checkForCircularImports && this.importStack.contains(configClass)) {
      this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
      this.importStack.push(configClass);
      try {
        for (SourceClass candidate : importCandidates) {
          //如果是ImportSelector接口的实现类,就在此处理
          if (candidate.isAssignable(ImportSelector.class)) {
            // Candidate class is an ImportSelector -> delegate to it to determine imports
            Class<?> candidateClass = candidate.loadClass();
            //实例化这些ImportSelector的实现类
            ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
            //如果这实现类还实现了BeanFactoryAware、EnvironmentAware这些接口,就要先执行这些接口中声明的方法
            invokeAwareMethods(selector);
            //如果这个实现类也实现了DeferredImportSelector接口,就被加入到集合deferredImportSelectors中
            if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
              this.deferredImportSelectors.add(
                  new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
            }
            else {
              //注意,这一行是关键代码!!!执行实现类的selectImports方法
              String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
              Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
              processImports(configClass, currentSourceClass, importSourceClasses, false);
            }
          }
          //此处略去的和ImportSelector不相关的逻辑代码
          ...
          ...
          ...
        }
      }
      catch (BeanDefinitionStoreException ex) {
        throw ex;
      }
      catch (Exception ex) {
        throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
            configClass.getMetadata().getClassName() + "]", ex);
      }
      finally {
        this.importStack.pop();
      }
    }
  }

  • 以上代码有两个关键点:

  • 第一、当前被处理的类,如果实现了 DeferredImportSelector 接口,就被加入到集合 deferredImportSelectors 中;

  • 第二、当前被处理的类,如果没有实现 DeferredImportSelector 接口,但是实现了 ImportSelector 接口,就被执行 selectImports 方法;

  • 接下来看看 processDeferredImportSelectors 方法的源码,提前推测应该是处理集合 deferredImportSelectors 中的所有类,这些类都实现了 DeferredImportSelector 接口:

private void processDeferredImportSelectors() {
    List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    this.deferredImportSelectors = null;
    //按照Order注解或者Ordered接口进行排序
    Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);

    for (DeferredImportSelectorHolder deferredImport : deferredImports) {
      ConfigurationClass configClass = deferredImport.getConfigurationClass();
      try {
        //此处是关键代码,执行DeferredImportSelector实现类的selectImports方法
        String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
        processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
      }
      catch (BeanDefinitionStoreException ex) {
        throw ex;
      }
      catch (Exception ex) {
        throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
            configClass.getMetadata().getClassName() + "]", ex);
      }
    }
  }
  • 至此,源码分析完毕了,从代码可以很清晰的看出 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 内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bolingcavalry</groupId>
    <artifactId>customizeimportselector</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>customizeimportselector</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

  • 创建三个接口 CustomizeService1、CustomizeService2、CustomizeService3,第一个源码如下,另外两个除了类名,其余部分一样:
package com.bolingcavalry.customizeimportselector.service;

public interface CustomizeService1 {
    void execute();
}
  • 创建三个类,分别实现上面的三个接口,也是除了类名其余部分一样:
package com.bolingcavalry.customizeimportselector.service.impl;

import com.bolingcavalry.customizeimportselector.service.CustomizeService1;

public class CustomizeServiceImpl1 implements CustomizeService1 {

    public CustomizeServiceImpl1() {
        System.out.println("construct : " + this.getClass().getSimpleName());
    }

    @Override

    public void execute() {
        System.out.println("execute : " + this.getClass().getSimpleName());
    }
}
  • 创建 CustomizeImportSelector1:
package com.bolingcavalry.customizeimportselector.selector;

import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotationMetadata;

@Order
public class CustomizeImportSelector1 implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        System.out.println("selectImports : " + this.getClass().getSimpleName());
        return new String[]{"com.bolingcavalry.customizeimportselector.service.impl.CustomizeServiceImpl1"};
    }
}
  • 创建 CustomizeImportSelector2:
package com.bolingcavalry.customizeimportselector.selector;

import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotationMetadata;

@Order
public class CustomizeImportSelector2 implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        System.out.println("selectImports : " + this.getClass().getSimpleName());
        return new String[]{"com.bolingcavalry.customizeimportselector.service.impl.CustomizeServiceImpl2"};
    }
}
  • 创建 CustomizeImportSelector3,实现的是 ImportSelector 接口:
package com.bolingcavalry.customizeimportselector.selector;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotationMetadata;

public class CustomizeImportSelector3 implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        System.out.println("selectImports : " + this.getClass().getSimpleName());
        return new String[]{"com.bolingcavalry.customizeimportselector.service.impl.CustomizeServiceImpl3"};
    }
}

  • 创建配置类,将 CustomizeImportSelector1、CustomizeImportSelector2、CustomizeImportSelector3 全部用 Import 注解引入:
package com.bolingcavalry.customizeimportselector;

import com.bolingcavalry.customizeimportselector.selector.CustomizeImportSelector1;
import com.bolingcavalry.customizeimportselector.selector.CustomizeImportSelector2;
import com.bolingcavalry.customizeimportselector.selector.CustomizeImportSelector3;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({CustomizeImportSelector1.class, CustomizeImportSelector2.class, CustomizeImportSelector3.class})
public class SysConfig {
}
  • 创建启动类 CustomizeimportselectorApplication.java:
package com.bolingcavalry.customizeimportselector;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CustomizeimportselectorApplication {

    public static void main(String[] args) {
        SpringApplication.run(CustomizeimportselectorApplication.class, args);
    }
}
  • 启动应用,可见输入信息如下:
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)
2018-09-09 15:43:45.791  INFO 15364 --- [           main] c.b.c.CustomizeimportselectorApplication : No active profile set, falling back to default profiles: default
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
selectImports : CustomizeImportSelector3
selectImports : CustomizeImportSelector2
selectImports : CustomizeImportSelector1
2018-09-09 15:43:46.425  INFO 15364 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2018-09-09 15:43:46.430  INFO 15364 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-09-09 15:43:46.431  INFO 15364 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.23
2018-09-09 15:43:46.493  INFO 15364 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-09-09 15:43:46.493  INFO 15364 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 670 ms
2018-09-09 15:43:46.569  INFO 15364 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2018-09-09 15:43:46.572  INFO 15364 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-09-09 15:43:46.572  INFO 15364 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-09-09 15:43:46.572  INFO 15364 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-09-09 15:43:46.572  INFO 15364 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
construct : CustomizeServiceImpl1
construct : CustomizeServiceImpl2
construct : CustomizeServiceImpl3
  • 从上述信息可以看出:

  • 首先、三个 selector 实现类的 selectImports 方法执行顺序符合预期:先执行 ImportSelector 实现类的,再执行 DeferredImportSelector 实现类的,并且 DeferredImportSelector 实现类的执行顺序会按照 Order 的设置 从小到大 执行;

  • 其次、CustomizeServiceImpl1、CustomizeServiceImpl2、CustomizeServiceImpl3 的实例化顺序并未受到影响;

  • 至此,ImportSelector 与 DeferredImportSelector 的区别已经分析和验证完毕,随着对 Configuration 初始化处理逻辑的深入了解,我们可以定制出更灵活强大的配置逻辑,以符合业务需求;