同胞兄弟,ImportSelector接口与DeferredImportSelector接口的区别

本文主要是介绍同胞兄弟,ImportSelector接口与DeferredImportSelector接口的区别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、前言
  • 二、ImportSelector和DeferredImportSelector的区别
  • 三、分析spring源码中对这两个接口的处理
    • 3.1 ConfigurationClassParser类的方法parse()
    • 3.2 parse()方法调用processConfigurationClass()方法
    • 3.3 processConfigurationClass()方法调用doProcessConfigurationClass()方法
    • 3.4 源码解析:processImports()
    • 3.5 源码解析:processDeferredImportSelectors()
  • 四、面试金手指(ImportSelector和DeferredImportSelector的区别)
  • 五、小结

一、前言

上一篇博客【Spring源码解析 011 Spring处理Imports注解】知道,在使用@Import注解来注册bean的时候,Import注解的值可以是ImportSelector或者DeferredImportSelector的实现类spring容器会实例化这个实现类,并执行其selectImports方法

这篇博客的问题是:ImportSelector和DeferredImportSelector的区别在哪里,我们自定义Imort逻辑的时候该选择哪个呢?本文通过分析相关的spring源码来查找答案;

本文接口:

  1. ImportSelector和DeferredImportSelector的区别;
  2. 分析spring源码中对这两个接口的处理;

二、ImportSelector和DeferredImportSelector的区别

  1. DeferredImportSelector接口是ImportSelector接口的一个扩展;
  2. ImportSelector实例的selectImports方法的执行时机,是在@Configguration注解中的其他逻辑被处理之前,所谓的其他逻辑,包括对@ImportResource、@Bean这些注解的处理(注意,这里只是对@Bean修饰的方法的处理,并不是立即调用@Bean修饰的方法,这个区别很重要!);
  3. DeferredImportSelector实例的selectImports方法的执行时机,是在@Configguration注解中的其他逻辑被处理完毕之后,所谓的其他逻辑,包括对@ImportResource、@Bean这些注解的处理;
  4. DeferredImportSelector的实现类可以用Order注解,或者实现Ordered接口来对selectImports的执行顺序排序;

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

3.1 ConfigurationClassParser类的方法parse()

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());   // parse方法,两个参数}else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());   // parse方法,两个参数}else {parse(bd.getBeanClassName(), holder.getBeanName());   // parse方法,两个参数}}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)做了些什么呢?继续看;

3.2 parse()方法调用processConfigurationClass()方法

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

3.3 processConfigurationClass()方法调用doProcessConfigurationClass()方法

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

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {//为了聚焦Import相关处理,此处略去部分不相关代码,不在这里展示了......// 处理@Import注解  1、 要进去里面看具体逻辑 processImports()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 recursereturn sourceClass.getSuperClass();}}// 再也没有父类了,返回null表示当前Configuration处理完毕return null;}

根据doProcessConfigurationClass(),可以梳理出下图中的逻辑:

在这里插入图片描述

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

3.4 源码解析:processImports()

先看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 importsClass<?> 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方法;

金手指1:因为DeferredImportSelector接口是ImportSelector接口的子接口,所有判断逻辑在ImportSelector接口的if判断的里面
(1)当前被处理的类,如果实现了DeferredImportSelector接口,就被加入到集合deferredImportSelectors中,还没有处理;
(2)当前被处理的类,如果没有实现DeferredImportSelector接口,但是实现了ImportSelector接口,就立即处理,就被执行selectImports方法;
金手指2:processImports()方法中执行selectImports()方法的位置,找到selectImports()方法执行的位置就好了。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ImportBeanDefinitionRegistrar的处理
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
普通类
在这里插入图片描述

3.5 源码解析:processDeferredImportSelectors()

接下来看看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修饰的方法,这个区别很重要!);

四、面试金手指(ImportSelector和DeferredImportSelector的区别)

  1. DeferredImportSelector接口是ImportSelector接口的一个扩展;
  2. ImportSelector实例的selectImports方法的执行时机,是在@Configguration注解中的其他逻辑被处理之前,所谓的其他逻辑,包括对@ImportResource、@Bean这些注解的处理(注意,这里只是对@Bean修饰的方法的处理,并不是立即调用@Bean修饰的方法,这个区别很重要!);
  3. DeferredImportSelector实例的selectImports方法的执行时机,是在@Configguration注解中的其他逻辑被处理完毕之后,所谓的其他逻辑,包括对@ImportResource、@Bean这些注解的处理;
  4. DeferredImportSelector的实现类可以用Order注解,或者实现Ordered接口来对selectImports的执行顺序排序;

根据doProcessConfigurationClass(),可以梳理出下图中的逻辑:

在这里插入图片描述

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

五、小结

ImportSelector接口与DeferredImportSelector接口的区别,好了。

天天打码,天天进步!!!

这篇关于同胞兄弟,ImportSelector接口与DeferredImportSelector接口的区别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/481284

相关文章

native和static native区别

本文基于Hello JNI  如有疑惑,请看之前几篇文章。 native 与 static native java中 public native String helloJni();public native static String helloJniStatic();1212 JNI中 JNIEXPORT jstring JNICALL Java_com_test_g

Java 后端接口入参 - 联合前端VUE 使用AES完成入参出参加密解密

加密效果: 解密后的数据就是正常数据: 后端:使用的是spring-cloud框架,在gateway模块进行操作 <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.0-jre</version></dependency> 编写一个AES加密

Android fill_parent、match_parent、wrap_content三者的作用及区别

这三个属性都是用来适应视图的水平或者垂直大小,以视图的内容或尺寸为基础的布局,比精确的指定视图的范围更加方便。 1、fill_parent 设置一个视图的布局为fill_parent将强制性的使视图扩展至它父元素的大小 2、match_parent 和fill_parent一样,从字面上的意思match_parent更贴切一些,于是从2.2开始,两个属性都可以使用,但2.3版本以后的建议使

Collection List Set Map的区别和联系

Collection List Set Map的区别和联系 这些都代表了Java中的集合,这里主要从其元素是否有序,是否可重复来进行区别记忆,以便恰当地使用,当然还存在同步方面的差异,见上一篇相关文章。 有序否 允许元素重复否 Collection 否 是 List 是 是 Set AbstractSet 否

javascript中break与continue的区别

在javascript中,break是结束整个循环,break下面的语句不再执行了 for(let i=1;i<=5;i++){if(i===3){break}document.write(i) } 上面的代码中,当i=1时,执行打印输出语句,当i=2时,执行打印输出语句,当i=3时,遇到break了,整个循环就结束了。 执行结果是12 continue语句是停止当前循环,返回从头开始。

maven发布项目到私服-snapshot快照库和release发布库的区别和作用及maven常用命令

maven发布项目到私服-snapshot快照库和release发布库的区别和作用及maven常用命令 在日常的工作中由于各种原因,会出现这样一种情况,某些项目并没有打包至mvnrepository。如果采用原始直接打包放到lib目录的方式进行处理,便对项目的管理带来一些不必要的麻烦。例如版本升级后需要重新打包并,替换原有jar包等等一些额外的工作量和麻烦。为了避免这些不必要的麻烦,通常我们

ActiveMQ—Queue与Topic区别

Queue与Topic区别 转自:http://blog.csdn.net/qq_21033663/article/details/52458305 队列(Queue)和主题(Topic)是JMS支持的两种消息传递模型:         1、点对点(point-to-point,简称PTP)Queue消息传递模型:         通过该消息传递模型,一个应用程序(即消息生产者)可以

深入探讨:ECMAScript与JavaScript的区别

在前端开发的世界中,JavaScript无疑是最受欢迎的编程语言之一。然而,很多开发者在使用JavaScript时,可能并不清楚ECMAScript与JavaScript之间的关系和区别。本文将深入探讨这两者的不同之处,并通过案例帮助大家更好地理解。 一、什么是ECMAScript? ECMAScript(简称ES)是一种脚本语言的标准,由ECMA国际组织制定。它定义了语言的语法、类型、语句、

Lua 脚本在 Redis 中执行时的原子性以及与redis的事务的区别

在 Redis 中,Lua 脚本具有原子性是因为 Redis 保证在执行脚本时,脚本中的所有操作都会被当作一个不可分割的整体。具体来说,Redis 使用单线程的执行模型来处理命令,因此当 Lua 脚本在 Redis 中执行时,不会有其他命令打断脚本的执行过程。脚本中的所有操作都将连续执行,直到脚本执行完成后,Redis 才会继续处理其他客户端的请求。 Lua 脚本在 Redis 中原子性的原因

java线程深度解析(一)——java new 接口?匿名内部类给你答案

http://blog.csdn.net/daybreak1209/article/details/51305477 一、内部类 1、内部类初识 一般,一个类里主要包含类的方法和属性,但在Java中还提出在类中继续定义类(内部类)的概念。 内部类的定义:类的内部定义类 先来看一个实例 [html]  view plain copy pu