SpringBoot的组件扫描是基于Spring @ComponentScan注解实现的,该注解使用basePackages和basePackageClasses配置扫描的包,如果未配置这两个参数,Spring将扫描该配置类所属包下面的组件。

在服务启动时,将使用ConfigurationClassPostProcessor扫描当前所有的BeanDefinition,解析Configuration类,如果Configuration类标注了ComponentScan注解,将获取basePackages和basePackageClasses配置并扫描对应的包下面的组件。

ConfigurationClassPostProcessor类

实现了BeanDefinitionRegistryPostProcessor接口,在Spring启动的invokeBeanFactoryPostProcessors阶段被调用。

BeanDefinitionRegistryPostProcessor接口

继承BeanFactoryPostProcessor接口,允许在常规BeanFactoryPostProcessor调用之前注册更多的BeanDefinition。特别是,这些BeanDefinition反过来可以定义BeanFactoryPestProcessor实例。

  1. public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
  2. /**
  3. * Modify the application context's internal bean definition registry after its
  4. * standard initialization. All regular bean definitions will have been loaded,
  5. * but no beans will have been instantiated yet. This allows for adding further
  6. * bean definitions before the next post-processing phase kicks in.
  7. */
  8. void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
  9. }

postProcessBeanDefinitionRegistry实现

在ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry实现中:

  1. 扫描当前所有的BeanDefinition,找出所有的Configuration类
  2. 使用ConfigurationClassParser解析所有的Configuration类并找出需要注册的组件
    • 解析Component注解
    • 解析PropertySource注解
    • 解析ComponentScan注解
    • 解析Bean注解
  3. 将解析出来的组件注册到Spring容器

解析ComponentScan注解

这里使用到了ComponentScanAnnotationParser类。专门用来解析@ComponentScan注解。

ComponentScanAnnotationParser类

  1. public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
  2. ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
  3. componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
  4. Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
  5. boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
  6. scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
  7. BeanUtils.instantiateClass(generatorClass));
  8. ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
  9. if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
  10. scanner.setScopedProxyMode(scopedProxyMode);
  11. } else {
  12. Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
  13. scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
  14. }
  15. scanner.setResourcePattern(componentScan.getString("resourcePattern"));
  16. for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
  17. for (TypeFilter typeFilter : typeFiltersFor(filter)) {
  18. scanner.addIncludeFilter(typeFilter);
  19. }
  20. }
  21. for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
  22. for (TypeFilter typeFilter : typeFiltersFor(filter)) {
  23. scanner.addExcludeFilter(typeFilter);
  24. }
  25. }
  26. boolean lazyInit = componentScan.getBoolean("lazyInit");
  27. if (lazyInit) {
  28. scanner.getBeanDefinitionDefaults().setLazyInit(true);
  29. }
  30. // 解析basePackages属性
  31. Set<String> basePackages = new LinkedHashSet<>();
  32. String[] basePackagesArray = componentScan.getStringArray("basePackages");
  33. for (String pkg : basePackagesArray) {
  34. String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
  35. ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
  36. Collections.addAll(basePackages, tokenized);
  37. }
  38. // 解析basePackageClasses属性
  39. for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
  40. basePackages.add(ClassUtils.getPackageName(clazz));
  41. }
  42. // 如果上面两个属性都没有配置,则默认扫描Configuration类所在包及其子包
  43. if (basePackages.isEmpty()) {
  44. basePackages.add(ClassUtils.getPackageName(declaringClass));
  45. }
  46. scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
  47. @Override
  48. protected boolean matchClassName(String className) {
  49. return declaringClass.equals(className);
  50. }
  51. });
  52. return scanner.doScan(StringUtils.toStringArray(basePackages));
  53. }

doScan方法

  1. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  2. Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  3. for (String basePackage : basePackages) {
  4. // 查找basePackage下面的类文件,使用ASM解析封装成BeanDefinition
  5. Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
  6. for (BeanDefinition candidate : candidates) {
  7. ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
  8. candidate.setScope(scopeMetadata.getScopeName());
  9. String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
  10. if (candidate instanceof AbstractBeanDefinition) {
  11. postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
  12. }
  13. if (candidate instanceof AnnotatedBeanDefinition) {
  14. AnnotationConfigUtils
  15. .processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
  16. }
  17. if (checkCandidate(beanName, candidate)) {
  18. BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
  19. definitionHolder = AnnotationConfigUtils
  20. .applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  21. beanDefinitions.add(definitionHolder);
  22. registerBeanDefinition(definitionHolder, this.registry);
  23. }
  24. }
  25. }
  26. return beanDefinitions;
  27. }

springboot启动流程 (2) 组件扫描的更多相关文章

  1. SpringBoot启动流程解析

    写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...

  2. SpringBoot启动流程及其原理

    Spring Boot.Spring MVC 和 Spring 有什么区别? 分别描述各自的特征: Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等:但他们的 ...

  3. SpringBoot 启动流程

    SpringBoot 启动流程 加载 resources/META-INF/spring.factories 中配置的 ApplicationContextInitializer 和 Applicat ...

  4. SpringBoot源码学习3——SpringBoot启动流程

    系列文章目录和关于我 一丶前言 在 <SpringBoot源码学习1--SpringBoot自动装配源码解析+Spring如何处理配置类的>中我们学习了SpringBoot自动装配如何实现 ...

  5. SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  6. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  7. SpringBoot启动流程分析(六):IoC容器依赖注入

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  8. SpringBoot启动流程分析(一):SpringApplication类初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  9. SpringBoot启动流程分析(二):SpringApplication的run方法

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  10. SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

随机推荐

  1. 数字孪生技术结合GIS系统能在农业领域作出什么改变?

    数字孪生技术和地理信息系统(GIS)是两个独立但高度互补的领域,它们的结合在农业领域具有巨大的潜力,可以带来巨大的改变.在这篇文章中,我们将讨论数字孪生技术和GIS系统如何协同作用,为农业带来创新和可 ...

  2. Go 语言为什么很少使用数组?

    大家好,我是 frank,「Golang 语言开发栈」公众号作者. 01 介绍 在 Go 语言中,数组是一块连续的内存,数组不可以扩容,数组在作为参数传递时,属于值传递. 数组的长度和类型共同决定数组 ...

  3. python3发送Gratuitous ARP更新vip绑定关系

    操作系统 :CentOS 7.6_x64 Python版本:3.9.12 FreeSWITCH版本 :1.10.9 高可用场景下,vip切换完成后需要发送arp广播更新ip和mac地址的绑定关系,如果 ...

  4. 基于Docker 部署 Seafile+OnlyOffice+Wiki插件

    原文:基于 Docker 部署 SeafilePro + OnlyOffice(CentOS版) 官方文档:用 Docker 部署 Seafile 服务 CentOS 服务器 基于 Docker 部署 ...

  5. 如何在GitHub正确提PR(Pull Requests),给喜欢的开源项目贡献代码

    最好的中文TTS项目Bert-vits2更新了中文特化分支,但可能由于时间仓促,代码中存在不少的bug,作为普通用户,有的时候也想为自己喜欢的开源项目做一点点贡献,帮助作者修改一些简单的bug,那么该 ...

  6. [极客大挑战 2019]Havefun 1

    [极客大挑战 2019]Havefun 1 一,审题,观察题目信息和知识点 观察题目,没发现有效信息 ​ F12打开源代码,发现有一个GET传输. ​ 知识点 GET方法的数据传输是通过URL传输的, ...

  7. Feign源码解析4:调用过程

    背景 前面几篇分析了Feign的初始化过程,历经艰难,可算是把@FeignClient注解的接口对应的代理对象给创建出来了.今天看下在实际Feign调用过程中的一些源码细节. 我们这里Feign接口如 ...

  8. 关于向上转型以及向下转型、instanceof的一些应用。

    一.前言 在Java编程中,我们常常遇到各种类型转换的情况,尤其是在处理继承关系的类时.本文将深入探讨Java中的向上转型(upcasting).向下转型(downcasting)以及instance ...

  9. 《架构整洁之道》学习笔记 Part 1 概述

    本书主题 介绍什么是优秀的软件架构,以提高软件架构质量 介绍系统架构的各种属性与成本和生产力的关系,以采用好的设计和架构以便减少构建成本 好的软件架构可以带来什么? 大大节省软件项目构建与维护的人力成 ...

  10. 华为云API Explorer重磅推出API编排,开发者0代码高效构建工作流

    本文分享自华为云社区<华为云API Explorer重磅推出API编排,开发者0代码高效构建工作流(体验用户招募中)>,作者:华为云PaaS服务小智. 打破传统开发模式,API编排应运而生 ...