https://blog.csdn.net/boling_cavalry/article/details/82555352
在使用@Import注解来注册bean的时候,Import注解的值可以是ImportSelector或者DeferredImportSelector的实现类,spring容器会实例化这个实现类,并执行其selectImports方法,那么问题来了:ImportSelector和DeferredImportSelector的区别在哪里,我们自定义Imort逻辑的时候该选择哪个呢?本文通过分析相关的spring源码来查找答案;
原文地址:ImportSelector与DeferredImportSelector的区别(spring4)_程序员欣宸的博客-CSDN博客_importselector
全文概览
本文由以下几部分组成: \1. 看官方文档; \2. 分析spring源码中对这两个接口的处理; \3. 实战验证;
看官方文档
先看官方文档看起,我选择了4.3.9版本在线文档(这是个Release版),地址:Spring Framework 4.3.19.RELEASE 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源码中对这两个接口的处理
接下来看看源码: \1. 在spring-framework-4.1.8.RELEASE工程中找到类ConfigurationClassParser.java,这里面有处理配置类的主要逻辑; \2. 找到方法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(); }1234567891011121314151617181920212223242526272829
由以上代码可以大致看出DeferredImportSelector的实现类被最后放在processDeferredImportSelectors方法中处理,那么前面的parse(AnnotationMetadata metadata, String beanName)做了些什么呢?继续看;
\3. 展开方法parse(AnnotationMetadata metadata, String beanName)里面,是执行processConfigurationClass方法; \4. 再展开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; }12345678910111213141516171819202122232425262728293031323334353637
\5. 根据上述代码分析,可以梳理出下图中的逻辑:
现在需要再看看processImports和processDeferredImportSelectors这两个方法的具体代码;
\6. 先看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(); } } }12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
以上代码有两个关键点: 第一、当前被处理的类,如果实现了DeferredImportSelector接口,就被加入到集合deferredImportSelectors中; 第二、当前被处理的类,如果没有实现DeferredImportSelector接口,但是实现了ImportSelector接口,就被执行selectImports方法;
\7. 接下来看看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); } } }12345678910111213141516171819202122
至此,源码分析完毕了,从代码可以很清晰的看出ImportSelector与DeferredImportSelector的区别,就是selectImports方法执行时机有差别,这个差别期间,spring容器对此Configguration类做了些其他的逻辑:包括对@ImportResource、@Bean这些注解的处理(注意,这里只是对@Bean修饰的方法的处理,并不是立即调用@Bean修饰的方法,这个区别很重要!);
实战验证
接下来到了实战验证的环节了,本次实战的内容是创建一个springboot工程,在里面自定义三个ImportSelector接口的实现类,如果您不想敲代码,也可以去github下载源码,地址和链接信息如下表所示:
名称 | 链接 | 备注 |
---|---|---|
项目主页 | GitHub - zq2599/blog_demos: CSDN博客专家程序员欣宸的github,这里有五百多篇原创文章的详细分类和汇总,以及对应的源码,内容涉及Java、Docker、Kubernetes、DevOPS等方面 | 该项目在GitHub上的主页 |
git仓库地址(https) | GitHub - zq2599/blog_demos: CSDN博客专家程序员欣宸的github,这里有五百多篇原创文章的详细分类和汇总,以及对应的源码,内容涉及Java、Docker、Kubernetes、DevOPS等方面 | 该项目源码的仓库地址,https协议 |
git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
这个git项目中有多个文件夹,本章源码在文件夹customizeimportselector下,如下图红框所示:
开始编码吧: \1. 我们创建三个ImportSelector的实现类来检查其先后顺序,三个Selector类简介如下表,有两个是DeferredImportSelector的实现类,一个是ImportSelector的实现类,每个Selector负责向spring容器注册一种实例,:
类名 | 实现接口 | Order注解 | Import的类 |
---|---|---|---|
CustomizeImportSelector1 | DeferredImportSelector | 102 | CustomizeServiceImpl1 |
CustomizeImportSelector2 | DeferredImportSelector | 101 | CustomizeServiceImpl2 |
CustomizeImportSelector3 | ImportSelector | 无 | CustomizeServiceImpl3 |
\2. 基于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>123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
\3. 创建三个接口CustomizeService1、CustomizeService2、CustomizeService3,第一个源码如下,另外两个除了类名,其余部分一样:
package com.bolingcavalry.customizeimportselector.service; public interface CustomizeService1 { void execute(); }12345
\4. 创建三个类,分别实现上面的三个接口,也是除了类名其余部分一样:
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()); } }12345678910111213141516
\5. 创建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(102) 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"}; } }123456789101112131415
\6. 创建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(101) 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"}; } }1234567891011121314
\7. 创建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"}; } }12345678910111213
\8. 创建配置类,将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 { }123456789101112
\9. 创建启动类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); } }123456789101112
10, 启动应用,可见输入信息如下:
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 : CustomizeServiceImpl312345678910111213141516171819
从上述信息可以看出: 首先、三个selector实现类的selectImports方法执行顺序符合预期:先执行ImportSelector实现类的,再执行DeferredImportSelector实现类的,并且DeferredImportSelector实现类的执行顺序会按照Order的设置从小到大执行; 其次、CustomizeServiceImpl1、CustomizeServiceImpl2、CustomizeServiceImpl3的实例化顺序并未受到影响;
至此,ImportSelector与DeferredImportSelector的区别已经分析和验证完毕,随着对Configuration初始化处理逻辑的深入了解,我们可以定制出更灵活强大的配置逻辑,以符合业务需求;