Spring 源码阅读 40:AutowiredAnnotationBeanPostProcessor 分析(1)

简介: 本文介绍了自动装配注解后处理器中的 determineCandidateConstructors 方法如何为 Bean 类型确定候选的构造函数。

基于 Spring Framework v5.2.6.RELEASE

概述

之前用两篇文章分析了 ConfigurationClassPostProcessor 后处理器对基于注解的 Spring 配置类的处理,包含配置类的解析和增强等。

在 AnnotationConfigApplicationContext 上下文初始化的时候,会初始化一个 AnnotatedBeanDefinitionReader 类型的成员变量,在此期间,会通过 AnnotationConfigUtils 类中的registerAnnotationConfigProcessors方法,注册一些与注解配置相关的处理器。ConfigurationClassPostProcessor 就是其中之一。

本文通过阅读源码的方式,分析在这里注册的另外一个重要的处理器 AutowiredAnnotationBeanPostProcessor。

AutowiredAnnotationBeanPostProcessor 分析

先看 AutowiredAnnotationBeanPostProcessor 类的继承关系。

image.png

可以看出,它是一个 BeanPostProcessor,也就是 Bean 的后处理器。它还实现了一些 BeanPostProcessor 的字接口,BeanPostProcessor 和它的字接口中的方法,会在 Bean 实例初始化的过程中某些特定的时机被调用。所以,我们顺便了解一下这几个接口中包含的方法。

image.png

这里大概介绍一下其中一些关键方法的调用时机。

  • BeanPostProcessor
  • postProcessBeforeInitialization 在 Bean 实例的初始化方法执行前被调用。
  • postProcessAfterInitialization 在 Bean 实例的初始化方法执行后被调用,或者postProcessBeforeInstantiation执行后返回值不为空的情况下被调用。
  • MergedBeanDefinitionPostProcessor
  • postProcessMergedBeanDefinition 在通过doCreateBean使用反射机制创建 Bean 实例之后调用。
  • InstantiationAwareBeanPostProcessor
  • postProcessBeforeInstantiationdoCreateBean执行前自定义 Bean 实例创建的扩展点。
  • postProcessAfterInstantiation 在早期的 Bean 实例创建之后,属性填充和初始化逻辑执行之前。
  • postProcessProperties 在装配 Bean 实例的属性前对属性进行处理。
  • SmartInstantiationAwareBeanPostProcessor
  • getEarlyBeanReference 用于在早期 Bean 实例被获取之前对其进行处理。
  • determineCandidateConstructors 用于在通过反射创建 Bean 实例是获取候选的构造函数。

以上知识对这些方法做了最简单的描述,它们在具体场景中的应用都可以在我之前的一系列源码分析文章中找到,欢迎阅读我的源码阅读系列文章。

再结合前面的类关系图,可以知道 AutowiredAnnotationBeanPostProcessor 对这些方法的实现,都可以从它本身和它的父类 InstantiationAwareBeanPostProcessorAdapter 中找到。在这两个类中,大部分的方法都继承了接口的默认实现,除了determineCandidateConstructors、postProcessMergedBeanDefinitionpostProcessProperties三个方法。下面以这三个方法被调用的时机为顺序,分析这三个方法都做了什么。

determineCandidateConstructors方法分析

首先我们来分析determineCandidateConstructors方法,这个方法被调用的时机,是 Spring 通过反射创建 Bean 实例对象的时候,会通过这个方法来获取候选的构造方法。我们查看方法的源码。

image.png

代码量相当可观,我们接下来会挑重点分析。

在第一个if语句块中,处理了@Lookup注解相关的逻辑,这一部分不太重要,也很少会用到,因此不过介绍,我们重点看后面的部分。

// Quick check on the concurrent map first, with minimal locking.Constructor<?>[] candidateConstructors=this.candidateConstructorsCache.get(beanClass);
if (candidateConstructors==null) {
// Fully synchronized resolution now...synchronized (this.candidateConstructorsCache) {
candidateConstructors=this.candidateConstructorsCache.get(beanClass);
if (candidateConstructors==null) {
// 省略语句块中的代码      }
   }
}
return (candidateConstructors.length>0?candidateConstructors : null);

首先,会从缓存集合candidateConstructorsCache中查询候选构造方法数组,如果缓存中获取的值不为空,则返回数组(数组中有元素)或者空(数组中没有元素)。另外,在if语句块中,还会在synchronized语句块中再次判断一遍。

确保缓存中确实没有内容的情况下,进入之后的流程。也就是上述代码中被省略的部分。

Constructor<?>[] rawCandidates;
try {
rawCandidates=beanClass.getDeclaredConstructors();
}
catch (Throwableex) {
thrownewBeanCreationException(beanName,
"Resolution of declared constructors on bean Class ["+beanClass.getName() +"] from ClassLoader ["+beanClass.getClassLoader() +"] failed", ex);
}

首先,会从当前 Bean 的类型中,获取到所有的构造方法,放到提前声明好的rawCandidates数组中。

List<Constructor<?>>candidates=newArrayList<>(rawCandidates.length);
Constructor<?>requiredConstructor=null;
Constructor<?>defaultConstructor=null;
Constructor<?>primaryConstructor=BeanUtils.findPrimaryConstructor(beanClass);
intnonSyntheticConstructors=0;

然后声明了一系列的变量,供后续的流程使用。这里的primaryConstructor主要是针对 Kotlin 的,如果是 Kotlin 类型,则会返回其主构造方法对应的 Java 构造方法,如果是 Java 类型,则为空。这里可以通过findPrimaryConstructor方法来看一下。

@Nullablepublicstatic<T>Constructor<T>findPrimaryConstructor(Class<T>clazz) {
Assert.notNull(clazz, "Class must not be null");
if (KotlinDetector.isKotlinReflectPresent() &&KotlinDetector.isKotlinType(clazz)) {
Constructor<T>kotlinPrimaryConstructor=KotlinDelegate.findPrimaryConstructor(clazz);
if (kotlinPrimaryConstructor!=null) {
returnkotlinPrimaryConstructor;
      }
   }
returnnull;
}

回到之前的方法中,之后开始对rawCandidates中的构造方法进行遍历。

for (Constructor<?>candidate : rawCandidates) {
// ...}

接下来看for循环语句块中的内容。

if (!candidate.isSynthetic()) {
nonSyntheticConstructors++;
}
elseif (primaryConstructor!=null) {
continue;
}

首先判断了当前的构造方法是不是「合成」的构造方法,不是的话nonSyntheticConstructors变量自增,如果是,且primaryConstructor不是空,则跳过档次循环。

MergedAnnotation<?>ann=findAutowiredAnnotation(candidate);
if (ann==null) {
Class<?>userClass=ClassUtils.getUserClass(beanClass);
if (userClass!=beanClass) {
try {
Constructor<?>superCtor=userClass.getDeclaredConstructor(candidate.getParameterTypes());
ann=findAutowiredAnnotation(superCtor);
      }
catch (NoSuchMethodExceptionex) {
// Simply proceed, no equivalent superclass constructor found...      }
   }
}

这一步是为了获取构造方法上的@Autowired注解,不仅会从当前的构造方法上查找,如果当前的类是CGLIB代理类型,还会从其userClass,也就是用户声明的类中查找。

如果当前的构造方法没有被标记@Autowired注解,则ann变量为空,反之则不为空。获取完成之后,会进入一个if-else语句,针对ann是否为空的情况,执行不同的流程。

if (ann!=null) {
if (requiredConstructor!=null) {
thrownewBeanCreationException(beanName,
"Invalid autowire-marked constructor: "+candidate+". Found constructor with 'required' Autowired annotation already: "+requiredConstructor);
   }
booleanrequired=determineRequiredStatus(ann);
if (required) {
if (!candidates.isEmpty()) {
thrownewBeanCreationException(beanName,
"Invalid autowire-marked constructors: "+candidates+". Found constructor with 'required' Autowired annotation: "+candidate);
      }
requiredConstructor=candidate;
   }
candidates.add(candidate);
}

如果ann不为空,则进入以上的代码块中。首先会通过其中的第一个if语句判断requiredConstructor是否为空,如果不是空的话会抛出异常。当循环第一次走到这里的时候,它一定是空的,我们接着往下看。

接下来会判断当前构造方法的@Autowired注解的required属性是否为true。如果是true的话,则会进入下一个if语句块。因为如果有构造方法的@Autowired注解的required属性是否为true的时候,被注解标记的构造方法只能有一个,所以,如果当前的构造方法如果符合条件,但是candidates不为空,则会抛出异常。确保没有异常之后,会将当前的构造方法赋值给requiredConstructor变量。

最后,只要当前的构造方法被标记了@Autowired注解,都会被添加到candidates集合中。

如果当前的构造方法没有被标记@Autowired注解,且构造方法是无参构造方法,则将其赋值给defaultConstructor变量。

elseif (candidate.getParameterCount() ==0) {
defaultConstructor=candidate;
}

至此,遍历所有构造方法的for循环就执行完了。接下来会进入一系列的条件判断语句,它们的主要作用是对之前遍历过程中得到的结果进行处理。

if (!candidates.isEmpty()) {
// Add default constructor to list of optional constructors, as fallback.if (requiredConstructor==null) {
if (defaultConstructor!=null) {
candidates.add(defaultConstructor);
      }
elseif (candidates.size() ==1&&logger.isInfoEnabled()) {
logger.info("Inconsistent constructor declaration on bean with name '"+beanName+"': single autowire-marked constructor flagged as optional - "+"this constructor is effectively required since there is no "+"default constructor to fall back to: "+candidates.get(0));
      }
   }
candidateConstructors=candidates.toArray(newConstructor<?>[0]);
}

如果candidates不为空,也就是存在被标记@Autowired注解的构造方法,则进入当前的语句块。

再次判断,如果requiredConstructor为空,也就是不存在@Autowired注解的required属性是否为true的构造方法,则将默认构造方法(也就是空参的构造发方法)defaultConstructor添加到candidates中。

当前if语句块的最后,将candidates转换为数组赋值给candidateConstructors作为候选的构造方法数组。

如果candidates为空,则进入后续的条件判断语句块。

elseif (rawCandidates.length==1&&rawCandidates[0].getParameterCount() >0) {
candidateConstructors=newConstructor<?>[] {rawCandidates[0]};
}
elseif (nonSyntheticConstructors==2&&primaryConstructor!=null&&defaultConstructor!=null&&!primaryConstructor.equals(defaultConstructor)) {
candidateConstructors=newConstructor<?>[] {primaryConstructor, defaultConstructor};
}
elseif (nonSyntheticConstructors==1&&primaryConstructor!=null) {
candidateConstructors=newConstructor<?>[] {primaryConstructor};
}
else {
candidateConstructors=newConstructor<?>[0];
}

后续的几个条件连起来看。

  1. 如果当前类型只有一个构造方法,且是有参数的构造方法,则candidateConstructors为之包含这个构造方法的数组。
  2. 如果只有两个非合成的构造方法,其中一个是默认构造方法defaultConstructor,另一个是primaryConstructor,且两者不是同一个构造方法,则candidateConstructors为包含这两者的数组,primaryConstructordefaultConstructor之前。
  3. 如果只有一个primaryConstructor,则candidateConstructors是之包含它的数组。
  4. 如果之前的条件都不符合,则candidateConstructors为空数组。

处理完之后,会将结果放到缓存中。

this.candidateConstructorsCache.put(beanClass, candidateConstructors);

在方法的最后,会判断candidateConstructors是否有元素,如果有则返回,没有就返回空。

return (candidateConstructors.length>0?candidateConstructors : null);

总结

本文介绍了 AutowiredAnnotationBeanPostProcessor 后处理器中的determineCandidateConstructors如何为 Bean 类型确定候选的构造函数。

前文中提到的另外两个后处理器方法,将在后续的文章中进行分析。

目录
相关文章
|
5天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
38 0
|
5天前
|
监控 数据可视化 安全
一套成熟的Spring Cloud智慧工地平台源码,自主版权,开箱即用
这是一套基于Spring Cloud的智慧工地管理平台源码,具备自主版权,易于使用。平台运用现代技术如物联网、大数据等改进工地管理,服务包括建设各方,提供人员、车辆、视频监控等七大维度的管理。特色在于可视化管理、智能报警、移动办公和分布计算存储。功能涵盖劳务实名制管理、智能考勤、视频监控AI识别、危大工程监控、环境监测、材料管理和进度管理等,实现工地安全、高效的智慧化管理。
|
5天前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
25 6
|
5天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
32 3
|
5天前
|
Java 关系型数据库 MySQL
【Java Spring开源项目】新蜂(NeeBee)商城项目运行、分析、总结
【Java Spring开源项目】新蜂(NeeBee)商城项目运行、分析、总结
154 4
|
5天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
17 1
|
5天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
108 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
5天前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。
|
5天前
|
存储 缓存 Java
【spring】06 循环依赖的分析与解决
【spring】06 循环依赖的分析与解决
9 1
|
5天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)