你知道Spring是怎么解析配置类的吗?(1)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 你知道Spring是怎么解析配置类的吗?(1)

推荐阅读:

Spring官网阅读系列

彻底读懂Spring(一)读源码,我们可以从第一行读起

Spring执行流程图如下:

image.png

如果图片显示不清楚可以访问如下链接查看高清大图:


Spring执行流程图


这个流程图会随着我们的学习不断的变得越来越详细,也会越来越复杂,希望在这个过程中我们都能朝着精通Spring的目标不断前进!


在上篇文章我们学习了Spring中的第一行代码,我们已经知道了Spring中的第一行代码其实就是创建了一个AnnotatedBeanDefinitionReader对象,这个对象的主要作用就是注册bd(BeanDefinition)到容器中。并且在创建这个对象的过程中,Spring还为容器注册了开天辟地的几个bd,包括ConfigurationClassPostProcessor,AutowiredAnnotationBeanPostProcessor等等。


那么在本文中,我们就一起来看看Spring中的第二行代码又做了些什么?


Spring中的第二行代码


第二行代码在上面的流程图中已经标注的非常明白了,就是

this.scanner = new ClassPathBeanDefinitionScanner(this);

只是简单的创建了一个ClassPathBeanDefinitionScanner对象。**那么这个ClassPathBeanDefinitionScanner有什么作用呢?从名字上来看好像就是这个对象来完成Spring中的扫描的,真的是这样吗?**希望同学们能带着这两个问题往下看


ClassPathBeanDefinitionScanner源码分析


这个类名直译过来就是:类路径下的BeanDefinition的扫描器,所以我们就直接关注其扫描相关的方法,就是其中的doScan方法。其代码如下:

// 这个方法会完成对指定包名下的class文件的扫描
// basePackages:指定包名,是一个可变参数
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    for (String basePackage : basePackages) {    
        // 1.findCandidateComponents这个方法是实际完成扫描的方法,也是接下来我们要分析的方法
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {、
            // 上篇文章中我们已经分析过了,完成了@Scope注解的解析
            // 参考《彻底读懂Spring(一)读源码,我们可以从第一行读起》
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
        // 2.如果你对BeanDefinition有一定了解的话,你肯定会知道这个判断一定会成立的,这意味着        // 所有扫描出来的bd都会执行postProcessBeanDefinition方法进行一些后置处理      
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }                           
            if (candidate instanceof AnnotatedBeanDefinition) {  
    // 3. 是不是一个AnnotatedBeanDefinition,如果是的话,还需要进行额外的处理 
  AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
           // 4.检查容器中是否已经有这个bd了,如果有就不进行注册了                                 
            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;
}

上面这段代码主要做了四件事


  1. 通过findCandidateComponents方法完成扫描
  2. 判断扫描出来的bd是否是一个AbstractBeanDefinition,如果是的话执行postProcessBeanDefinition方法
  3. 判断扫描出来的bd是否是一个AnnotatedBeanDefinition,如果是的话执行processCommonDefinitionAnnotations方法
  4. 检查容器中是否已经有这个bd了,如果有就不进行注册了

接下来我们就一步步分析这个方法,搞明白ClassPathBeanDefinitionScanner到底能起到什么作用


1、通过findCandidateComponents方法完成扫描


findCandidateComponents方法源码如下:

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
        return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    }
    else {
        // 正常情况下都是进入这个判断,对classpath下的class文件进行扫描
        return scanCandidateComponents(basePackage);
    }
}
  • addCandidateComponentsFromIndex

不用过多关注这个方法。正常情况下Spring都是采用扫描classpath下的class文件来完成扫描,但是虽然基于classpath扫描速度非常快,但通过在编译时创建候选静态列表,可以提高大型应用程序的启动性能。在这种模式下,应用程序的所有模块都必须使用这种机制,因为当 ApplicationContext检测到这样的索引时,它将自动使用它而不是扫描类路径。

要生成索引,只需向包含组件扫描指令目标组件的每个模块添加附加依赖项即可:

Maven:

org.springframework spring-context-indexer 5.0.6.RELEASE true

大家有兴趣的话可以参考官网:https://docs.spring.io/spring/docs/5.1.14.BUILD-SNAPSHOT/spring-framework-reference/core.html#beans-scanning-index


这个依赖实在太大了,半天了拉不下来,我这里就不演示了


  • scanCandidateComponents(basePackage)

正常情况下我们的应用都是通过这个方法完成扫描的,其代码如下:

  private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
        // 用来存储返回的bd的集合
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
            // 拼接成这种形式:classpath*:com.dmz.spring
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
          resolveBasePackage(basePackage) + '/' + this.resourcePattern;
            // 获取到所有的class文件封装而成的Resource对象
      Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
            // 遍历得到的所有class文件封装而成的Resource对象
      for (Resource resource : resources) {
        if (traceEnabled) {
          logger.trace("Scanning " + resource);
        }
        if (resource.isReadable()) {
          try {
                        // 通过Resource构建一个MetadataReader对象,这个MetadataReader对象包含了对应class文件的解析出来的class的元信息以及注解元信息
            MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                        // 并不是所有的class文件文件都要被解析成为bd,只有被添加了注解(@Component,@Controller等)才是Spring中的组件
            if (isCandidateComponent(metadataReader)) {
                            // 解析元信息(class元信息以及注解元信息)得到一个ScannedGenericBeanDefinition
              ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
              sbd.setResource(resource);
              sbd.setSource(resource);
              if (isCandidateComponent(sbd)) {
                if (debugEnabled) {
                  logger.debug("Identified candidate component class: " + resource);
                }
                candidates.add(sbd);
              }
    // 省略多余的代码
    return candidates;
  }

在Spring官网阅读(一)容器及实例化 一文中,我画过这样一张图

微信图片_20221113150528.jpg

从上图中可以看出,java class + configuration metadata 最终会转换为一个BenaDefinition,结合我们上面的代码分析可以知道,java class + configuration metadata实际上就是一个MetadataReader对象,而转换成一个BenaDefinition则是指通过这个MetadataReader对象创建一个ScannedGenericBeanDefinition。


2、执行postProcessBeanDefinition方法

protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
    // 为bd中的属性设置默认值
    beanDefinition.applyDefaults(this.beanDefinitionDefaults);
    // 注解模式下这个值必定为null,使用XML配置时,
    if (this.autowireCandidatePatterns != null) {
        beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
    }
}
  // 设置默认值
public void applyDefaults(BeanDefinitionDefaults defaults) {
    setLazyInit(defaults.isLazyInit());
    setAutowireMode(defaults.getAutowireMode());
    setDependencyCheck(defaults.getDependencyCheck());
    setInitMethodName(defaults.getInitMethodName());
    setEnforceInitMethod(false);
    setDestroyMethodName(defaults.getDestroyMethodName());
    setEnforceDestroyMethod(false);
}

可以看出,postProcessBeanDefinition方法最主要的功能就是给扫描出来的bd设置默认值,进一步填充bd中的属性


3、执行processCommonDefinitionAnnotations方法


这句代码将进一步解析class上的注解信息,Spring在创建这个abd的信息时候就已经将当前的class放入其中了,所有这行代码主要做的就是通过class对象获取到上面的注解(包括@Lazy,@Primary,@DependsOn注解等等),然后将得到注解中对应的配置信息并放入到bd中的属性中


4、注册BeanDefinition


跟**彻底读懂Spring(一)读源码,我们可以从第一行读起**的注册逻辑是一样的


通过上面的分析,我们已经知道了ClassPathBeanDefinitionScanner的作用,毋庸置疑,Spring肯定是通过这个类来完成扫描的,但是问题是,Spring是通过第二步创建的这个对象来完成扫描的吗?我们再来看看这个ClassPathBeanDefinitionScanner的创建过程:

// 第一步
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
    this(registry, true);
}
// 第二步                         
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
    this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
}
// 第三步  
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
                                      Environment environment) {
    this(registry, useDefaultFilters, environment,
         (registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
}
// 第四步
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
                                      Environment environment, @Nullable ResourceLoader resourceLoader) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    this.registry = registry;
    if (useDefaultFilters) {
        // 注册默认的扫描过滤规则(要被@Component注解修饰)
        registerDefaultFilters();
    }
    setEnvironment(environment);
    setResourceLoader(resourceLoader);
}

在这个ClassPathBeanDefinitionScanner的创建过程中我们全程无法干涉,不能对这个ClassPathBeanDefinitionScanner进行任何配置。而我们在配置类上明明是可以对扫描的规则进行配置的,例如:

@ComponentScan(value = "com.spring.study.springfx.aop.service", useDefaultFilters = true,
               excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {IndexService.class}))

所以Spring中肯定不是使用在这里创建的这个ClassPathBeanDefinitionScanner对象。


实际上真正完成扫描的时机是在我们流程图中的3-5-1步。完成扫描这个功能的类就是我们在上篇文章中所提到的ConfigurationClassPostProcessor。接下来我们就通过这个类,看看Spring到底是如何完成的扫描,这也是本文重点想要说明的问题


相关文章
|
5天前
|
安全 Java 数据库连接
后端框架的学习----mybatis框架(3、配置解析)
这篇文章详细介绍了MyBatis框架的核心配置文件解析,包括环境配置、属性配置、类型别名设置、映射器注册以及SqlSessionFactory和SqlSession的生命周期和作用域管理。
后端框架的学习----mybatis框架(3、配置解析)
|
3天前
|
XML Java 数据格式
Spring Cloud全解析:注册中心之zookeeper注册中心
使用ZooKeeper作为Spring Cloud的注册中心无需单独部署服务器,直接利用ZooKeeper服务端功能。项目通过`spring-cloud-starter-zookeeper-discovery`依赖实现服务注册与发现。配置文件指定连接地址,如`localhost:2181`。启动应用后,服务自动注册到ZooKeeper的`/services`路径下,形成临时节点,包含服务实例信息。
|
4天前
|
负载均衡 网络协议 应用服务中间件
如何配置 DNS 轮询负载均衡以实现高可用性
如何配置 DNS 轮询负载均衡以实现高可用性
16 4
|
3天前
|
运维 Java Nacos
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
|
4天前
|
安全 Nacos 数据库
【技术安全大揭秘】Nacos暴露公网后被非法访问?!6大安全加固秘籍,手把手教你如何保护数据库免遭恶意篡改,打造坚不可摧的微服务注册与配置中心!从限制公网访问到启用访问控制,全方位解析如何构建安全防护体系,让您从此告别数据安全风险!
【8月更文挑战第15天】Nacos是一款广受好评的微服务注册与配置中心,但其公网暴露可能引发数据库被非法访问甚至篡改的安全隐患。本文剖析此问题并提供解决方案,包括限制公网访问、启用HTTPS、加强数据库安全、配置访问控制及监控等,帮助开发者确保服务安全稳定运行。
11 0
|
4天前
|
安全 Nacos 数据安全/隐私保护
【技术干货】破解Nacos安全隐患:连接用户名与密码明文传输!掌握HTTPS、JWT与OAuth2.0加密秘籍,打造坚不可摧的微服务注册与配置中心!从原理到实践,全方位解析如何构建安全防护体系,让您从此告别数据泄露风险!
【8月更文挑战第15天】Nacos是一款广受好评的微服务注册与配置中心,但其连接用户名和密码的明文传输成为安全隐患。本文探讨加密策略提升安全性。首先介绍明文传输风险,随后对比三种加密方案:HTTPS简化数据保护;JWT令牌减少凭证传输,适配分布式环境;OAuth2.0增强安全,支持多授权模式。每种方案各有千秋,开发者需根据具体需求选择最佳实践,确保服务安全稳定运行。
16 0
|
7天前
|
Java Spring
Spring Boot Admin 授权配置
Spring Boot Admin 授权配置
13 0
|
7天前
|
监控 Java Spring
Spring Boot Admin 配置应用
Spring Boot Admin 配置应用
15 0
|
10天前
|
安全 关系型数据库 MySQL
"深度解析:MySQL密码修改与远程登录配置全攻略,保障数据库安全与灵活访问"
【8月更文挑战第9天】MySQL是广受青睐的开源关系型数据库系统,其安全性和易用性对DBA和开发者至关重要。本文通过实例解析MySQL中用户密码更新及远程登录配置,确保数据库安全访问与高效管理。首先介绍如何分步修改密码,包括登录MySQL、选择数据库、使用`ALTER USER`命令更新密码,并刷新权限。接着,指导如何配置远程访问,涉及调整MySQL监听地址、授权用户远程登录、检查网络设置及测试远程连接。遵循这些步骤,可强化数据库安全性并实现灵活管理。
45 0
|
15天前
|
XML Java 数据库连接
深入解析 Spring 配置文件:从基础到高级
【8月更文挑战第3天】Spring配置文件是构建与管理Spring应用的核心,它涵盖了从基础到高级的各种配置技巧。基础配置采用`.xml`格式定义Bean及其依赖;中级配置包括设置Bean作用域及引入属性文件;高级配置则涉及AOP、事务管理和与其他框架的整合。熟练掌握这些配置能帮助开发者构建出更为灵活且易维护的应用系统。

热门文章

最新文章

推荐镜像

更多