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

简介: 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"));
  }
}
目录
相关文章
|
10小时前
|
安全 Java 开发者
深入理解Spring Boot配置绑定及其实战应用
【4月更文挑战第10天】本文详细探讨了Spring Boot中配置绑定的核心概念,并结合实战示例,展示了如何在项目中有效地使用这些技术来管理和绑定配置属性。
11 1
|
10小时前
|
机器学习/深度学习 存储 算法
卷积神经网络(CNN)的数学原理解析
卷积神经网络(CNN)的数学原理解析
5 1
卷积神经网络(CNN)的数学原理解析
|
10小时前
|
XML Java 开发者
springboot 启动原理、启动过程、启动机制的介绍
【5月更文挑战第13天】Spring Boot 是一种基于 Java 的框架,用于创建独立的、生产级别的 Spring 应用程序。它的主要目标是简化 Spring 应用的初始搭建和开发过程,同时提供一系列大型项目常见的非功能性特征(如嵌入式服务器、安全性、度量、健康检查和外部化配置)。
17 3
|
10小时前
|
XML JavaScript 数据格式
Beautiful Soup 库的工作原理基于解析器和 DOM(文档对象模型)树的概念
【5月更文挑战第10天】Beautiful Soup 使用解析器(如 html.parser, lxml, html5lib)解析HTML/XML文档,构建DOM树。它提供方法查询和操作DOM,如find(), find_all()查找元素,get_text(), get()提取信息。还能修改DOM,添加、修改或删除元素,并通过prettify()输出格式化字符串。它是处理网页数据的利器,尤其在处理不规则结构时。
23 2
|
10小时前
|
机器学习/深度学习 人工智能 数据可视化
号称能打败MLP的KAN到底行不行?数学核心原理全面解析
Kolmogorov-Arnold Networks (KANs) 是一种新型神经网络架构,挑战了多层感知器(mlp)的基础,通过在权重而非节点上使用可学习的激活函数(如b样条),提高了准确性和可解释性。KANs利用Kolmogorov-Arnold表示定理,将复杂函数分解为简单函数的组合,简化了神经网络的近似过程。与mlp相比,KAN在参数量较少的情况下能达到类似或更好的性能,并能直观地可视化,增强了模型的可解释性。尽管仍需更多研究验证其优势,KAN为深度学习领域带来了新的思路。
93 5
|
10小时前
|
敏捷开发 测试技术 持续交付
极限编程(XP)原理与技巧:深入解析与实践
【5月更文挑战第8天】极限编程(XP)是一种敏捷开发方法,注重快速反馈、迭代开发和简单设计,以提高软件质量和项目灵活性。关键原则包括客户合作、集体代码所有权、持续集成等。实践中,使用故事卡片描述需求,遵循编程约定,实行TDD,持续重构,结对编程,并定期举行迭代会议。通过理解和应用XP,团队能提升效率,应对变化。
|
10小时前
|
Java 文件存储 Spring
【springboot】logback配置
【springboot】logback配置
19 1
|
10小时前
|
缓存 自然语言处理 JavaScript
万字长文深度解析JDK序列化原理及Fury高度兼容的极致性能实现
Fury是一个基于JIT动态编译的高性能多语言原生序列化框架,支持Java/Python/Golang/C++/JavaScript等语言,提供全自动的对象多语言/跨语言序列化能力,以及相比于别的框架最高20~200倍的性能。
156491 0
|
10小时前
|
缓存 测试技术 Android开发
深入了解Appium:Capability 高级配置技巧解析
Appium 提供多种进阶配置项以优化自动化测试,如 deviceName 作为设备别名,udid 确保选择特定设备,newCommandTimeout 设置超时时间,PRINT_PAGE_SOURCE_ON_FIND_FAILURE 在错误时打印页面源,以及测试策略中的 noReset、shouldTerminateApp 和 forceAppLaunch 控制应用状态和重启。这些配置可提升测试效率和准确性。
17 2
|
10小时前
|
存储 弹性计算 固态存储
阿里云服务器配置选择指南,2024年全解析
阿里云服务器配置选择涉及CPU、内存、带宽和磁盘。个人开发者或中小企业推荐使用轻量应用服务器或ECS经济型实例,如2核2G3M配置,适合网站和轻量应用。企业用户则应选择企业级独享型ECS,如计算型c7、通用型g7,至少2核4G起,带宽建议5M,系统盘考虑SSD云盘或ESSD云盘以保证性能。阿里云提供了多种实例类型和配置,用户需根据实际需求进行选择。

推荐镜像

更多