ImportSelector 与 DeferredImportSelector 的区别(spring4)

ImportSelector 与 DeferredImportSelector 的区别(spring4)

欢迎访问我的 GitHub

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

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

全文概览

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

    • 先看官方文档看起,我选择了 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.
    • 我的理解:
  • DeferredImportSelector 是 ImportSelector 的一个扩展;
  • ImportSelector 实例的 selectImports 方法的执行时机,是在 @Configguration 注解中的其他逻辑被处理 之前 ,所谓的其他逻辑,包括对 @ImportResource、@Bean 这些注解的处理(注意,这里只是对 @Bean 修饰的方法的处理,并不是立即调用 @Bean 修饰的方法,这个区别很重要!);
  • DeferredImportSelector 实例的 selectImports 方法的执行时机,是在 @Configguration 注解中的其他逻辑被处理 完毕之后 ,所谓的其他逻辑,包括对 @ImportResource、@Bean 这些注解的处理;
  • DeferredImportSelector 的实现类可以用 Order 注解,或者实现 Ordered 接口来对 selectImports 的执行顺序排序;
  • 分析 spring 源码中对这两个接口的处理

    • 接下来看看源码:
    • 在 spring-framework-4.1.8.RELEASE 工程中找到类 ConfigurationClassParser.java,这里面有处理配置类的主要逻辑;
    • 找到方法 parse(Set configCandidates):

    public void parse(Set configCandidates) { this.deferredImportSelectors = new LinkedList(); //检查每个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 readerClass = importResource.getClass(“reader”); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // 处理@Bean注解,注意是处理注解,不是执行@Bean修饰的方法 Set 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 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 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 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 内容如下:

    4.0.0 com.bolingcavalry customizeimportselector 0.0.1-SNAPSHOT jar customizeimportselector Demo project for Spring Boot org.springframework.boot spring-boot-starter-parent 1.5.9.RELEASE UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin

    复制代码

    • 创建三个接口 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(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”}; }}

    复制代码

    • 创建 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”}; }}

    复制代码

    • 创建 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;@SpringBootApplicationpublic 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:githubblog_demoscustomizeimportselectorargetclasses started by 12167 in D:githubblog_demoscustomizeimportselector)2018-09-09 15:43:45.791 INFO 15364 — [ main] c.b.c.CustomizeimportselectorApplication : No active profile set, falling back to default profiles: default2018-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 hierarchyselectImports : CustomizeImportSelector3selectImports : CustomizeImportSelector2selectImports : CustomizeImportSelector12018-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.232018-09-09 15:43:46.493 INFO 15364 — [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext2018-09-09 15:43:46.493 INFO 15364 — [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 670 ms2018-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 : CustomizeServiceImpl1construct : CustomizeServiceImpl2construct : CustomizeServiceImpl3

    复制代码

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

    欢迎关注 InfoQ:程序员欣宸

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

    郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
    (0)
    用户投稿
    上一篇 2022年6月28日
    下一篇 2022年6月28日

    相关推荐

    • 语文说明文和记叙文的阅读方法是什么?

      很高兴回答你的问题! 说明文和记叙文是中小学常考的一种文体,掌握一定的答题技巧和方法是非常重要的! 关于说明文的考点,穆老师为大家整理如下: 关于记叙文考点,穆老师为大家整理如下:…

      2022年8月2日
    • 大合同赛道的叙事逻辑

      「合同」作为交易的载体,承载着企业经济活动的方方面面,因此围绕合同,衍生出了多个创业赛道,比如合同管理、智能合同审查、合同模板库等等,并诞生了众多法律科技公司。 之所以说法律科技公…

      2022年8月31日
    • 《奥拓星球:强敌》游民评测7.3分:机械军团,编程守家

      在游玩许多城市建造类游戏时,我常常会想:如果我有一支不需要食物和水,可日夜工作不知疲累的机器人队伍,那我便不再需要扮演压榨工人们的独裁者,还能轻松建立起一个各类设施齐备、且具有高度…

      2022年8月1日
    • 王者荣耀MVP加星卡在哪里领取?王者荣耀MVP加星卡获取方法

      王者荣耀MVP加星卡在哪里领取?MVP加星卡作为最新七周年的活动道具,不少玩家都想要了解MVP加星卡的获取途径,想要了解MVP加星卡获取方法的可以参考下面的攻略,小编会详细为大家介…

      2022年10月31日
    • 王者荣耀和英雄联盟手游最大的区别是什么?

      本篇均采用网友的回答 夜半蓝调: 玩了几个月的英雄联盟手游Q,以下是我的感受 1.意识。 王者荣耀因为没有眼,所以王者的意识在于你要通过小地图猜对方的位置,而lolm有眼, 你只要…

      2022年7月9日
    • 电视盒子哪个牌子最好?数码达人总结2022电视盒子推荐

      电视盒子哪个牌子最好? 最近在网上看到很多网友都有这样的疑问,虽说现在智能电视机更多,但是电视盒子的存在还是很有必要的,因为用上几年后,智能电视机也难逃卡顿的命运。所以电视盒子的需…

      2022年6月17日
    • Python – 魔法方法和方法重写

      魔法方法总结 字符串/字节序列表示相关:__repr__、__str__,__format__,__bytes__。这些主要是做一些格式化的操作。 数值转换:__abs__、__b…

      2022年8月20日
    • 新能源主线持续爆发,美女基金经理闫思倩是如何抓住机会的?

      今年以来,A股风云突变,前面4个月基本就是调整态势了,尤其是新能源等赛道股,由于前2年涨幅大,今年前4个月调整力度也很大。而自4月27日A股创下阶段低点2863点以来,A股持续爆发…

      2022年6月20日
    • 中兴商用平板K98正式发布

      骁龙680处理器 10.4寸2K高清显示屏 后置1300万像素 前置800万像素 7.8mm,净重490g 7250mAh电池 配备Pogo Pin,3.5mmAudio Jack…

      2022年7月27日
    • 陨石

      图说陨石与一般石头的区别。

      2022年8月17日

    联系我们

    联系邮箱:admin#wlmqw.com
    工作时间:周一至周五,10:30-18:30,节假日休息