Spring创建BeanDefinition之路径扫描详解

 更新时间:2025年04月07日 09:50:26   作者:程序员侠客行  
这篇文章主要介绍了Spring创建BeanDefinition之路径扫描方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

一、从示例开始

当我们创建AnnotationConfigApplicationContext对象时,Spring底层到底做了些什么?

来看下面示例。

package com.xiakexing;

import com.xiakexing.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		UserService userService = context.getBean("userService", UserService.class);
		userService.test();
    }
}
package com.xiakexing;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(value = "com.xiakexing")
public class AppConfig {

}
package com.xiakexing.service;

import org.springframework.stereotype.Component;

@Component
public class UserService {

    public void test() {
        System.out.println("hello spring");
    }
}

我们猜测这几行代码执行逻辑:

  • new AnnotationConfigApplicationContext(AppConfig.class)时,从AppConfig类解析扫描路径即@ComponentScan;
  • 遍历扫描路径下的所有Java类,如果某个类上有@Component、@Service等注解,Spring就为这个类创建BeanDefinition,保存到Map中,比如Map<String, Class>,key是根据规则生成的beanName,value就是当前类的class对象。
  • context.getBean("userService")时,Spring根据beanName找到类的class对象,反射调用构造器创建对象。

带着上面的猜想,我们来看看源码。本文暂且关注路径扫描的实现,随后的文章将讲解BeanDefinition的创建过程。

二、创建AnnotationConfigApplicationContext

构造方法的入参是componentClasses,即可以传入多个配置类。

this()中创建了AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner,将用于扫描指定路径下的类,创建BeanDefinition。

JFR 是 Java Flight Record (Java飞行记录),是JVM 内置的基于事件的JDK监控记录框架。StartupStep是Spring基于JFR对运行过程的监控,阅读源码时可忽略它。

注意,AnnotationConfigApplicationContext间接实现了BeanDefinitionRegistry接口,具备向容器中注册BeanDefinition的能力。

在创建ClassPathBeanDefinitionScanner对象时,指定了使用DefaultFilters:将扫描所有带有@Component注解的类。

总结:this()仅仅实例化了容器对象,创建了Reader、Scanner,用于解析类信息。

三、注册Configuration类

3.1 创建BeanDefinition

register(componentClasses),显然是将配置类注册到容器中。

来看AnnotatedBeanDefinitionReader#doRegisterBean的核心逻辑:

  • 为配置类创建AnnotatedGenericBeanDefinition对象;
  • 处理@Conditional,如果条件不满足,将舍弃这个类;
  • 给BeanDefinition对象属性赋值;
  • 生成beanName,解析@Lazy、@Primary、@DependsOn等注解;
  • 创建BeanDefinitionHolder对象,发起注册。

3.2 注册BeanDefinition

BeanDefinitionHolder类只是对BeanDefinition的包装,仅有三个属性:beanDefinition、beanName和aliases。

在BeanDefinitionReaderUtils#registerBeanDefinition中

最终会调用DefaultListableBeanFactory#registerBeanDefinition方法,执行这几行代码:

看到了BeanFactory的两个核心数据结构:

  • beanDefinitionMap保存了beanName与beanDefinition的映射;
  • beanDefinitionNames保存了所有的beanName

果然与我们当初的猜想一致。

至此,配置类已被添加到beanDefinitionMap中,可是@ComponentScan指定的包路径,在哪儿被处理了呢?

四、扫描包路径

先说结论:@ComponentScan包路径下的类,是在ClassPathBeanDefinitionScanner#scan中被处理的。

来看AnnotationConfigApplicationContext的另一个构造方法:入参就是指定包路径。

转调到ClassPathBeanDefinitionScanner#scan。

基于配置类创建AnnotationConfigApplicationContext时,是在哪儿调了scan()或doScan()呢?答案就在refresh()中。

4.1 BeanFactoryPostProcessor接口

先看类注释:

Factory hook that allows for custom modification of an application context's bean definitions, adapting the bean property values of the context's underlying bean factory.

工厂钩子,允许自定义修改应用程序上下文的bean定义,调整上下文的底层bean工厂的bean属性值。

A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances.

BeanFactoryPostProcessor可以与bean定义交互和修改,但不能与bean实例交互。

可见,该接口是BeanFactory的后置处理器,在创建Bean实例前,干涉BeanDefinition创建和更新。

仅有一个方法:

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

该接口有个重要的子接口BeanDefinitionRegistryPostProcessor,能够向容器注册更多的BeanDefinition。

Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in.

对标准BeanFactoryPostProcessor SPI的扩展,允许在常规BeanFactoryPostProcessor检测开始之前注册进一步的bean定义。

4.2 ConfigurationClassPostProcessor类

源码中,BeanDefinitionRegistryPostProcessor接口仅有唯一实现ConfigurationClassPostProcessor。

在ConfigurationClassPostProcessor#processConfigBeanDefinitions中,

检查已注册的每一个BeanDefinition,是否是候选配置类(或组件),满足以下任意条件即可:

  • 类上有@Configuration注解;
  • 类上有以下任意一个注解;

  • 类中有@Bean注解的方法;

得到Set<BeanDefinitionHolder> candidates后,会调用ConfigurationClassParser.parse()

接下来会遍历处理每一个候选类

在ConfigurationClassParser#doProcessConfigurationClass中,解析@ComponentScan、@ComponentScans注解;

  • 执行Filter逻辑后,得到basePackages路径集;
  • 调用ClassPathBeanDefinitionScanner#doScan,为路径下的Bean创建BeanDefinition,并注册到容器中。

关于doScan方法的详细逻辑,我们下一篇再看。

五、逻辑闭环

要用ConfigurationClassPostProcessor来处理配置类,Spring容器中就得先有该类的实例。那么,这个类是何时注册到容器中的?

答案就在new AnnotatedBeanDefinitionReader(this)中:

为ConfigurationClassPostProcessor创建BeanDefinition并注册。

当执行AbstractApplicationContext#refresh时,其中有一步是调用容器中BeanFactoryPostProcessor接口所有实现。

此时,ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry将被执行。

流程图

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SPRING IOC注入方式过程解析

    SPRING IOC注入方式过程解析

    这篇文章主要介绍了SPRING IOC注入方式过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • Java中将多个PDF文件合并为一个PDF的方法步骤

    Java中将多个PDF文件合并为一个PDF的方法步骤

    这篇文章主要给大家介绍了关于Java中将多个PDF文件合并为一个PDF的方法步骤, Java PDF合并是指将多个PDF文件合并成一个PDF文件的过程,需要的朋友可以参考下
    2023-09-09
  • 关于Spring Validation数据校检的使用流程分析

    关于Spring Validation数据校检的使用流程分析

    在实际项目中,对客户端传递到服务端的参数进行校验至关重要,SpringValidation提供了一种便捷的方式来实现这一需求,通过在POJO类的属性上添加检查注解,本文给大家介绍Spring Validation数据校检的使用流程,感兴趣的朋友一起看看吧
    2024-11-11
  • Java中实现线程间通信的实例教程

    Java中实现线程间通信的实例教程

    线程通信的目标是使线程间能够互相发送信号,另一方面线程通信使线程能够等待其他线程的信号,这篇文章主要给大家介绍了关于Java中实现线程间通信的相关资料,本文通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-09-09
  • 使用MyBatis返回其它类对象的字段处理

    使用MyBatis返回其它类对象的字段处理

    这篇文章主要介绍了使用MyBatis返回其它类对象的字段处理方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java使用POI从Excel读取数据并存入数据库(解决读取到空行问题)

    Java使用POI从Excel读取数据并存入数据库(解决读取到空行问题)

    有时候需要在java中读取excel文件的内容,专业的方式是使用java POI对excel进行读取,这篇文章主要给大家介绍了关于Java使用POI从Excel读取数据并存入数据库,文中介绍的办法可以解决读取到空行问题,需要的朋友可以参考下
    2023-12-12
  • 一天时间用Java写了个飞机大战游戏,朋友直呼高手

    一天时间用Java写了个飞机大战游戏,朋友直呼高手

    前两天我发现论坛有两篇飞机大战的文章异常火爆,但都是python写的,竟然不是我大Java,说实话作为老java选手,我心里是有那么一些失落的,今天特地整理了这篇文章,需要的朋友可以参考下
    2021-05-05
  • SpringBoot整合minio服务的示例代码

    SpringBoot整合minio服务的示例代码

    本文主要介绍了SpringBoot整合minio服务的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • IDEA检查项目的jdk版本需要看的地方

    IDEA检查项目的jdk版本需要看的地方

    这篇文章主要介绍了IDEA检查项目的jdk版本需要看的地方,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-06-06
  • 理解Java当中的回调机制(翻译)

    理解Java当中的回调机制(翻译)

    今天我要和大家分享一些东西,举例来说这个在JavaScript中用的很多。我要讲讲回调(callbacks)。你知道什么时候用,怎么用这个吗?你真的理解了它在java环境中的用法了吗?当我也问我自己这些问题,这也是我开始研究这些的原因
    2014-10-10

最新评论