Spring 自动装配[1.1] import - 解析

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

前言

我们知道:

  • SpringBoot简化了Spring的配置,有了SpringBoot之后我们就不用写一大堆东西来引入中间件了,只需要我们在启动类上加上:

    @SpringBootApplication
    复制代码

    并加上对应的中间件的注解,比如此时我们需要引入一个Eureka:

    @EnableEurekaClient
    复制代码

    这样我们就能把对应的中间件引入到应用中了(当然,其他的什么地址之类的东西还是要配置的)。

那么这个功能是如何实现的呢?

@SpringBootApplication

首先,我们来看看这个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
复制代码

自动配置自动配置,这里有个注解和我们想看到的关系很大:

@EnableAutoConfiguration

这个注解如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
复制代码

这里两个auto,我们先看看这个:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
复制代码

到这里出现了两个import,这是干啥用的?看起来应该是很重要的东西。

那么来看看这个Import注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
复制代码

注解上写的是:标识一个或多个component类需要被引入,例如:@Configuration标识的类。

@Configuration注解可太熟了,注册数据源、redis之类的东西,都会写这么个玩意。

打开idea看看用到@Import注解的地方,可以发现其实大部分的enableXXX的注解,都是通过这玩意实现的,例如:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
复制代码

那么此时就有问题了:

  • 这个@Import是如何工作的?
  • @Import中的类,又该如何编码呢?

@Import

继续通过idea,来看看@Import是在什么地方被处理的.

找了好久终于找到了两个类对于这个注解有处理:

  • ConfigurationClassParser
  • ConfigurationClassUtils

我们一个个看。

ConfigurationClassParser

收集import信息

我们是从这段代码中看到Import注解被处理的:

private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
      throws IOException {

   if (visited.add(sourceClass)) {
      for (SourceClass annotation : sourceClass.getAnnotations()) {
         String annName = annotation.getMetadata().getClassName();
         if (!annName.equals(Import.class.getName())) {
            collectImports(annotation, imports, visited);
         }
      }
      imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
   }
}
复制代码

方法上的注解写得很明白:

  • 递归地将Import的值收集起来。
  • 至于为什么是递归,下面也给了个例子:
For example, it is common for a @Configuration class to declare direct
 @Import s in addition to meta-imports originating from an @Enable
annotation.
复制代码

​ 意思大概就是**@Configuration经常用一个@Import注解,来引入一些源于@Enable**注解的元导入。

​ 也就是说一层是不够解析出要import的所有对象的,写代码的经常除了@Import之外给你用一些@EnableXXX的玩意,这些玩意在这里也不好直接解析(万一enable里再enable的情况咋整),所以这里就直接给开个递归解决这种问题。

这个方法粗浅一点看也大概知道是个什么意思:将sourceClass里包括的(以及可能存在的更多层次的)@Import里标识的那些类,给你塞到这个imports里。

这里的sourceClass不必深究,我们此时看到的代码层次只要知道是对于class对象的一个封装即可。【1】

上面方法的链路

上面的方法我们也看到了:

  • 这其实就是个收集的方法

那么这里离我们要看到的如何注入的东西还是相差甚远的,此时我们在这个子问题中需要解决的是:

  • 哪里调用来收集?
  • 哪里处理已收集的信息?

我们先来看看这个方法的调用链:

graph LR
doProcessConfigurationClass-->getImports --> collectImports --> processImports

getImport就是个safe的代理方法,本身没什么就是保证两个list不是null。

上面的关系是从这里得出的:

// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
复制代码

看样子,processImports就是处理这些import注解的方法了。

import信息的处理

这里一共五十几行代码,为了方便对应啥意思就写代码上了,可能会出现阅读困难的地方做了标记,下面会解释:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
      boolean checkForCircularImports) {

    //如果你要import的东西都没有,那我就不处理了
   if (importCandidates.isEmpty()) {
      return;
   }

    //这里就是判断是否有循环import的说法了
    //我们知道Spring会给你处理循环的bean,但是这里如果出现循环就直接报错了
   if (checkForCircularImports && isChainedImportOnStack(configClass)) {
      this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
   }
    
    //其实这里都不用写else,上面的卫语句已经处理掉了
   else {
       //上面判断是不是有循环的stack,在这里做更新
      this.importStack.push(configClass);
      try {
         for (SourceClass candidate : importCandidates) {
             //如果我们import的类是个ImportSelector:实例化这个selector【A1】
            if (candidate.isAssignable(ImportSelector.class)) {
               // Candidate class is an ImportSelector -> delegate to it to determine imports
               Class<?> candidateClass = candidate.loadClass();
               ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                     this.environment, this.resourceLoader, this.registry);
               Predicate<String> selectorFilter = selector.getExclusionFilter();
                //这里就是加上了selector中指定的判断了
               if (selectorFilter != null) {
                  exclusionFilter = exclusionFilter.or(selectorFilter);
               }
                //看名字也知道:延迟importSelector,把这个行为往后延迟,等到后面再处理【A2】
               if (selector instanceof DeferredImportSelector) {
                  this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
               }
                //这里对应的是:是importSelector,但不是延迟的,那么就直接处理这个importSelector了呗
               else {
                   //调用selectIimports方法,获取需要import的类名称
                  String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                   //封装
                  Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                   //递归了,原因跟上面的collectImports类似
                  processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
               }
            }
             //这里的else对应的是:sourceClass不是ImportSelector,而是:ImportBeanDefinitionRegistrar【A3】
            else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
               // Candidate class is an ImportBeanDefinitionRegistrar ->
               // delegate to it to register additional bean definitions
               Class<?> candidateClass = candidate.loadClass();
               ImportBeanDefinitionRegistrar registrar =
                     ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                           this.environment, this.resourceLoader, this.registry);
               configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
            }
             //这里看注释就知道是来处理@Configuration的【2】
            else {
               // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
               // process it as an @Configuration class
               this.importStack.registerImport(
                     currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
               processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
            }
         }
      }
      catch (BeanDefinitionStoreException ex) {
         throw ex;
      }
      catch (Throwable ex) {
         throw new BeanDefinitionStoreException(
               "Failed to process import candidates for configuration class [" +
               configClass.getMetadata().getClassName() + "]", ex);
      }
      finally {
         this.importStack.pop();
      }
   }
}
复制代码
  • 【A1】:ImportSelector

    还记不记得之前提到的EnableAutoConfiguration?这里import的就是个ImportSelector:

    @Import(AutoConfigurationImportSelector.class)

    这个接口提供了两个方法:

    //根据元数据,返回需要导入的具体的class名称
    String[] selectImports(AnnotationMetadata importingClassMetadata);
    
    //返回一个判断函数,这个函数如果返回的是true,代表输入的String不会被作为一个配置类处理
    default Predicate<String> getExclusionFilter() {
    		return null;
    	}
    复制代码
  • 【A2】:DeferredImportSelector

    从类名可以看出来:延迟的importSelector,当然是继承ImportSelector的。

    这个接口额外提供了一个方法:

    Class<? extends Group> getImportGroup()
    复制代码

    这个ImportGroup,是这个接口中定义的一个子类,用于收集不同importSelector中的结果的。

    上面说的AutoConfigurationImportSelector实际上实现的是这个接口。

    而这里的handle其实做的事情就是把这个延迟的给存起来:

    public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
    DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
    if (this.deferredImportSelectors == null) {
    DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
    handler.register(holder);
       //这里会处理一下存起来的
    handler.processGroupImports();
    }
    else {
    this.deferredImportSelectors.add(holder);
    }
    }
    复制代码
  • 【A3】:ImportBeanDefinitionRegistrar

    看类名,这个就和BeanDefinition相关了,该接口上的注释也是这么说的:

    Interface to be implemented by types that register additional bean definitions when
    processing @{@link Configuration} classes. Useful when operating at the bean definition
    level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
    复制代码

    大概就是说这个类,是用来注册一些额外的beanDefinition的,这部分我们放到后面再来看看。【3】

小结

那么,上面的代码做了什么事情?

  • 通过预先取出来import里的SourceClass来做处理。

  • 如何处理(下面说的是sourceClass对应的类)?

    • 如果是importSelector
      • 如果不是:递归往下处理
      • 如果是延迟(DeferedImportSelector):
        • 那么先存起来
    • 如果不是,分为ImportBeanDefinitionRegistrar其他的情况,分别处理【2】【3】

    这里做一下分析:

    • 因为处理必须是有目的的:要么改变某些东西,要么保存某些东西。
    • 而这里如果是ImportSelector,且不是Defered,那么其实是递归处理的没有做额外的保存等工作
    • 因此,因为这里的处理有目的性,可见ImportSelector中的selectImport方法,返回的最终结果(多次递归之后最终不需要再往下递归的情况),在这里必须是DeferImportSelector或者是ImportBeanDefinitionRegistrar,或者是其他的东西(多是@Configuration,反正是可以按照@Configuration处理的),而不能只是ImportSelector。

    因此这里回过头来看【A1】,是否对于selectImports方法有了更深一点的理解?

小结

这里我们算是知道了:

  • @import注解是在哪个方法中做的处理。

但就现在来看,我们只知道这个注解可能:

  • 有多层次的情况
  • 有循环import的可能

以及一些具体处理的细节。

但是具体处理的落地,在挖的坑【2】和【3】中。

这就意味着此时我们并没有看完整个过程的代码,只是流程中的一小部分,此时还涉及到:

  • 这个方法在哪里被调用? - 这个事情很关键,我们需要知道@Import是如何工作的。
  • 下面的坑要填 - 【2】【3】具体如何落地?
  • 延迟的ImportSelector是如何工作的?

还有【1】,这个SourceClass是什么东西?

猜你喜欢

转载自juejin.im/post/7031901957505695780