我正在参加「启航方案」

 咱们好,我是贰师兄,好久不见,甚是牵挂。今天咱们来聊聊Spring的类扫描机制,也便是Spring判别哪些类归自己统辖,哪些类只能远观不行亵玩的类辨认机制。关于Spring的类扫描机制的规划,贰师兄整理下来的感触是:规划的精巧,完结的高雅,是值得拿出来和小伙伴们解读一波的。吹捧了这么久,咱们赶忙进入正题吧,先来看看咱们预备从哪几个方面,和咱们一同解读Spring的类扫描机制吧。

  1. 装备类扫描的流程剖析。
  2. 扫描过滤器的作业流程剖析。
  3. 首个装备类解析,触发扫描流程盯梢。
  4. Mybatis拓宽类扫描机制,完结Mapper接口类注册剖析(本章节限于篇幅,暂不剖析)。

 当然,咱们仍是遵循聊透的底线,让咱们做到:知其然,且知其所以然。每个方面咱们都会结合源码详细打开,有理有据,一步一图,让咱们都能的搞得懂。

贰师兄始终坚信,学不会不是你的错,是教你常识的教师的问题。不知道要开罪多少教师了

聊透Spring类扫描机制

1.扫描装备类的流程剖析

 从整个Spring容器的生命周期来看,类扫描产生容器发动的时分,在聊透Spring bean的生命周期中咱们也重复提及,Spring的流程是:先扫描出一切的需求处理类,然后存放到beanDefinitionMap中,终究共同履行实例化操作。

 所以,必定的,类扫描的机遇在Spring容器的生命周期中是比较靠前的,详细由ConfigurationClassPostProcessor这个bean工厂的后置处理器触发,后边第三章有详细介绍,这儿小伙伴有个形象即可。

关于扫描机制的触发,详细的源码位置在:refresh() -> invokeBeanFactoryPostProcessors() -> ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(),感兴趣的小伙伴能够自行检查。

 对类的扫描机遇有了解后,下面咱们一同看一下类扫描的全体流程,大致能够分为下面四个流程:

  1. 经过@ComponentScan获取需求扫描的包,并获取途径下一切的类资源文件。
  2. 经过ASM读取类资源文件的字节码信息。
  3. 判别是否契合Spring处理的条件(运用TypeFilter判别)。
  4. 存放到beanDefinitionMap中,供后续运用。

 流程比较简略,1、4流程很好了解,关于2和3或许咱们有些疑问,主要是关于ASM和Spring契合条件判别,这儿给咱们回答下。

  • Spring为什么选用ASM读取字节码的办法解析类,而没有选用Class字节码解析?

 这儿或许有两个原因,一是或许ASM读取的办法功率更高,故Spring选用这种办法。二是Class字节码解析,需求运用类似于Class.forName的办法,先获取Class方针,可是这个操作必定导致类的加载,假如类有静态代码块,还会触发静态代码块的履行,这会影响用户的行为。因而,Spring选用ASM的办法。

 贰师兄认为应该是第二种原因导致Spring选用SAM的办法的,终究发动阶段那一点点功率真的无关宏旨,可是损坏类加载准则却是很严重的。咱们知道JVM加载类的准则是懒加载,也便是用到谁加载谁,Spring没必要去损坏这个准则。不论用不用,因为我要判别是扫描,都加载进来,这显然是不合理的,也不是Spring的风格。

  • 什么样的类契合Spring的处理条件?

 正常状况下,加了@Component族群的全装备类或许半装备类,都能够被Spring处理。一同为了兼容其他结构,加了javax.annotation.@ManagedBeanjavax.inject.@Named的类也能被Spring处理。不过这只是第一道槛,想被Spring办理,还需求类是非笼统类、非接口类(或许加了@Lookup的笼统类)。单单加了@Component还不行呢,看来委身于人,还不太容易呢。

关于@Component族群、全装备和半装备类不太了解的小伙伴,能够检查贰师兄的聊透spring @Configuration装备类一文,里边说的很清楚了,这儿不再叨叨。

 当然,假如@ComponentScan装备了includeFiltersexcludeFilters特点也会影响能否被处理的判别,详细逻辑和原理,咱们放到下一章节解说。这儿咱们先知道:加了@Component注解的非笼统类,都能被Spring扫描并处理就行了。

  OK,现在咱们对Spring类扫描流程应该现已很明晰了,那下面又到了咱们的总结时刻,咱们画一张图加深一下形象,一同给咱们附上源码,感兴趣的小伙伴能够自行盯梢调试源码。

聊透Spring类扫描机制

# ConfigurationClassParser.java
// ①. 解析装备类上的@ComponentScan,触发扫描
protected final SourceClass doProcessConfigurationClass(
      ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
      throws IOException {
   // 省掉部分非相关代码
   // 3: 获取@ComponentScan,包括@ComponentScans
   Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
         sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
  for (AnnotationAttributes componentScan : componentScans) {
     // 3.1: 解析ComponentScan装备信息,完结扫描(契合IncludeFilter的增加,契合excludeFilter的扫除) (检查②)
     // 扫描出来的现已到放入beanDefinitionMap中了
     Set<BeanDefinitionHolder> scannedBeanDefinitions =
           this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
  }
}
# ComponentScanAnnotationParser.java
// ②. 解析@ComponentScan注解,进行扫描
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
   // 1: 初始化ClassPathBeanDefinitionScanner,
   // 初始化时会注册默许的IncludeFilter,其中包括处理@Component的AnnotationTypeFilter
   ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
         componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
   // 2: 处理在@ComponentScan中装备的信息
   // 2.1 解析装备的includeFilters和excludeFilters
   // 增加咱们自界说的includeFilters,在ComponentScan中装备
   for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
      for (TypeFilter typeFilter : typeFiltersFor(filter)) {
         scanner.addIncludeFilter(typeFilter);
      }
   }
   // 增加咱们自界说的excludeFilters,在ComponentScan中装备
   for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
      for (TypeFilter typeFilter : typeFiltersFor(filter)) {
         scanner.addExcludeFilter(typeFilter);
      }
   }
   // 3: 解析装备的扫描途径(扫描途径能够装备多个)
   Set<String> basePackages = new LinkedHashSet<>();
   String[] basePackagesArray = componentScan.getStringArray("basePackages");
   for (String pkg : basePackagesArray) {
      String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      Collections.addAll(basePackages, tokenized);
   }
   for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
   }
   if (basePackages.isEmpty()) {
      basePackages.add(ClassUtils.getPackageName(declaringClass));
   }
   // 5: 进行扫描 (检查③)
   // a: 把一切的类文件获取到(ASM)
   // b: 获取到类能不能变成beanDefinition需求看是否合格()
   // c: 放入beanDefinitionMap中
   return scanner.doScan(StringUtils.toStringArray(basePackages));
}
# ClassPathBeanDefinitionScanner.java
// ③ 依据包途径扫描,验证是否能够处理,存储到beanDefinitionMap中
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   // 1: 遍历扫描的包名
   for (String basePackage : basePackages) {
      //2: 在包途径下,加载一切契合条件的类界说, 该办法内部完结了扫描,(检查④)
      // a: 验证包下class文件的合法性验证
      // b: 转化是否为非笼统类
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      for (BeanDefinition candidate : candidates) {
            // 3: 放入beanDefinitionMap中,即注册到spring容器中
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}
#ClassPathScanningCandidateComponentProvider.java
// ④ 判别类是否能被Spring处理
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    // 1: 包名转化为途径
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
        resolveBasePackage(basePackage) + '/' + this.resourcePattern;
    // 依据途径转化为Resource,本质是一个输入流
    Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
    // 2: 遍历包下一切的文件
    for (Resource resource : resources) {
      // 3: 读取到的类文件的信息(ASM扫描出来的)
      MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
      // 4: 合规性验证(中心)
        // a: 契合excludeFilters规矩的,直接扫除
        // b: 契合includeFilters规矩的,进行处理
        //   > 是否需求spring处理(加了@Compont等必要注解)
      if (isCandidateComponent(metadataReader)) {
           // 5:判别当前类,是否非笼统的,非接口的
           if (isCandidateComponent(sbd)) {
              // 增加到调集回来
              candidates.add(sbd);
           }
       }
    }
}

2. 扫描过滤器的作业流程

 上一章节咱们现已清楚了类扫描的全体流程,不过也埋了一个究竟什么条件的类Spring才会处理没有讲透,本章节咱们的方针便是:把这个坑填上。

 关于类是否契合Spring处理的判别,是由TypeFilter过滤器来决议的,类扫描器有两个TypeFilter过滤器调集,别离是includeFilters包括过滤器和excludeFilters扫除过滤器,存放着进行契合判别的过滤器。这两个过滤器的联系是:只有满意包括过滤器的,才能被Spring处理,只要满意扫除过滤器,则一定不能被Spring处理

 咱们凭借源码,加深一下这两个过滤器的功用:

// 判别类是否满意Spring扫描条件
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
   // 满意任一扫除过滤器条件,直接不进行处理
   for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
         return false;
      }
   }
   // 满意任一包括过滤器条件,才进行处理(比方加了@Component)
   for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
         return isConditionMatch(metadataReader);
      }
   }
   // 其他状况,不满意包括过滤器的,也不进行处理
   return false;
}

 OK,现在皮球滚到了tf.match()逻辑里边了,生杀大权都在他手里了,咱们只能持续一探终究。这种只能依靠源码才能明晰的底层逻辑,仍是直接对中心源码进行解读。

#AbstractTypeHierarchyTraversingFilter.java
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
      throws IOException {
   // 1: 匹配本身信息
   if (matchSelf(metadataReader)) {
      return true;
   }
   ClassMetadata metadata = metadataReader.getClassMetadata();
   // 2: 匹配类名
   if (matchClassName(metadata.getClassName())) {
      return true;
   }
   // 3: 是否考虑Inherited,在结构办法中传入
   if (this.considerInherited) {
      String superClassName = metadata.getSuperClassName();
      if (superClassName != null) {
         //3.1 对承继的父类进行匹配
         Boolean superClassMatch = matchSuperClass(superClassName);
         if (superClassMatch != null) {
            if (superClassMatch.booleanValue()) {
               return true;
            }
         }
         else {
            // 3.2 运用父类重走匹配逻辑
            if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
                return true;
            }
         }
      }
   }
   // 4: 是否考虑接口,在结构办法中传入
   if (this.considerInterfaces) {
      for (String ifc : metadata.getInterfaceNames()) {
         //4.1 对完结的接口进行匹配
         Boolean interfaceMatch = matchInterface(ifc);
         if (interfaceMatch != null) {
            if (interfaceMatch.booleanValue()) {
               return true;
            }
         }
         else {
             // 4.2 运用完结的接口重走匹配逻辑
             if (match(ifc, metadataReaderFactory)) {
                return true;
             }
         }
      }
   }
   return false;
}

 好家伙,不看不知道,一看吓一跳,咋又引伸出来这么多判别逻辑。不要紧张,不要惧怕,贰师兄和你一同解读一下这些办法,保证包教包会。

聊透Spring类扫描机制

 在探究新逻辑之前,咱们先来看一下TypeFilter的类图联系,有个大致的形象,在剖析时,咱们也会解析详细子类的完结,便于让小伙伴了解这些判别逻辑的效果和运用办法。

  • matchSelf(): 依据本身特征判别是否匹配
     该办法依据类元信息判别是否匹配,参数类型为MetadataReader,经过该参数能够获取到类信息、注解信息等,从而能够快捷的进行匹配判别。
public interface MetadataReader {
   /**
    * 获取类资源信息
    */
   Resource getResource();
   /**
    * 获取类元数据信息(是否接口、是否注解、是否笼统等)
    */
   ClassMetadata getClassMetadata();
   /**
    * 获取类上包括的注解信息
    */
   AnnotationMetadata getAnnotationMetadata();
}

 有了这些信息,咱们就能够自界说匹配逻辑了。这咱们不自己完结,一同看一下Spring最常用的AnnotationTypeFilter过滤器对该办法的完结,供小伙伴们学习参阅。

# AnnotationTypeFilter.java
@Override
protected boolean matchSelf(MetadataReader metadataReader) {
   // 经过metadataReader获取注解信息
   AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
   // 判别是否存在指定注解
   return metadata.hasAnnotation(this.annotationType.getName()) ||
         (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}

 其实注解过滤器AnnotationTypeFilter关于matchSelf()完结很明晰,便是查找是否存在指定注解,假如存在就契合,不存在则契合。

  • matchClassName():依据类名判别是否匹配
     该办法经过类名判别是否匹配。逻辑比较明晰,相同的,咱们看一下Spring另外一个常用的AssignableTypeFilter过滤器对该办法的完结,供小伙伴们学习参阅。
#AssignableTypeFilter.java
@Override
protected boolean matchClassName(String className) {
   //直接判别类名是否相等
   return this.targetType.getName().equals(className);
}
  • matchSuperClass():依据承继的父类信息判别是否匹配
     该办法的履行,需求设置了considerInherited为true,翻译过来的意思是:是否考虑承继的父类信息用于匹配,比方父类注解等。也便是说假如当前类不契合,但承继的父类契合,终究也是能够判别为契合的。

  那依据父类的的什么信息判别呢,当然也是看详细子类过滤器的完结了,咱们仍是拿AnnotationTypeFilter注解过滤器对该办法的完结来说。

# AnnotationTypeFilter.java
@Override
@Nullable
protected Boolean matchSuperClass(String superClassName) {
   return hasAnnotation(superClassName);
}
@Nullable
protected Boolean hasAnnotation(String typeName) {
   // 父类是Object,直接回来不契合
   if (Object.class.getName().equals(typeName)) {
      return false;
   }
   // 要求类必须是java开头的,也便是JDK的类
   else if (typeName.startsWith("java")) {
      if (!this.annotationType.getName().startsWith("java")) {
         return false;
      }
      try {
         // 依据类型获取Class字节码信息,此时是父类的
         Class<?> clazz = ClassUtils.forName(typeName, getClass().getClassLoader());
         // 判别父类的注解
         return ((this.considerMetaAnnotations ? AnnotationUtils.getAnnotation(clazz, this.annotationType) :
               clazz.getAnnotation(this.annotationType)) != null);
      }
      catch (Throwable ex) {
         // Class not regularly loadable - can't determine a match that way.
      }
   }
   return null;
}

 关于匹配逻辑,小伙伴们自行检查注释哦。有一点需求注意,假如matchSuperClass()匹配结果为空,会从头履行match(),也便是履行matchSelf() -> matchSuperClass() -> …的逻辑(子类没有重写match()的状况下),不过传递的参数现已是父类,不再是子类了哦

  下面给小伙伴们弥补一下关于@Inherited的常识,咱们知道通常状况下,父类的注解,子类是无法承继的,那假如想要承继怎样办呢?

  JDK供给了@Inherited注解,该注解效果于一个注解上,也便是说,加了@Inherited注解的注解,假如标示的父类上,子类也是能够承继到该注解的。不过限定于类承继哦,假如类完结的接口上加了@Inherited标示的注解,是不会收效的哦。给小伙伴们总结一下@Inherited承继收效的状况:

类联系 @Inherited润饰的注解能够被承继 说明
类承继 子类会承继父类运用的注解中被@Inherited润饰的注解
类完结 子接口不会承继父接口中的任何注解,不论父接口中运用的注解有没有被@Inherited润饰
接口承继 类完结接口时不会承继任何接口中界说的注解
  • matchInterface():依据完结的接口信息判别是否匹配

 该办法的履行,需求设置了considerInterfaces为true,翻译过来便是:是否考虑完结的接口信息,比方接口注解等。逻辑和matchSuperClass()类似,这儿咱们就不烦琐了。

 过滤器的匹配咱们就说完了,是不是很精巧且高雅,在AbstractTypeHierarchyTraversingFilter笼统类中界说了共同的匹配流程,并将详细完结交由子类,保证共同逻辑且易于拓宽。

 老规矩,咱们持续一同总结一下,加深形象,直接上图:

聊透Spring类扫描机制

扫描器的注册

 好了,开胃菜上完了,下面咱们上主食。小伙伴是不是大吃一惊,看了这么多字,痛苦了这么久,你以为学完了,其实才刚刚开始。能怎样办,作为一个硬核的博主,我得给你讲透啊,不然写这么多字,画这么多图,为了什么啊。

 好了,不废话了,下面咱们一同看一下,履行类扫描的这些内置过滤器,是怎样注册和并驱动作业的,帮小伙伴们连贯起来。

这儿多说一句,看Spring的功用代码,小伙伴们静下心来都能够读的了解,贰师兄认为Spring源码最难的是串联起来,因为很涣散且伏笔许多,需求小伙伴铢积寸累,且对Spring容器生命周期很熟悉才行。因而贰师兄才会不厌其烦的,在每篇文章都用许多的篇幅帮小伙伴整理串联流程。

 咱们再来回顾一下之前剖析过的,Spring判别是否扫描接收的中心逻辑。

# ClassPathScanningCandidateComponentProvider.java
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
   // 满意任一扫除过滤器条件,直接不进行处理
   for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
         return false;
      }
   }
   // 满意任一包括过滤器条件,才进行处理(比方加了@Component)
   for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
         return isConditionMatch(metadataReader);
      }
   }
   // 其他状况,不满意包括过滤器的,也不进行处理
   return false;
}

 代码的履行逻辑,上面咱们剖析过了,这儿不再赘述,本章节咱们要说的,是进行循环判别的this.excludeFiltersthis.includeFilter的过滤器都有哪些?别离是什么时分赋值的?有什么效果?

  • 类扫描器初始化时,默许注册匹配@Component注解的过滤器

 有过Spring运用经历的小伙伴都知道,即便在@ComponentScan不指定includeFilters特点的状况下,Spring也会将@Component标识的类归入办理范围。结合上面咱们的剖析,能够笃定:一定有一个匹配@ComponentTypeFilterthis.includeFilters中。那究竟是不是这样呢,咱们找一下。

 咱们知道,担任类扫描的是ClassPathBeanDefinitionScanner,通常关于特点的初始化都在结构办法中,咱们顺着这个思路找一下:

# ClassPathBeanDefinitionScanner
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
      Environment environment, @Nullable ResourceLoader resourceLoader) {
   Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
   this.registry = registry;
   // 重点:注册默许的过滤器
   if (useDefaultFilters) {
      registerDefaultFilters();
   }
   setEnvironment(environment);
   setResourceLoader(resourceLoader);
}
// 注册处理@Component、@ManagedBean、@Named注解的过滤器
protected void registerDefaultFilters() {
   // 1:注册处理@Component类型的过滤器
   // 假如类上加了 @Component 注解,就在include列表中
   this.includeFilters.add(new AnnotationTypeFilter(Component.class));
   ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
   try {
      // 2: 判别是否加了 @ManagedBean注解,假如项目不想运用spring,可是类需求交给spring办理,能够运用ManagedBean
      // 这儿选用类反射的办法判别类是否存在,类存在的话,才会加进去
      this.includeFilters.add(new AnnotationTypeFilter(
            ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
      logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
   }
   catch (ClassNotFoundException ex) {
      // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
   }
   try {
      // 3:判别是否加了@Named注解
      this.includeFilters.add(new AnnotationTypeFilter(
            ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
      logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
   }
   catch (ClassNotFoundException ex) {
      // JSR-330 API not available - simply skip.
   }
}

  果然,在类扫描器初始化的时分,注册了能够处理@Component的过滤器,所以默许加了@Component的类才能被Spring处理,本来一切都有迹可循啊。

  仔细的小伙伴应该也发现了,注册默许过滤器的前提是useDefaultFilters为true。默许状况下,咱们不在结构方面中指定,默许都为true。这儿Spring也没有设置该参数,所以是成立的。

mybatis完结的类扫描器,就设置的为false哦,其实也好了解,mybatis扫描出需求自己处理的Mapper类即可,这儿的默许注册器是处理Spring的@Component的,mybatis没必要再处理一次。

聊透Spring类扫描机制

  • 解析@ComponentScan注解时,注册注性指定的过滤器

  咱们知道,@ComponentScan是能够指定包括过滤器(includeFilters)和扫除过滤器(excludeFilters)的。必定,这两者也是需求加入到this.includeFilters和中this.excludeFilters中,咱们看一下逻辑。

#ComponentScanAnnotationParser.java
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
    //省掉非相关代码
    // 2: 处理在@ComponentScan中装备的信息
    // 2.1 解析装备的includeFilters和excludeFilters
    // 增加咱们自界说的includeFilters,在ComponentScan中装备
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
       for (TypeFilter typeFilter : typeFiltersFor(filter)) {
          scanner.addIncludeFilter(typeFilter);
       }
    }
    // 增加咱们自界说的excludeFilters,在ComponentScan中装备
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
       for (TypeFilter typeFilter : typeFiltersFor(filter)) {
          scanner.addExcludeFilter(typeFilter);
       }
    }
  //省掉非相关代码
}

 经过源码,明晰的知道,在解析@ComponentScan时,也加入了进来,so easy。

聊透Spring类扫描机制

对@ComponentScan的includeFiltersexcludeFilters怎么指定的小伙伴能够自行百度哦,全体来说比较简略,小伙伴们把握即可,平常作业中也不常常运用。

其实,Spring还增加了一个匿名的扫除过滤器,用来扫除首个装备类的,不过不影响全体流程,细枝末节而已。

 老规矩,咱们一同总结一下,看图说话。

聊透Spring类扫描机制

3. 发动类触发扫描流程概览

  在开始本章节之前,咱们先回忆一下,Spring是怎么发动起来的(怕小伙伴SpringBoot运用太多,忘记了),咱们直接上测验代码。

// 测验类
public class CircularDependencyTest {
   @Test
   public void test(){
      // 指定首个装备类为:CircularDependencyConfig
      AnnotationConfigApplicationContext
            context = new AnnotationConfigApplicationContext(CircularDependencyConfig.class);
      A a = context.getBean(A.class);
   }
}
// 运用@ComponentScan指定扫描包途径
@ComponentScan("com.ivy.circular.dependency")
public class CircularDependencyConfig {
}

  这个便是原始的Spring的写法,在创立ApplicationContext时,指定首个装备类,在首个装备类上运用@ComponentScan指定扫描包途径,Spring在解析到@ComponentScan时,就触发了类扫描流程,从而持续上面咱们吧啦吧啦的一大堆流程。本章节给小伙伴们连贯一下这个流程,对小伙伴们自主阅览源码,是有帮助的。

SpringBoot的流程和这儿的原生Spring写法,差异不大,只是稍微做了一点自动化的操作,本质和上述流程共同的,后边有机会给咱们共享SpringBoot时,再跟咱们细讲。

 首先,咱们需求清楚,在创立ApplicationContext时指定的首个装备类,是直接注册到beanDefinitionMap中的。后续咱们运用时,也是直接从beanDefinitionMap中获取即可。

 那后续的流程小伙伴或许有都清楚了,咱们上面简略提过,这儿再给咱们整理一下:容器进行发动(refresh()) -> 调用bean工厂的后置处理器(invokeBeanFactoryPostProcessors) -> 履行ConfigurationClassPostProcessor后置处理器的postProcessBeanDefinitionRegistry()办法 ->取出首个装备类,进行装备类解析(解分出@ComponentScan,触发扫描) -> 扫描出的类存放到beanDefinitionMap中 -> 进行bean的实例化。

 关于类的扫描流程咱们上述都剖析过,这儿最最关键的是:依据首个装备类解析,解析到@ComponentScan时触发扫描,对扫描出的类持续递归解析,直到一切装备类都解析完结

 关于装备类的解析内容,是很大很大的一块内容,涉及到的常识点也许多,限于篇幅和小伙伴的阅览体验,咱们这儿不赘述了,给咱们整理了一张流程图,很明晰明了,咱们必定能看了解,咱们认真观看即可,后续咱们有需求,咱们也能够独自剖析。

聊透Spring类扫描机制

咱们一定要认真看装备类的解析流程,大部分Spring中心功用都在这儿触发发动的,比方@PropertySources@ComponentScan@Import@Bean等等,能够这么说,搞懂了ConfigurationClassPostProcessor这个后置处理器,Spring你就会70%了,什么AOP、事务啥的,轻轻松松拿捏。小伙伴们一定要静下心来啃一啃,相信我,会有收获的。

 好了,本来还想给小伙伴们剖析一下Mybatis是怎样拓宽Spring这一套扫描流程,完结Mapper文件的扫描、解析、注册的。篇幅的确有点太长了,咱们反馈读一篇贰师兄的文章,比上一天班还累。找机会咱们独自写吧,就不掺杂在这儿面了,感兴趣的小伙伴也可自行研究一下,咱们一同交流。