spring源码之BeanDefinitionScanner的底层源码详解

简介: spring源码之BeanDefinitionScanner的底层源码详解

一,BeanDefinitionScanner加载流程

1,源码分析前准备

在分析这个源码的时候,首先需要去官网下载这个spring的源码包,建议下载5.x.x的版本


我这里安装的是:https://github.com/spring-projects/spring-framework/tree/5.2.x


2,源码分析

1,ApplicationContext是参与了整个springIoc的加载流程,因此ApplicationContext也是作为SpringIoc的一个入口了。由于ApplicationContext接口有很多的实现类,因此这里使用注解的方式来获取上下文的内容。


首先通过这个注解类AnnotationConfigApplicationContext获取这个上下文的全部信息,然后加载里面的配置信息,环境等。

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);

2,然后进入这个AnnotationConfigApplicationContext类里面,可以发现有一个无参构造方法,注册配置类的方法和一个refresh刷新IOC容器的方法,这里的话主要先看这个无参的构造方法。

public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
  //调用构造函数
  this();
  //注册我们的配置类
  register(annotatedClasses);
  //IOC容器刷新接口
  refresh();
}

3,在这个构造函数里面,会实例化一个BeanDefinitionReader的读取器和一个BeanDefinitionScanner的扫描器。读取器就是为了读取注解,扫描器是为了扫描这个包和类,最后注册成一个BeanDefinition,这个BeanDefinitionScanner 也是本篇文章的核心内容

public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
  super(beanFactory);
  this.reader = new AnnotatedBeanDefinitionReader(this);
  this.scanner = new ClassPathBeanDefinitionScanner(this);
}

4,在这个ClassPathBeanDefinitionScanner类的构造方法里面,首先会设置一下这个当前的环境以及资源加载器,还有一个重要的就是有一个初始化一个默认的过滤器的方法。

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
  Environment environment, @Nullable ResourceLoader resourceLoader) {
  this.registry = registry;
  if (useDefaultFilters) {
    registerDefaultFilters();
  }
  //设置环境对象
  setEnvironment(environment);
  //设置资源加载器
  setResourceLoader(resourceLoader);
}

然后可以来查看一下这个registerDefaultFilters的方法,里面会初始化这个includeFilter的这个包含过滤器,其底层就是一个List集合。这个包含过滤器里面会去添加所有的加了@Component注解的类,并且在spring中,这个@Component的这个注解就是在这个阶段进行扫描的。这个包含过滤器会在后面是否成为一个候选的bean的时候起到作用。

private final List<TypeFilter> includeFilters = new LinkedList<>();
protected void registerDefaultFilters() {
  //加入扫描我们的@Component的
  this.includeFilters.add(new AnnotationTypeFilter(Component.class));
}

5,接下来进入这个ClassPathBeanDefinitionScanner扫描类里面,会有一个scan方法,然后开始进行真正的对这个包路径的扫描。

public int scan(String... basePackages) {
    //对这些pachage的这些包进行扫描
  doScan(basePackages);
}

接下来进入这个doScan的这个方法里面,就是开始扫描包。

protected Set < BeanDefinitionHolder > doScan(String...basePackages) {
  //创建bean定义的holder对象用于保存扫描后生成的bean定义对象
  Set <BeanDefinitionHolder> beanDefinitions = new LinkedHashSet <> ();
  //循环我们的包路径集合
  for (String basePackage: basePackages) {
    //找到候选的Components
    Set < BeanDefinition > candidates = findCandidateComponents(basePackage);
    for (BeanDefinition candidate: candidates) {
      ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
      candidate.setScope(scopeMetadata.getScopeName());
      //设置我们的beanName
      String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
      //这是默认配置 autowire-candidate
      if (candidate instanceof AbstractBeanDefinition) {
        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
      }
      //获取@Lazy @DependsOn等注解的数据设置到BeanDefinition中
      if (candidate instanceof AnnotatedBeanDefinition) {
        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
      }
      //把我们解析出来的组件bean定义注册到我们的IOC容器中(容器中没有才注册)
      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;
}

6,在上面的doScan方法里面,会有一个findCandidateComponents(basePackage) 的方法,主要是为了找到需要生成beanDefinition的候选者。进入这个方法,里面会有一个这个scanCandidateComponents方法用于扫描全部的候选者。并且里面有一个componentsIndex的一个索引,主要是为了增加这个查询的效率

public Set < BeanDefinition > findCandidateComponents(String basePackage) {
  if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
    return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
  } else {
    return scanCandidateComponents(basePackage);
  }
}

7,接下来在查看这个scanCandidateComponents方法,就是用来扫描这些候选的Component配置类的。这里面的basePackage就是具体的包路径,比如说com.zhs.study,最后会将这个包路径转化为资源路径com/zhs/study。然后会去遍历这个资源集合,最后判断这些包路径下面的类是不是一个候选的component

private Set <BeanDefinition> scanCandidateComponents(String basePackage) {
  Set <BeanDefinition> candidates = new LinkedHashSet<>();
  try {
    //把我们的包路径转为资源路径 com/zhs/study
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
        resolveBasePackage(basePackage) + '/' + this.resourcePattern;
    //扫描指定包路径下面的所有.class文件
    Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        //遍历这个resources集合
    for (Resource resource : resources) {
            try {
                //获取这个当前类的一个读取器,就可以读取当前类的类名,注解等
        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                //判断当前类是不是一个候选的bean
                if (isCandidateComponent(metadataReader)) {
                    //如果这个类是一个有效的bean
          ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
          //判断类的属性,如是否是接口,抽象类,内部类等,如果是也不能注册
          if (isCandidateComponent(sbd)) {    
            //加入到集合中
            candidates.add(sbd);
          }
                }
            }
        }
  }
}

8,判断这个类是不是一个有效的bean的方法如下,主要是在这个 isCandidateComponent 方法里面实现。


首先会判断一下这个类在不在这个excludeFilters排除过滤器里面,如果在里面,那么直接返回false;


如果不在排除过滤器里面,那么会判断在不在这个includeFilters包含过滤器里面,就是判断一下这个类上面有没有这个@Component的这个注解。


如果有这个@Component注解,又会去判断一下这个类上面有没有这个@Conditional这个条件注解,如果有这个注解,则会判断是否符合里面的条件,如果符合条件,那么可以成为一个BeanDefinition;如果没有这个注解,则可以通过这个过滤器,可以成为一个BeanDefinition


如果都不在这两个过滤器里面,那么也会返回一个false,表示不符合条件

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
  //通过excludeFilters 进行是否需要排除的
  for (TypeFilter tf: this.excludeFilters) {
    if (tf.match(metadataReader, getMetadataReaderFactory())) {
      return false;
    }
  }
  //includeFilters 是否需要进行包含的
  for (TypeFilter tf: this.includeFilters) {
    if (tf.match(metadataReader, getMetadataReaderFactory())) {
      return isConditionMatch(metadataReader);
    }
  }
  return false;
}
//如果有这个@Component的这个注解,又会再判断一下这个这个类上面有没有加这个@Conditional的这个注解
private boolean isConditionMatch(MetadataReader metadataReader) {
  if (this.conditionEvaluator == null) {
    this.conditionEvaluator =
      new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
  }
  return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
}

9,如果这个类是一个有效的bean,那么里面又内嵌了一个 isCandidateComponent 的布尔类型的方法,主要是判断一下这个有效bean的类是一个什么类型的类。如果里面不是一些接口,抽象类,内部类等,那么才能将这个有效的BeanDefinition对象加入set集合给返回。


protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
  AnnotationMetadata metadata = beanDefinition.getMetadata();
  // metadata.isIndependent()=顶级类、嵌套类、静态内部类
  // metadata.isConcrete() =非接口、非抽象类
  // metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName() = 抽象类并且必须方法中有@LookUp
  return (metadata.isIndependent() && (metadata.isConcrete() ||
    (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

10,回到5里面的doScan方法,再获取到这个全部需要生成的BeanDefinition之后,就会去给这个BeanDefinition进行一个初始的赋值。比如说设置一些作用域,bean的名字,是否懒加载等。

String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
//这是默认配置 autowire-candidate
if (candidate instanceof AbstractBeanDefinition) {
  postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
//获取@Lazy @DependsOn等注解的数据设置到BeanDefinition中
if (candidate instanceof AnnotatedBeanDefinition) {
  AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}

11,依旧是回到5里面的doScan的方法里面,再设置完一些属性之后,就会开始将这个BeanDefinition注册到这个springIoc的容器里面了。首先会判断一下这个BeanDefinition在这个容器里面是否存在,如果不存在,那么就会将这个BeanDefinition注册到这个springIoc的容器里面。

//把我们解析出来的组件bean定义注册到我们的IOC容器中(容器中没有才注册)
if (checkCandidate(beanName, candidate)) {
  BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
  definitionHolder =
    AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  beanDefinitions.add(definitionHolder);
  registerBeanDefinition(definitionHolder, this.registry);
}

二,总结

1,BeanDifinitionScanner执行流程总结

7aecbc5366c54a508afac77e9a40b84c.png


1,首先会调用一个scan的一个方法,然后调用里面的doscan方法开始真正的扫描。


2,首先会扫描所有的包路径,会获取包下面所有的类,然后会这些类都会成为一个被候选的类,如果满足条件那么就可以成为最终的BeanDefinition。


3,候选的规则如下,首先会判断一下这个类在不在一个excludeFilters的排除过滤器里面,如果在里面,那么直接返回;再判断一下这个类在不在一个includeFilters的包含过滤器里面,就是这个类上面有没有一个@Component这个类的注解,没有则直接return返回;有的话则继续判断一下这个类上面有没有一个@Conditional的条件注解,如果有的话看一下这个类是否满足里里面的条件表达式,如果不满足则直接return返回,如果满足的话就可以成为一个有效的BeanDifinition,如果这个类上面没有这个@Conditional的这个注解,那么也会成为一个有效的BeanDifinition


4,在成为一个有效的BeanDefinition之后,会判断一下这个BeanDefinition的类是一个什么类型,如果是接口,抽象类,内部类等,那么直接return ;如果不是接口,抽象类,内部类等,那么会将这个有效类加入到这个set集合里面,最后返回这个set集合


5,在获取到所有的BeanDefinition之后,会设置一些BeanDefinition的一些属性,如一些作用域、是否懒加载等,并且这些BeanDefinition都会加入到一个BeanDefinitionMap里面


6,最后会去判断一下这个Ioc容器里面是否存在这个BeanDefinition,如果不存在,那么会通过这个beanDefinitionRegistry将这个BeanDefinition注册到Spring的Ioc容器里面


2,@Component注解总结

就是在spring启动时,这个加了@Component这个注解上面的类,是在这个@IncludeFilter包含过滤器里面被创建和加载的。在创建这个@IncludeFilter的时候,就会去获取所有加了这个@Component这个注解的类,会把这些类加载到这个Spring的容器里面。


相关文章
|
1天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
36 0
|
1天前
|
监控 数据可视化 安全
一套成熟的Spring Cloud智慧工地平台源码,自主版权,开箱即用
这是一套基于Spring Cloud的智慧工地管理平台源码,具备自主版权,易于使用。平台运用现代技术如物联网、大数据等改进工地管理,服务包括建设各方,提供人员、车辆、视频监控等七大维度的管理。特色在于可视化管理、智能报警、移动办公和分布计算存储。功能涵盖劳务实名制管理、智能考勤、视频监控AI识别、危大工程监控、环境监测、材料管理和进度管理等,实现工地安全、高效的智慧化管理。
|
1天前
|
消息中间件 NoSQL Java
Spring Cloud项目实战Spring Cloud视频教程 含源码
Spring Cloud项目实战Spring Cloud视频教程 含源码
34 1
|
1天前
|
设计模式 Java Spring
【Spring源码】WebSocket做推送动作的底层实例是谁
我们都知道WebSocket可以主动推送消息给用户,那做推送动作的底层实例究竟是谁?我们先整体看下整个模块的组织机构。可以看到handleMessage方法定义了每个消息格式采用不同的消息处理方法,而这些方法该类并**没有实现**,而是留给了子类去实现。
29 1
【Spring源码】WebSocket做推送动作的底层实例是谁
|
1天前
|
存储 设计模式 Java
【Spring源码】Bean采用什么数据结构进行存储
我们再来看看中间新加入的阅读线索4,不知大家忘记了没。我们可以对照图片1的代码组织结构,发现这些没存储在包里的功能类都是比较杂乱的,想必是Spring觉得目前这些功能类还构不成一个包的体系,可能后面规模更大会统一集成起来管理。
33 1
【Spring源码】Bean采用什么数据结构进行存储
|
1天前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
20 6
|
1天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
21 3
|
1天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
15 1
|
1天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
102 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
1天前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。
http://www.vxiaotou.com