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

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

ConfigurationClassPostProcessor类

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

BeanDefinitionRegistryPostProcessor接口

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

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

	/**
* Modify the application context's internal bean definition registry after its
* standard initialization. All regular bean definitions will have been loaded,
* but no beans will have been instantiated yet. This allows for adding further
* bean definitions before the next post-processing phase kicks in.
*/
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

postProcessBeanDefinitionRegistry实现

在ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry实现中:

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

解析ComponentScan注解

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

ComponentScanAnnotationParser类

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass)); 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));
} scanner.setResourcePattern(componentScan.getString("resourcePattern")); 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);
}
} boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
} // 解析basePackages属性
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);
}
// 解析basePackageClasses属性
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 如果上面两个属性都没有配置,则默认扫描Configuration类所在包及其子包
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
} scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
return scanner.doScan(StringUtils.toStringArray(basePackages));
}

doScan方法

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 查找basePackage下面的类文件,使用ASM解析封装成BeanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils
.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils
.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}

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. 小傅哥自研插件,为开发提效80%,已经有8.1k安装量!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 哈喽,大家好我是技术UP主小傅哥. 如果你担心维护成本和性能考量,不想使用 BeanUtils ...

  2. 使用kubeadm在Centos8上部署kubernetes1.18

    // 查看系统版本 cat /etc/centos-release CentOS Linux release 8.1.1911 (Core) // 如果系统环境为8.0(云服务器默认最大安装环境为8. ...

  3. React 类组件转换为函数式

    函数式的 React 组件更加现代,并支持有用的 hooks,现在流行把旧式的类组件转换为函数式组件.这篇文章总结了转换的一些通用的步骤和陷阱. 通用替换 定义 从 class (\w+) exten ...

  4. C#数据结构与算法系列(十七):时间复杂度(下)

    1.常见的时间复杂度 常数阶:O(1) 对数阶:O(log2n) 线性阶:O(n) 线性对数阶:O(nlog2n) 平方阶:O(n^2) 立方阶:O(n^3) k次方阶:O(2^n) 常见的算法时间复 ...

  5. C++产生N以内的随机整数

    C++产生N(这里N=100)以内的随机整数的例子: #include <iostream> #include <ctime> using namespace std; int ...

  6. react+echarts出现“There is a chart instance already initialized on the dom.”

    写了一个关于echatrs组件,报错dom重复 配置信息从props拿 let chart; useEffect(() => { if (chart) { updateChartView(); ...

  7. 神经网络基础篇:详解向量化逻辑回归(Vectorizing Logistic Regression)

    向量化逻辑回归 讨论如何实现逻辑回归的向量化计算.这样就能处理整个数据集,甚至不会用一个明确的for循环就能实现对于整个数据集梯度下降算法的优化 首先回顾一下逻辑回归的前向传播步骤.所以,如果有 \( ...

  8. JavaScript回调函数的高手指南

    摘要:本文将会解释回调函数的概念,同时帮你区分两种回调:同步和异步. 回调函数是每个前端程序员都应该知道的概念之一.回调可用于数组.计时器函数.promise.事件处理中. 本文将会解释回调函数的概念 ...

  9. appuploader不是开发者账号

    Appuploader是一款可以帮助开发者上传iOS应用到Apple App Store的工具.很多开发者都知道,在上传应用到App Store之前,需要创建开发者账号并获得苹果官方的认证才能进行上传 ...

  10. 一文详解特权访问管理(PAM)

    什么是特权访问管理(PAM)? 特权访问管理(Privileged Access Management)是一个包含网络安全策略和访问管理工具的解决方案,用于控制和监管和保护具有特权访问权限的用户.在之 ...