你知道Spring是怎么解析配置类的吗?(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到底是如何完成的扫描,这也是本文重点想要说明的问题


相关文章
|
2月前
|
Java 关系型数据库 MySQL
Spring Boot自动配置:魔法背后的秘密
Spring Boot 自动配置揭秘:只需简单配置即可启动项目,背后依赖“约定大于配置”与条件化装配。核心在于 `@EnableAutoConfiguration` 注解与 `@Conditional` 系列条件判断,通过 `spring.factories` 或 `AutoConfiguration.imports` 加载配置类,实现按需自动装配 Bean。
|
2月前
|
缓存 安全 Java
Spring Security通用权限管理模型解析
Spring Security作为Spring生态的核心安全框架,结合RBAC与ACL权限模型,基于IoC与AOP构建灵活、可扩展的企业级权限控制体系,涵盖认证、授权流程及数据库设计、性能优化等实现策略。
235 0
|
2月前
|
缓存 安全 Java
Spring Security权限管理解析
Spring Security是Spring生态中的核心安全框架,采用认证与授权分离架构,提供高度可定制的权限管理方案。其基于过滤器链实现认证流程,通过SecurityContextHolder管理用户状态,并结合RBAC模型与动态权限决策,支持细粒度访问控制。通过扩展点如自定义投票器、注解式校验与前端标签,可灵活适配多租户、API网关等复杂场景。结合缓存优化与无状态设计,适用于高并发与前后端分离架构。
251 0
|
1月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
1月前
|
前端开发 Java 应用服务中间件
《深入理解Spring》 Spring Boot——约定优于配置的革命者
Spring Boot基于“约定优于配置”理念,通过自动配置、起步依赖、嵌入式容器和Actuator四大特性,简化Spring应用的开发与部署,提升效率,降低门槛,成为现代Java开发的事实标准。
|
1月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
2月前
|
Java 数据库 数据安全/隐私保护
Spring Boot四层架构深度解析
本文详解Spring Boot四层架构(Controller-Service-DAO-Database)的核心思想与实战应用,涵盖职责划分、代码结构、依赖注入、事务管理及常见问题解决方案,助力构建高内聚、低耦合的企业级应用。
789 1
|
2月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
532 5
|
消息中间件 SpringCloudAlibaba Java
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
1641 0
|
XML Java 数据库连接
【Spring学习笔记 五】Spring注解及Java类配置开发
【Spring学习笔记 五】Spring注解及Java类配置开发
189 0

推荐镜像

更多
  • DNS
  • 下一篇
    oss云网关配置