聊透Spring类扫描机制

本文深入探讨了Spring的类扫描机制,包括配置类扫描的流程、扫描过滤器的工作原理,以及启动过程中的触发扫描流程。文章分析了Spring为何使用ASM而非Class字节码解析,并解释了符合Spring处理条件的类标准。同时,详细介绍了过滤器如何决定类是否被处理,如matchSelf()、matchClassName()、matchSuperClass()和matchInterface()等方法的匹配逻辑。最后,讨论了类扫描器的注册和启动流程,以及@ComponentScan注解在类扫描中的作用。
摘要由CSDN通过智能技术生成

大家好,我是小满,好久不见,甚是想念。今天我们来聊聊Spring的类扫描机制,也就是Spring判断哪些类归自己管辖,哪些类只能远观不可亵玩的类识别机制。关于Spring的类扫描机制的设计,贰师兄梳理下来的感受是:设计的精巧,实现的优雅,是值得拿出来和小伙伴们解读一波的。吹捧了这么久,我们赶紧进入正题吧,先来看看我们准备从哪几个方面,和大家一起解读Spring的类扫描机制吧。

  1. 配置类扫描的流程分析。
  2. 扫描过滤器的工作流程分析。
  3. 首个配置类解析,触发扫描流程跟踪。
  4. Mybatis拓展类扫描机制,实现Mapper接口类注册分析(本章节限于篇幅,暂不分析)。

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

小满始终坚信,学不会不是你的错,是教你知识的老师的问题。不知道要得罪多少老师了😅

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类扫描流程应该已经很清晰了,那下面又到了我们的总结时刻,我们画一张图加深一下印象,同时给大家附上源码,感兴趣的小伙伴可以自行跟踪调试源码。

# 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,
         comp
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值