【小家Spring】Spring解析@ComponentScan注解源码分析(ComponentScanAnnotationParser、ClassPathBeanDefinitionScanner)(上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 【小家Spring】Spring解析@ComponentScan注解源码分析(ComponentScanAnnotationParser、ClassPathBeanDefinitionScanner)(上)

前言


前面我在这篇博文:【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser) 解释Spring解析@Configuration的时候,提到过了解析:@PropertySource、@ComponentScan、@Import…等等的解析过程。


它在这个类里大概如下:ConfigurationClassParser#doProcessConfigurationClass:


protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass){
  //1、Process any @PropertySource annotations
  //2、Process any @ComponentScan annotations ===
  ==== 这一步解析,现在就是我们今天的主题,下面会对源码进行分析 ====
  //3、Process any @Import annotations
  //4、Process any @ImportResource annotations
  //5、Process individual @Bean methods
  //6、Process default methods on interfaces
  //7、Process superclass, if any
}


因为扫描模式在应用(我们自己在涉及自己的框架的时候,扫描模式也会被大量的、广泛的应用)使用非常的广泛,因此本文有必要来说说@ComponentScan的原理,旨在掌握它的运行过程,然后学以致用。


Spring Boot默认扫描Bean的处理,就是基于@ComponentScan这个注解的


源码分析


入口处源码


前言部分已经提到了入口处,因此这里直接贴出此部分的源码吧:

    // Process any @ComponentScan annotations
    // 拿到该类上面所有的@ComponentScan注解,包含重复注解
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    // 不为空,且此类不会被跳过,就开始解析吧
    if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      for (AnnotationAttributes componentScan : componentScans) {
        // The config class is annotated with @ComponentScan -> perform the scan immediately
        // 最终委托给了componentScanParser去完成这件事:至于componentScanParser是什么呢?下面有解释
        // 备注:这个方法虽然有返回值,但是其实内部都已经把Bean定义信息加入到工厂里面去了
        Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
        // Check the set of scanned definitions for any further config classes and parse recursively if needed
        //这里面有一个重要的一步,还需要把每个Bean检测一遍。因为Scan出来的Bean,还有可能是@Configuration的,或者标注有@Import等等一些列注解的,因此需要再次交给parse一遍,防止疏漏
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
          BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
          if (bdCand == null) {
            bdCand = holder.getBeanDefinition();
          }
          if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
            parse(bdCand.getBeanClassName(), holder.getBeanName());
          }
        }
      }
    }
`componentScanParser`是什么呢?
  private final ComponentScanAnnotationParser componentScanParser;
  // 然后在ConfigurationClassParser的构造函数里,对他进行了初始化
  public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
      ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
      BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {
    this.metadataReaderFactory = metadataReaderFactory;
    this.problemReporter = problemReporter;
    this.environment = environment;
    this.resourceLoader = resourceLoader;
    this.registry = registry;
    // 这里对componentScanParser 进行初始化。持有环境、ResourceLoader、名字生成器、注册器的引用
    this.componentScanParser = new ComponentScanAnnotationParser(environment, resourceLoader, componentScanBeanNameGenerator, registry);
    // 初始化condition的计算器,持有注册器、环境、ResourceLoader的引用
    this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
  }

ComponentScanAnnotationParser:准们解析@ComponentScan的类


从访问权限(Default)来看,它被定义为Spring的一个自己内部使用的工具类。它的惟一一个public方法为:parse()


  public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    // 第一句代码就看出了端倪:原来扫描的工作最终还是委托给了ClassPathBeanDefinitionScanner去做
    // 注意:useDefaultFilters这个值特别的重要,能够解释伙伴们为何要配置的原因~~~下面讲解它的时候会有详解
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    // BeanName的生成器,我们可以单独制定。若不指定(大部分情况下都不指定),那就是默认的AnnotationBeanNameGenerator
    // 它的处理方式是:类名首字母小写
    Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
    boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
    scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
        BeanUtils.instantiateClass(generatorClass));
    // 这两个属性和scope代理相关的,这里略过,使用较少
    ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
    if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
      scanner.setScopedProxyMode(scopedProxyMode);
    } else {
      Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
      scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
    }
    // 控制去扫描哪些.clsss文件的模版。一般不设置   默认值为:**/*.class  全扫嘛
    scanner.setResourcePattern(componentScan.getString("resourcePattern"));
    // includeFilters和excludeFilters算是内部处理最复杂的逻辑了,但还好,对使用者是十分友好的
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
      for (TypeFilter typeFilter : typeFiltersFor(filter)) {
        scanner.addIncludeFilter(typeFilter);
      }
    }
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
      for (TypeFilter typeFilter : typeFiltersFor(filter)) {
        scanner.addExcludeFilter(typeFilter);
      }
    }
    // Spring4.1后出现的。哪怕我是扫描的Bean,也支持懒加载啦
    boolean lazyInit = componentScan.getBoolean("lazyInit");
    if (lazyInit) {
      scanner.getBeanDefinitionDefaults().setLazyInit(true);
    }
    // 这里属于核心逻辑,核心逻辑,核心逻辑
    Set<String> basePackages = new LinkedHashSet<>(); 
    String[] basePackagesArray = componentScan.getStringArray("basePackages");
    // Spring在此处有强大的容错处理。瑞然他是支持数组的,但是它这里也容错处理:支持,;换行等的符号分隔处理
    // 并且,并且更强大的地方在于:它支持${...}这种占位符的形式,非常的强大。我们可以动态的进行扫包了~~~~~厉害了我的哥
    for (String pkg : basePackagesArray) {
      String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      Collections.addAll(basePackages, tokenized);
    }
    //basePackageClasses有时候也挺好使的。它可以指定class,然后这个class所在的包(含子包)都会被扫描
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    // 如果我们没有指定此值,它会取当前配置类所在的包  比如SpringBoot就是这么来干的
    if (basePackages.isEmpty()) {
      basePackages.add(ClassUtils.getPackageName(declaringClass));
    }
    scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
      @Override
      protected boolean matchClassName(String className) {
        return declaringClass.equals(className);
      }
    });
    // 最后,最后,把@ComponentScan的属性都解析好了,就交给scanner去扫描吧  
    // 因为都准备好了,所以这里直接调用的doScan()哦~
    return scanner.doScan(StringUtils.toStringArray(basePackages));
  }


这里再次提示一下:@ComponentScan(basePackages = "${xxx}") 是支持这么写的 可谓非常暖心。


ClassPathBeanDefinitionScanner 类路径下的Bean定义扫描器


参考博文:【小家Spring】Spring容器加载Bean定义信息的两员大将:AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner


其实这个类我们早就接触过了,前面讲到AnnotationConfigApplicationContext容器初始化的时候,就讲到了它,它可以去扫描特定类型的组件。它有它自己的默认扫描策略。它继承自ClassPathScanningCandidateComponentProvider:


下面我们先从它的构造函数说起:


  public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
      Environment environment, @Nullable ResourceLoader resourceLoader) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    this.registry = registry;
    // 我们发现这个useDefaultFilters特别重要,默认情况下他是true,只会扫描默认的注解们
    // 至于是哪些注解,看下面
    if (useDefaultFilters) {
      registerDefaultFilters();
    }
    setEnvironment(environment);
    setResourceLoader(resourceLoader);
  }
//registerDefaultFilters();
  protected void registerDefaultFilters() {
    // @Component显然默认是必须要扫描的嘛
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    // 下面两个是,默认也支持JSR-250规范的`@ManagedBean`和JSR-330规范的@Named注解(但是我并不建议大家使用,还是使用Spring源生的吧)
    try {
      this.includeFilters.add(new AnnotationTypeFilter(
          ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
      logger.debug("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 {
      this.includeFilters.add(new AnnotationTypeFilter(
          ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
      logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
      // JSR-330 API not available - simply skip.
    }
  }


需要说明的是:@Controller、@ControllerAdvice、@Service、@Repository都属于@Component范畴。当然还有@Configuration它也是


相关文章
|
22天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
65 2
|
22天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
40 2
|
22天前
|
前端开发 Java 开发者
Spring MVC中的控制器:@Controller注解全解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序控制层的核心。它不仅简化了控制器的定义,还提供了灵活的请求映射和处理机制。本文将深入探讨`@Controller`注解的用法、特点以及在实际开发中的应用。
55 0
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
71 2
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
76 0
|
2月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
62 0
|
2月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
66 0
|
2月前
|
安全 Java 程序员
Collection-Stack&Queue源码解析
Collection-Stack&Queue源码解析
86 0
|
16天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
20天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
50 12

推荐镜像

更多
下一篇
DataWorks