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的容器里面。


相关文章
|
10天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
27天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
16天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
40 9
|
2月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
204 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
2月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
224 24
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
2月前
|
XML 缓存 Java
手写Spring源码(简化版)
Spring包下的类、手写@ComponentScan注解、@Component注解、@Autowired注解、@Scope注解、手写BeanDefinition、BeanNameAware、InitializingBean、BeanPostProcessor 、手写AnnotationConfigApplicationContext
手写Spring源码(简化版)
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
113 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
130 9
|
1月前
|
设计模式 JavaScript Java
Spring 事件监听机制源码
Spring 提供了事件发布订阅机制,广泛应用于项目中。本文介绍了如何通过自定义事件类、订阅类和发布类实现这一机制,并展示了如何监听 SpringBoot 启动过程中的多个事件(如 `ApplicationStartingEvent`、`ApplicationEnvironmentPreparedEvent` 等)。通过掌握这些事件,可以更好地理解 SpringBoot 的启动流程。示例代码展示了从事件发布到接收的完整过程。
下一篇
无影云桌面