3. Spring源码篇之ComponentScan

简介

前面介绍了scanner可以扫描某个包路径下的所有bean,我们最常用的也是通过ComponentScan指定包路径去扫描,在SpringBoot中也是如此,本文将介绍spring扫描的逻辑

BeanDefinitionRegistryPostProcessor

知道了ComponentScan的作用,那么spring什么时候开始解析ComponentScan去扫描bean呢

这个时候就要知道spring的其中一个生命周期 BeanDefinitionRegistryPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

该生命周期可以说是最早的一个,还在BeanFactoryPostProcessor#postProcessBeanFactory之前执行

从接口提供的方法就很明确,可以自己去注册一些bean

那么在该接口实现类里面就可以将最开始注册的AppConfig类拿出来,确定有ComponentScan注解,就可以拿到路径去扫描,最终注册bean

ConfigurationClassPostProcessor

ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor

在spring中通过该类进行配置类的解析,并且注册bean,只不过在spring中,标名是一个配置类的注解多种多样 ComponentScan 也只是其中一个

本文主要讲 ComponentScan 其它配置类后面讲

注册

源码链路

  1. AnnotationConfigApplicationContext(Class<?>… componentClasses) # 进入this()

  2. AnnotationConfigApplicationContext()

  3. AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry)

  4. AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment)

  5. AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry registry)

  6. AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source)

可以看到这么一行 注册ConfigurationClassPostProcessor

if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}

获取注册的ConfigurationClassPostProcessor

进入refresh方案,spring最重要的一个方法,整个生命周期都在这

  1. AnnotationConfigApplicationContext(Class<?>… componentClasses) # 进入this()
  2. AbstractApplicationContext#refresh()
  3. AbstractApplicationContext.invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory); 处理
    BeanFactory的后置处理器
  4. PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory,
    List beanFactoryPostProcessors)

会看到如下代码

// 获取BeanDefinitionRegistryPostProcessor,一般来说就只有一个 ConfigurationClassPostProcessor
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}
}
// 排个序
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
// 执行 BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
currentRegistryProcessors.clear();// 扫描完之后还有可能生成BeanFactoryPostProcessor
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {// 过滤之前执行过的BeanFactoryPostProcessorif (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));processedBeans.add(ppName);}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
currentRegistryProcessors.clear();

以上就是获取 ConfigurationClassPostProcessor 到执行它的 postProcessBeanDefinitionRegistry方法的
逻辑,通过beanFactory.getBeanNamesForType 获取 BeanDefinitionRegistryPostProcessor

将获取到的BeanDefinitionRegistryPostProcessor 排个序,通过@Order注解或者@javax.annotation.Priority注解确定排序

遍历执行postProcessBeanDefinitionRegistry方法

解析 ComponentScan

那么在ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry又是怎么解析ComponentScan注册bean呢

  1. ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
  2. ConfigurationClassPostProcessor#processConfigBeanDefinitions(BeanDefinitionRegistry registry)
    在这里会有一个检查,判断是不是配置类 ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef,
    this.metadataReaderFactory)
    那么什么是配置类呢,
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {// getFactoryMethodName表示是@Bean方式注入的bean,这种不作为配置类String className = beanDef.getBeanClassName();// 注解信息,通过MetadataReader获取(ASM技术) 并没有加载类AnnotationMetadata metadata;// 如果AnnotatedBeanDefinition,则直接取AnnotationMetadataif (beanDef instanceof AnnotatedBeanDefinition && className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();}// 如果是AbstractBeanDefinition,则解析beanClass得到AnnotationMetadataelse if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {metadata = AnnotationMetadata.introspect(beanClass);}else {// ASM技术,这里其实也是spring提供的一个工具,我们也可以使用,后续文章介绍MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);metadata = metadataReader.getAnnotationMetadata();}Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());// 存在@Configuration,并且proxyBeanMethods不为false,就是配置类 配置类型为Fullif (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {// 设置属性configurationClass,表示是配置类beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);}// 存在@Configuration,并且proxyBeanMethods为false时,也是配置类配置类型为lite// 不存在@Configuration,存在@Component、@ComponentScan、@Import、@ImportResource中的某一个,也是配置类 配置类型为lite// 有@Bean标注的方法也是配置类else if (config != null || isConfigurationCandidate(metadata)) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);}else {return false;}// It's a full or lite configuration candidate... Let's determine the order value, if any.Integer order = getOrder(metadata);if (order != null) {// 配置类也可以排序,使用 Order注解beanDef.setAttribute(ORDER_ATTRIBUTE, order);}return true;}
private static final Set<String> candidateIndicators = new HashSet<>(8);static {candidateIndicators.add(Component.class.getName());candidateIndicators.add(ComponentScan.class.getName());candidateIndicators.add(Import.class.getName());candidateIndicators.add(ImportResource.class.getName());}public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {// 接口不管if (metadata.isInterface()) {return false;}// 只要存在@Component、@ComponentScan、@Import、@ImportResource四个中的一个,就是lite配置类for (String indicator : candidateIndicators) {if (metadata.isAnnotated(indicator)) {return true;}}// Finally, let's look for @Bean methods...// 只要存在@Bean注解了的方法,就是lite配置类return hasBeanMethods(metadata);
}

根据上面的代码逻辑分析,拥有@Configuration @Component、@ComponentScan、@Import、@ImportResource
或者方法有@Bean注解,那么就是一个配置类,继续后面代码分析

  1. parser.parse(candidates); candidates 就是找出的所有配置累会进入 ConfigurationClassParser#parse(AnnotationMetadata
    metadata, String beanName)
  2. ConfigurationClassParser#processConfigurationClass(ConfigurationClass configClass, Predicate filter)
  3. ConfigurationClassParser#doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass,
    Predicate filter)

其它配置类先不看,那么会看到会看到如下代码

// 查看是否有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 immediatelySet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (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());}}}
}

查看该Bean的有没有ComponentScans或者ComponentScan注解,如果有那么使用ComponentScanAnnotationParser解析

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);// bean的名字生成器,默认是首字母小写的类名,可以自己定义其它规则,比如类的全限定名Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);// 默认为AnnotationBeanNameGeneratorscanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :BeanUtils.instantiateClass(generatorClass));// 默认就是扫描包下面的class文件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);}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));}scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {@Overrideprotected boolean matchClassName(String className) {// 排除掉自己return declaringClass.equals(className);}});// 开始扫描,注册beanreturn scanner.doScan(StringUtils.toStringArray(basePackages));}

以上就是如何解析ComponentScan的逻辑,解析完后,开始扫描,进行BeanDefinition注册,注意是BeanDefinition注册,还不是实例化bean

  1. ClassPathBeanDefinitionScanner#doScan
  2. ClassPathBeanDefinitionScanner#registerBeanDefinition
  3. BeanDefinitionReaderUtils#registerBeanDefinition
  4. DefaultListableBeanFactory#registerBeanDefinition

最终存放在 beanDefinitionMap beanDefinitionNames 记住这两个属性,特别重要

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

private volatile List beanDefinitionNames = new ArrayList<>(256);

以上就是通过ComponentScan注解注册Bean的逻辑了,其它配置类以及实例化bean的逻辑后面文章在介绍


欢迎关注,学习不迷路!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/145994.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Word文档处理:用Python轻松提取Word文档图文数据

将内容从Word文档中提取出来可以方便我们对其进行其他操作&#xff0c;如储将内容存在数据库中、将内容导入到其他程序中、用于AI训练以及制作其他文档等。使用Spire.Doc for Python提供了一个简单的方法直接提取Word文档中的文本内容&#xff0c;包括文本和图片&#xff0c;而…

Mybatisplus集成springboot完成分页查询

今天解决的是&#xff1a;Mybatisplus集成pringboot完成分页功能 &#x1f6f4;&#x1f6f4;&#x1f6f4; 之前一直用Pagehelper&#xff0c;迫于无奈pagehelper与springboot冲突太多&#xff0c;就改了MP自带的分页 &#x1f388;引入依赖 引入mybatisplus依赖 <depen…

【Linux】重定向|重新理解Linux下一切皆文件

文章目录 一、什么是重定向输出重定向的原理认识一下输出重定向的系统调用输出重定向的另外写法 二、浅谈输入重定向三、重定向和进程替换有冲突吗四、Linux下一切皆文件总结 一、什么是重定向 理解重定向之前&#xff1a;先理解一个叫做文件描述符的具体操作。 文件描述符&a…

【Qt之QWizardPage】使用

介绍 QWizardPage类是向导页面的基类。 QWizard表示一个向导。每个页面都是一个QWizardPage。当创建自己的向导时&#xff0c;可以直接使用QWizardPage&#xff0c;也可以子类化它以获得更多控制。 页面具有以下属性&#xff0c;由QWizard呈现&#xff1a;a title&#xff0c;…

JVM虚拟机-虚拟机执行子系统-第6章 字节码指令

字节码指令 Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字&#xff08;称为操作码&#xff0c;Opcode&#xff09;以及跟随其后的零至多个代表此操作所需的参数&#xff08;称为操作数&#xff0c;Operand&#xff09;构成。 字节码与数据类型 在Java虚拟…

uni-app 蓝牙打印, CPCL指令集使用

先上代码: GitHub - byc233518/uniapp-bluetooth-printer-demo: 使用uniApp 连接蓝牙打印机 Demo, CPCL 指令简单实用示例 (内含 芝珂,佳博,精臣 多个厂家指令集使用文档) 文件结构: ├── App.vue ├── CPCL 指令手册.pdf // 指令集参考手册 ├── LICENSE ├── R…

如何解决小程序异步请求问题

小程序异步请求问题指的是在小程序中进行异步请求时可能会出现的问题&#xff0c;比如请求失败、请求超时等。以下是一些解决方案&#xff1a; 检查网络连接&#xff1a;首先需要确保网络连接正常&#xff0c;只有网络连接正常时才能正常进行异步请求。 检查请求参数&#xff…

基于探路者算法优化概率神经网络PNN的分类预测 - 附代码

基于探路者算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于探路者算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于探路者优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

html所有标签和DOCTYPE的总结

一、DOCTYPE 1. 意义 DOCTYPE是一种标准通用标记语言的文档类型声明&#xff0c;告诉标准通用标记语言解析器它应该使用什么样的文档类型定义来解析文档。 2. 应用 现在&#xff0c;我们需要告诉标准通用标记语言解析器&#xff0c;我们接下去要用html来编写代码了。 <…

建造者模式(创建型)

目录 一、前言 二、建造者模式 三、链式编程实现建造者模式 四、总结 一、前言 当我们开发一个软件应用时&#xff0c;我们通常需要创建各种对象。有些对象是简单的&#xff0c;可以直接实例化&#xff0c;但有些对象则比较复杂&#xff0c;需要多个步骤才能创建完成。这时…

【备忘】websocket学习之挖坑埋自己

背景故事 以前没有好好学习过websocket&#xff0c;只知道它有什么用途&#xff0c;也知道是个好东西&#xff0c;平时在工作中没用过&#xff0c;所以对它并不知所以然。如今要做个自己的项目&#xff0c;要在付款的时候实时播报声音。自己是个开发者&#xff0c;也不想用别人…

解决升级docker导致的k8s崩溃问题

最近由于安装harbor升级了docker&#xff0c;然后发现k8s集群就启动不了。 查看kubelet日志发现&#xff1a;直接连不上apiserver了&#xff0c;直接connection refused 然后尝试启动apiserver容器直接报错&#xff1a;Error response from daemon: unknown or invalid runtim…

【原创】java+swing+mysql校园活动管理系统设计与实现

前言&#xff1a; 本文介绍了一个校园活动管理系统的设计与实现。该系统基于JavaSwing技术&#xff0c;采用C/S架构&#xff0c;使用Java语言开发&#xff0c;以MySQL作为数据库。系统实现了活动发布、活动报名、活动列表查看等功能&#xff0c;方便了校园活动的发布和管理&am…

list.toArray

直接去看原文 原文链接:List的toArray()方法_list.toarray-CSDN博客 -------------------------------------------------------------------------------------------------------------------------------- toArray()介绍 toArray()方法是List接口中提供的方法&#xff…

线性表的概念

目录 1.什么叫线性表2.区分线性表的题 1.什么叫线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串… 线性表在逻辑上是…

餐厅订座预约小程序的效果如何

市场中无论哪种城市&#xff0c;餐厅非常多&#xff0c;一条不长的商业街&#xff0c;汇聚着数家餐饮品牌&#xff0c;且相互间竞争激烈&#xff0c;并且各个商家都希望用成本低高效率的方法引流及转化。 随着互联网深入各个行业&#xff0c;传统餐饮行业经营痛点不少。 传统餐…

前端---CSS的样式汇总

文章目录 CSS的样式元素的属性设置字体设置文字的粗细设置文字的颜色文本对齐文本修饰文本缩进行高设置背景背景的颜色背景的图片图片的属性平铺位置大小 圆角矩形 元素的显示模式行内元素和块级元素的转化弹性布局水平方向排列方式&#xff1a;justify-content垂直方向排序方式…

Abp6.0 使用 appsettings.json配置Serilog.Sinks.MariaDB

Abp6.0中已经启用Serilog,使用Serilog.Sinks.MariaDB包可以保存到MariaDB&#xff0c;mysql中 一种做法是在var loggerConfiguration new LoggerConfiguration( )后使用WriteTo.MariaDB扩展方法来配置&#xff0c;这样在代码中配置不够灵活&#xff0c;修改起来也不方便 其实…

mysql查看回滚记录

1、查看MySQL的回滚记录&#xff0c;先确认MySQL数据库的版本。MySQL的回滚记录功能从版本5.6.5开始引入&#xff0c;如果数据库版本较低&#xff0c;则无法使用该功能。 2、找到mysql&#xff1a;where mysql 开启回滚日志功能&#xff0c;在MySQL的配置文件&#xff08;my.…

基于深度学习的活体人脸识别检测算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1. 活体人脸识别检测算法概述 4.2. 深度学习在活体人脸识别检测中的应用 4.3. 算法流程 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 …