SpringBoot自动配置原理解析(五)

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: SpringBoot自动配置原理解析(五)

本文我们主要研究一下@ComponentScan的解析过程。

如下所示,在ConfigurationClassParserdoProcessConfigurationClass方法中会对@ComponentScan注解进行处理。

// Process any @ComponentScan annotations
//扫描sourceClass上配置的@ComponentScans  @ComponentScan注解得到每一个注解属性
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
    sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
//如果不为空且不需要跳过,则对每一个componentScan 进行处理
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 immediately
//使用解析器对注解进行解析得到扫描的BeanDefinitionHolder集合
    Set<BeanDefinitionHolder> scannedBeanDefinitions =
        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
    // Check the set of scanned definitions for any further config classes and parse recursively if needed
//对每一个BeanDefinitionHolder 判断其是否可以作为候选配置类,如果是则触发配置类解析过程
    for (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());
      }
    }
  }
}

可以看到这里首先使用componentScanParser解析每一个componentScan 得到BeanDefinitionHolder 的集合scannedBeanDefinitions。然后对集合进行遍历如果可以作为候选配置类,则触发配置类的解析流程。配置类解析过程我们本文不再赘述,我们看一下this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());


这里componentScanParser是ComponentScanAnnotationParser。


① parse方法

// ComponentScanAnnotationParser
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// 实例化扫描器
  ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
      componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
//本文这里得到的是AnnotationBeanNameGenerator
  Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
  // true 
  boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
  //设置beanNameGenerator 
  scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
      BeanUtils.instantiateClass(generatorClass));
//默认是DEFAULT
  ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
  if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
    scanner.setScopedProxyMode(scopedProxyMode);
  }
  else {
  // 本文这里得到的是AnnotationScopeMetadataResolver
    Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
    scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
  }
//默认是**/*.class"
  scanner.setResourcePattern(componentScan.getString("resourcePattern"));
//解析includeFilters
  for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
    for (TypeFilter typeFilter : typeFiltersFor(filter)) {
      scanner.addIncludeFilter(typeFilter);
    }
  }
//解析excludeFilters
  for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
    for (TypeFilter typeFilter : typeFiltersFor(filter)) {
      scanner.addExcludeFilter(typeFilter);
    }
  }
//是否懒加载,默认为false
  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得到包名放到basePackages中
  for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
    basePackages.add(ClassUtils.getPackageName(clazz));
  }
  // 如果为空,则添加当前declaringClass所在包路径
  if (basePackages.isEmpty()) {
    basePackages.add(ClassUtils.getPackageName(declaringClass));
  }
//添加排他过滤器
  scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
    @Override
    protected boolean matchClassName(String className) {
      return declaringClass.equals(className);
    }
  });
  //触发扫描,最终得到过滤后的BeanDefinition集合。
  return scanner.doScan(StringUtils.toStringArray(basePackages));
}

方法如上所示,首先实例化得到一个ClassPathBeanDefinitionScanner ,然后解析当前注解的属性最终得到basePackages对其进行扫描,最终得到过滤后的BeanDefinition集合。


② doScan扫描

这里basePackages是基础包路径,从这里面遍历扫描候选组件然后注册为BeanDefinition。

// ClassPathBeanDefinitionScanner 
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  Assert.notEmpty(basePackages, "At least one base package must be specified");
  Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  //遍历basePackages
  for (String basePackage : basePackages) {
  //获取当前basePackage下的候选组件,会进行双重过滤
    Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
  //遍历候选组件
    for (BeanDefinition candidate : candidates) {
    //首先为BeanDefinition设置scope,默认是singleton
    // AnnotationScopeMetadataResolver
      ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
      candidate.setScope(scopeMetadata.getScopeName());
      //获取当前BeanDefinition对应的beanName
      String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
//对BeanDefinition做后置处理
      if (candidate instanceof AbstractBeanDefinition) {
//对BeanDefinition做一些处理如设置autowireCandidate、lazyInit、setAutowireMode等
        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
      }
      if (candidate instanceof AnnotatedBeanDefinition) {
//BeanDefinition的通用后置处理
        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
      }
      //检测BeanDefinition是否不存在或是否可共存
      if (checkCandidate(beanName, candidate)) {
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
        //如果ScopedProxyMode不为NO,则尝试创建代理,默认为NO
        definitionHolder =
            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        beanDefinitions.add(definitionHolder);
        //注册BeanDefinition到BeanFactory中
        registerBeanDefinition(definitionHolder, this.registry);
      }
    }
  }
  return beanDefinitions;
}

方法逻辑梳理如下:


遍历basePackages

获取当前basePackage下的候选组件,会进行双重过滤

遍历候选组件

首先为BeanDefinition设置scope,默认是singleton

获取当前BeanDefinition对应的beanName

对BeanDefinition做后置处理

检测BeanDefinition是否不存在或是否可共存

如果ScopedProxyMode不为NO,则尝试创建代理,默认为NO

注册BeanDefinition到BeanFactory中


③ 获取候选组件

// ClassPathScanningCandidateComponentProvider#findCandidateComponents
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
//本文这里componentsIndex 为null
  if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
    return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
  }
  else {
  //走了该分支
    return scanCandidateComponents(basePackage);
  }
}


扫描候选组件方法如下,本质就是根据基础包路径扫描得到一个Resource数组然后进行后滤。过滤后的实例化得到一个BeanDefinition,再次过滤得到最终需要的结果。

// ClassPathScanningCandidateComponentProvider#scanCandidateComponents
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
  Set<BeanDefinition> candidates = new LinkedHashSet<>();
  try {
  //classpath*: +包路径+ / + **/*.class
  //比如classpath*:com/recommend/**/*.class
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
        resolveBasePackage(basePackage) + '/' + this.resourcePattern;
//解析路径,得到一个个具体的资源
    Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
    boolean traceEnabled = logger.isTraceEnabled();
    boolean debugEnabled = logger.isDebugEnabled();
//遍历resources,将每一个Resource 生成为一个ScannedGenericBeanDefinition
    for (Resource resource : resources) {
      if (traceEnabled) {
        logger.trace("Scanning " + resource);
      }
      //判断资源是否可读的
      if (resource.isReadable()) {
        try {
        //metadataReader包括了resource和annotationMetadata
          MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
//判断是否为候选组件,这里会应用excludeFilters、includeFilters
//第一次过滤
          if (isCandidateComponent(metadataReader)) {
          //实例化BeanDefinition
            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
            //设置source resource
            sbd.setResource(resource);
            sbd.setSource(resource);
//第二次过滤
//(非接口非抽象||抽象&Lookup标注方法)&&isIndependent 是独立的
            if (isCandidateComponent(sbd)) {
              if (debugEnabled) {
                logger.debug("Identified candidate component class: " + resource);
              }
              candidates.add(sbd);
            }
            else {
              if (debugEnabled) {
                logger.debug("Ignored because not a concrete top-level class: " + resource);
              }
            }
          }
          else {
            if (traceEnabled) {
              logger.trace("Ignored because not matching any filter: " + resource);
            }
          }
        }
        catch (Throwable ex) {
          throw new BeanDefinitionStoreException(
              "Failed to read candidate component class: " + resource, ex);
        }
      }
      else {
        if (traceEnabled) {
          logger.trace("Ignored because not readable: " + resource);
        }
      }
    }
  }
  catch (IOException ex) {
    throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
  }
  return candidates;
}


可以看到这里首先会解析得到packageSearchPath,然后使用ResourcePatternResolver对packageSearchPath进行处理得到一个Resource[] resources数组。这个数组也就是基础包路径下的一个个类,对每个Resource进行双重过滤后将合适的ScannedGenericBeanDefinition放到candidates中最后返回给上游。


④ BeanDefinition的后置处理

如下所示得到候选组件后,我们会对每一个candidate做处理。


其中下面两行是对BeanDefinition做处理。

postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);

4.1 postProcessBeanDefinition

这个主要是为beanDefinition设置默认属性值。

// ClassPathBeanDefinitionScanner#postProcessBeanDefinition
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
// 设置默认属性
  beanDefinition.applyDefaults(this.beanDefinitionDefaults);
  //尝试设置AutowireCandidate
  if (this.autowireCandidatePatterns != null) {
    beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
  }
}
// AbstractBeanDefinition#applyDefaults  设置默认值
public void applyDefaults(BeanDefinitionDefaults defaults) {
  //默认是null
  Boolean lazyInit = defaults.getLazyInit();
  if (lazyInit != null) {
    setLazyInit(lazyInit);
  }
  // 默认是AUTOWIRE_NO  0
  setAutowireMode(defaults.getAutowireMode());
  //默认是DEPENDENCY_CHECK_NONE 0 
  setDependencyCheck(defaults.getDependencyCheck());
  setInitMethodName(defaults.getInitMethodName());
  setEnforceInitMethod(false);
  setDestroyMethodName(defaults.getDestroyMethodName());
  setEnforceDestroyMethod(false);
}


4.2 processCommonDefinitionAnnotations

最终会来到AnnotationConfigUtilsprocessCommonDefinitionAnnotations方法。主要用来处理@Lazy、@Primary、@DependsOn、@Role以及@Description注解信息。

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
//获取Lazy注解信息
  AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
  if (lazy != null) {
    abd.setLazyInit(lazy.getBoolean("value"));
  }
  else if (abd.getMetadata() != metadata) {
    lazy = attributesFor(abd.getMetadata(), Lazy.class);
    if (lazy != null) {
      abd.setLazyInit(lazy.getBoolean("value"));
    }
  }
//获取@Primary注解信息
  if (metadata.isAnnotated(Primary.class.getName())) {
    abd.setPrimary(true);
  }
  //获取@DependsOn注解信息
  AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
  if (dependsOn != null) {
    abd.setDependsOn(dependsOn.getStringArray("value"));
  }
// 获取@Role注解信息
  AnnotationAttributes role = attributesFor(metadata, Role.class);
  if (role != null) {
    abd.setRole(role.getNumber("value").intValue());
  }
  //获取@Description注解信息
  AnnotationAttributes description = attributesFor(metadata, Description.class);
  if (description != null) {
    abd.setDescription(description.getString("value"));
  }
}
目录
相关文章
|
6天前
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
53 22
解析静态代理IP改善游戏体验的原理
|
4天前
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
48 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
27天前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
60 17
springboot自动配置原理
|
5天前
|
存储 人工智能 并行计算
2025年阿里云弹性裸金属服务器架构解析与资源配置方案
🚀 核心特性与技术创新:提供100%物理机性能输出,支持NVIDIA A100/V100 GPU直通,无虚拟化层损耗。网络与存储优化,400万PPS吞吐量,ESSD云盘IOPS达100万,RDMA延迟<5μs。全球部署覆盖华北、华东、华南及海外节点,支持跨地域负载均衡。典型应用场景包括AI训练、科学计算等,支持分布式训练和并行计算框架。弹性裸金属服务器+OSS存储+高速网络综合部署,满足高性能计算需求。
|
1月前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
|
1月前
|
JavaScript Java 程序员
SpringBoot自动配置及自定义Starter
Java程序员依赖Spring框架简化开发,但复杂的配置文件增加了负担。SpringBoot以“约定大于配置”理念简化了这一过程,通过引入各种Starter并加载默认配置,几乎做到开箱即用。
116 10
SpringBoot自动配置及自定义Starter
|
2月前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
2月前
|
Java Maven Spring
SpringBoot配置跨模块扫描问题解决方案
在分布式项目中,使用Maven进行多模块开发时,某些模块(如xxx-common)没有启动类。如何将这些模块中的类注册为Spring管理的Bean对象?本文通过案例分析,介绍了两种解决方案:常规方案是通过`@SpringBootApplication(scanBasePackages)`指定扫描路径;推荐方案是保持各模块包结构一致(如com.xxx),利用SpringBoot默认扫描规则自动识别其他模块中的组件,简化配置。
SpringBoot配置跨模块扫描问题解决方案
|
2月前
|
存储 物联网 大数据
探索阿里云 Flink 物化表:原理、优势与应用场景全解析
阿里云Flink的物化表是流批一体化平台中的关键特性,支持低延迟实时更新、灵活查询性能、无缝流批处理和高容错性。它广泛应用于电商、物联网和金融等领域,助力企业高效处理实时数据,提升业务决策能力。实践案例表明,物化表显著提高了交易欺诈损失率的控制和信贷审批效率,推动企业在数字化转型中取得竞争优势。
120 16
|
9月前
|
XML 安全 Java
深入实践springboot实战 蓄势待发 我不是雷锋 我是知识搬运工
springboot,说白了就是一个集合了功能的大类库,包括springMVC,spring,spring data,spring security等等,并且提供了很多和可以和其他常用框架,插件完美整合的接口(只能说是一些常用框架,基本在github上能排上名次的都有完美整合,但如果是自己写的一个框架就无法实现快速整合)。

推荐镜像

更多