在Spring中,一个bean需要通过实例化来获取一个对象,而实例化的过程涉及到构造方法的调用。本文将主要探讨简单的构造推断和实例化过程,让我们首先深入了解实例化的步骤。

实例化源码

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// Make sure bean class is actually resolved at this point.
Class<?> beanClass = resolveBeanClass(mbd, beanName); ..... // BeanDefinition中添加了Supplier,则调用Supplier来得到对象
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName);
} // @Bean对应的BeanDefinition
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
} // Shortcut when re-creating the same bean...
// 一个原型BeanDefinition,会多次来创建Bean,那么就可以把该BeanDefinition所要使用的构造方法缓存起来,避免每次都进行构造方法推断
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
synchronized (mbd.constructorArgumentLock) {
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
// autowireNecessary表示有没有必要要进行注入,比如当前BeanDefinition用的是无参构造方法,那么autowireNecessary为false,否则为true,表示需要给构造方法参数注入值
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if (resolved) {
// 如果确定了当前BeanDefinition的构造方法,那么看是否需要进行对构造方法进行参数的依赖注入(构造方法注入)
if (autowireNecessary) {
// 方法内会拿到缓存好的构造方法的入参
return autowireConstructor(beanName, mbd, null, null);
}
else {
// 构造方法已经找到了,但是没有参数,那就表示是无参,直接进行实例化
return instantiateBean(beanName, mbd);
}
} // 如果没有找过构造方法,那么就开始找了 // Candidate constructors for autowiring?
// 提供一个扩展点,可以利用SmartInstantiationAwareBeanPostProcessor来控制用beanClass中的哪些构造方法
// 比如AutowiredAnnotationBeanPostProcessor会把加了@Autowired注解的构造方法找出来,具体看代码实现会更复杂一点
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); // 如果推断出来了构造方法,则需要给构造方法赋值,也就是给构造方法参数赋值,也就是构造方法注入
// 如果没有推断出来构造方法,但是autowiremode为AUTOWIRE_CONSTRUCTOR,则也可能需要给构造方法赋值,因为不确定是用无参的还是有参的构造方法
// 如果通过BeanDefinition指定了构造方法参数值,那肯定就是要进行构造方法注入了
// 如果调用getBean的时候传入了构造方法参数值,那肯定就是要进行构造方法注入了
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
} // Preferred constructors for default construction?
ctors = mbd.getPreferredConstructors();
if (ctors != null) {
return autowireConstructor(beanName, mbd, ctors, null);
} // No special handling: simply use no-arg constructor.
// 不匹配以上情况,则直接使用无参构造方法
return instantiateBean(beanName, mbd);
}

在Spring框架中的AbstractAutowireCapableBeanFactory类中的createBeanInstance()方法是用来执行实例化Bean对象的操作,该方法会根据Bean定义信息和配置选项,经过一系列步骤和逻辑判断,最终创建一个全新的Bean实例。大致步骤如下:

  • 根据BeanDefinition加载类并获取对应的Class对象
  • 如果BeanDefinition绑定了一个Supplier,那么应调用Supplier的get方法以获取一个对象,并将其直接返回。
  • 如果在BeanDefinition中存在factoryMethodName属性,则调用该工厂方法以获取一个bean对象,并将其返回。
  • 如果BeanDefinition已经自动构造过了,那么就调用autowireConstructor()方法来自动构造一个对象。
  • 调用SmartInstantiationAwareBeanPostProcessor接口的determineCandidateConstructors()方法,以确定哪些构造方法是可用的。
  • 如果存在可用的构造方法,或者当前BeanDefinition的autowired属性设置为AUTOWIRE_CONSTRUCTOR,或者BeanDefinition中指定了构造方法参数值,或者在创建Bean时指定了构造方法参数值,那么将调用autowireConstructor()方法来自动构造一个对象。
  • 最后,如果不符合前述情况,那么将根据不带参数的构造方法来实例化一个对象。

推断构造方法

我们看下源码:

public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
throws BeanCreationException {
//前面跟@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) {
Constructor<?>[] rawCandidates;
try {
// 拿到所有的构造方法
rawCandidates = beanClass.getDeclaredConstructors();
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Resolution of declared constructors on bean Class [" + beanClass.getName() +
"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
}
List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length); // 用来记录required为true的构造方法,一个类中只能有一个required为true的构造方法
Constructor<?> requiredConstructor = null;
// 用来记录默认无参的构造方法
Constructor<?> defaultConstructor = null;
......
int nonSyntheticConstructors = 0; // 遍历每个构造方法
for (Constructor<?> candidate : rawCandidates) {
if (!candidate.isSynthetic()) {
// 记录一下普通的构造方法
nonSyntheticConstructors++;
}
else if (primaryConstructor != null) {
continue;
} // 当前遍历的构造方法是否写了@Autowired
MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);
if (ann == null) {
// 如果beanClass是代理类,则得到被代理的类的类型,我们不看这种情况
.......
} // 当前构造方法上加了@Autowired
if (ann != null) {
// 整个类中如果有一个required为true的构造方法,那就不能有其他的加了@Autowired的构造方法
if (requiredConstructor != null) {
throw new BeanCreationException(beanName,
"Invalid autowire-marked constructor: " + candidate +
". Found constructor with 'required' Autowired annotation already: " +
requiredConstructor);
} boolean required = determineRequiredStatus(ann);
if (required) {
if (!candidates.isEmpty()) {
throw new BeanCreationException(beanName,
"Invalid autowire-marked constructors: " + candidates +
". Found constructor with 'required' Autowired annotation: " +
candidate);
}
// 记录唯一一个required为true的构造方法
requiredConstructor = candidate;
}
// 记录所有加了@Autowired的构造方法,不管required是true还是false
// 如果默认无参的构造方法上也加了@Autowired,那么也会加到candidates中
candidates.add(candidate); // 从上面代码可以得到一个结论,在一个类中,要么只能有一个required为true的构造方法,要么只能有一个或多个required为false的方法
}
else if (candidate.getParameterCount() == 0) {
// 记录唯一一个无参的构造方法
defaultConstructor = candidate;
} // 有可能存在有参、并且没有添加@Autowired的构造方法
} if (!candidates.isEmpty()) {
// Add default constructor to list of optional constructors, as fallback.
// 如果不存在一个required为true的构造方法,则所有required为false的构造方法和无参构造方法都是合格的
if (requiredConstructor == null) {
if (defaultConstructor != null) {
candidates.add(defaultConstructor);
}
else if (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));
}
}
// 如果只存在一个required为true的构造方法,那就只有这一个是合格的
candidateConstructors = candidates.toArray(new Constructor<?>[0]);
}
// 没有添加了@Autowired注解的构造方法,并且类中只有一个构造方法,并且是有参的
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
}
......
else {
// 如果有多个有参、并且没有添加@Autowired的构造方法,是会返回空的
candidateConstructors = new Constructor<?>[0];
}
this.candidateConstructorsCache.put(beanClass, candidateConstructors);
}
}
}
return (candidateConstructors.length > 0 ? candidateConstructors : null);
}

接下来,让我们更深入地探讨推断构造方法,并且我还简单绘制了一张图示。

通常情况下,一个类通常只包含一个构造方法:

要么是无参的构造方法,要么是有参的构造方法。

如果一个类只有一个无参构造方法,那么在实例化时只能使用这个构造方法;而如果一个类只有一个有参构造方法,实例化时是否可以使用这个构造方法则取决于具体情况:

  • 使用AnnotationConfigApplicationContext进行实例化时,Spring会根据构造方法的参数信息去寻找对应的bean,并将找到的bean传递给构造方法。这样可以实现依赖注入,确保实例化的对象具有所需的依赖项。
  • 当使用ClassPathXmlApplicationContext时,表明使用XML配置文件的方式来定义bean。在XML中,可以手动指定构造方法的参数值来实例化对象,也可以通过配置autowire=constructor属性,让Spring自动根据构造方法的参数类型去寻找对应的bean作为参数值,实现自动装配。

上面是只有一个构造方法的情况,那么如果一个类存在多个构造方法,那么Spring进行实例化之前,该如何去确定到底用哪个构造方法呢?

  • 如果开发者明确定义了他们想要使用的构造方法,那么程序将会优先使用这个构造方法。
  • 如果开发者没有明确指定他们想要使用的构造方法,系统会自动检查开发者是否已经配置了让Spring框架自动选择构造方法的选项。
  • 如果开发者没有显式地指定让Spring框架自动选择构造方法的情况下,Spring将会默认尝试使用无参构造方法。如果目标类中没有无参构造方法,则系统将会抛出错误提示。

针对第一点,开发者可以通过什么方式来指定使用哪个构造方法呢?

  • 在XML中,标签用于表示构造方法的参数。开发者可以根据这个标签确定所需使用的构造方法的参数个数,从而精确指定想要使用的构造方法。
  • 通过@Autowired注解,我们可以在构造方法上使用@Autowired注解。因此,当我们在特定构造方法上使用@Autowired注解时,表示开发者希望使用该构造方法。与通过xml方式直接指定构造方法参数值的方式不同,@Autowired注解方式需要Spring通过byType+byName的方式来查找符合条件的bean作为构造方法的参数值。
  • 当然,还有一种情况需要考虑,即当多个构造方法上都标注了@Autowired注解时,此时Spring会抛出错误。然而,由于@Autowired注解具有一个required属性,默认值为true,因此在一个类中只能有一个构造方法标注了@Autowired或者@Autowired(required=true),否则会导致错误。不过,可以有多个构造方法标注@Autowired(required=false)。在这种情况下,Spring会自动从这些构造方法中选择一个进行注入。

在第二种情况下,如果开发者没有明确指定要使用的构造方法,Spring将尝试自动选择一个合适的构造方法进行注入。这种情况下,只能通过ClassPathXmlApplicationContext实现,因为使用AnnotationConfigApplicationContext无法指定让Spring自动选择构造方法。通过ClassPathXmlApplicationContext,可以在XML配置文件中指定某个bean的autowire属性为constructor,从而告诉Spring可以自动选择构造方法。

总结

在Spring中,实例化Bean对象涉及构造方法的调用。通过分析源码,我们了解到实例化的步骤和推断构造方法的过程。当一个类只有一个构造方法时,Spring会根据具体情况决定是否使用该构造方法。如果一个类存在多个构造方法,就需要根据具体情况具体分析。本文简单判断了哪些构造方法是符合实例化的,但在存在多个符合条件的构造方法时,具体使用哪个构造方法尚未讨论。因此,我计划单独写一篇文章来详细讨论这个问题。毕竟是太复杂了。

解密Spring中的Bean实例化:推断构造方法(上)的更多相关文章

  1. 【Spring】Spring中的Bean - 2、Baen的实例化 (构造器、静态工厂、实例工厂)

    Bean的实例化 文章目录 Bean的实例化 构造器实例化 静态工厂方式实例化 实例工厂方式实例化 简单记录-Java EE企业级应用开发教程(Spring+Spring MVC+MyBatis)-S ...

  2. 第2章 Spring中的Bean

    2.1 Bean的配置 Bean本质是Java中的类.Spring可以被看做一个大型工厂,这个工厂的作用就是生产和管理Spring容器zho中的Bean.想在项目中使用这个工厂,就需要对Spring的 ...

  3. 【Spring】Spring中的Bean - 5、Bean的装配方式(XML、注解(Annotation)、自动装配)

    Bean的装配方式 简单记录-Java EE企业级应用开发教程(Spring+Spring MVC+MyBatis)-Spring中的Bean 文章目录 Bean的装配方式 基于XML的装配 基于注解 ...

  4. 【Spring】Spring中的Bean - 4、Bean的生命周期

    Bean的生命周期 简单记录-Java EE企业级应用开发教程(Spring+Spring MVC+MyBatis)-Spring中的Bean 了解Spring中Bean的生命周期有何意义? 了解Sp ...

  5. 传统javabean与spring中的bean的区别

    javabean已经没人用了 springbean可以说是javabean的发展, 但已经完全不是一回事儿了 用处不同:传统javabean更多地作为值传递参数,而spring中的bean用处几乎无处 ...

  6. 1.2(Spring学习笔记)Spring中的Bean

    一.<Bean>的属性及子元素 在1.1中我们对<Bean>有了初步的认识,了解了一些基本用法. 现在我们进一步理解<Bean>属性及子元素. 我们先来看下< ...

  7. spring(四):spring中给bean的属性赋值

    spring中给bean的属性赋值 xml文件properties标签设置 <bean id="student" class="com.enjoy.study.ca ...

  8. 【Spring】Spring中的Bean - 3、Bean的作用域

    Bean的作用域 简单记录-Java EE企业级应用开发教程(Spring+Spring MVC+MyBatis)-Spring中的Bean 通过Spring容器创建一个Bean的实例时,不仅可以完成 ...

  9. spring中bean的五种作用域?Spring中的bean是线程安全的吗?

    spring中bean的五种作用域 当通过spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域.Spring支持如下5种作用域: singleto ...

  10. JSP访问Spring中的bean

    JSP访问Spring中的bean <%@page import="com.sai.comment.po.TSdComment"%> <%@page import ...

随机推荐

  1. vue如何在render函数中使用判断(2)

    h函数的三个参数 第一个参数是必须的. 类型:{String | Object | Function} 一个 HTML 标签名.一个组件.一个异步组件.或一个函数式组件. 是要渲染的html标签. 第 ...

  2. 【代码片段】makefile 中通过 shell 函数执行 sed

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 先上代码:(在 macos 上调试通过) # define ...

  3. _0x4c9738 怎么还原?嘿,还真可以还原!

    _0x4c9738 变量名还原,噂嘟假嘟? 代码混淆(obfuscation)和代码反混淆(deobfuscation)在爬虫.逆向当中可以说是非常常见的情况了,初学者经常问一个问题,类似 _0x4c ...

  4. SqlSugar联表查询

    Join用法 语法糖1.2和3 在Where OrderBy GroupBy Select用法都一样的,他们区别就在JOIN的方式不一样,其它都一样 语法糖1 优点:好理解,5个表以内的联表非常爽,支 ...

  5. 语言模型的预训练[6]:思维链(Chain-of-thought,CoT)定义原理详解、Zero-shot CoT、Few-shot CoT 以及在LLM上应用

    大语言模型的预训练[6]:思维链(Chain-of-thought,CoT)定义原理详解.Zero-shot CoT.Few-shot CoT 以及在LLM上应用 1.思维链定义 背景 在 2017- ...

  6. Protobuf之proto基础语法

    目录 1.说明 2.字段类型 3.字段规则 4.字段编号 5.注释 6.类型 6.1.message 6.2.service 7.枚举enum 8.保留字段 9.import 9.1.protoc指令 ...

  7. CF145E Lucky Queries 题解

    题目链接:CF 或者 洛谷 前置知识点:序列操作 本文关键词 约定俗称:因为频繁敲最长不下降子序列 \(LNCS\) 和最长不上升子序列 \(LNIS\) 太麻烦了,下文将 \(000011111\) ...

  8. 【Flink入门修炼】1-2 Mac 搭建 Flink 源码阅读环境

    在后面学习 Flink 相关知识时,会深入源码探究其实现机制.因此,需要现在本地配置好源码阅读环境. 本文搭建环境: Mac M1(Apple Silicon) Java 8 IDEA Flink 官 ...

  9. [Spring 6.0源码解析] @Configuration注解源码解析

    Spring 6.0源码解析之@Configuration 首先写一个启动代码: public class ConfigurationAnnotationTest { private static f ...

  10. 深入 Nginx 之架构篇[转]

    前言 最近在读 Nginx 相关的书籍,做一下读书笔记. Nginx 作为业界知名的高性能服务器,被广泛的应用.它的高性能正是由于其优秀的架构设计,其架构主要包括这几点:模块化设计.事件驱动架构.请求 ...