Spring之推断构造方法源码解析

news2024/11/16 3:15:40

Spring之推断构造方法源码解析

1、推断构造方法流程图

https://www.processon.com/view/link/5f97bc717d9c0806f291d7eb

2、AutowiredAnnotationBeanPostProcessor中推断构造方法的不同情况分析

Untitled

https://www.processon.com/view/link/6146def57d9c08198c58bb26

// 有多个构造方法都可以使用
@Component
public class UserService {

    @Autowired
    private OrderService orderService;

    public UserService() {
        System.out.println("无参的构造方法");
    }

    public UserService(OrderService orderService) {
        this.orderService = orderService;
        System.out.println("参数个数为1的构造方法");
    }

    public UserService(OrderService orderService, OrderService orderService1) {
        this.orderService = orderService;
        System.out.println("参数个数为2的构造方法");
    }

    public void test() {
        System.out.println("orderService = " + orderService);
    }
}

// autowire为AUTOWIRE_CONSTRUCTOR
public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(UserService.class);
    beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
    context.registerBeanDefinition("userService", beanDefinition);
    UserService userService = (UserService) context.getBean("userService");
    userService.test(); // 打印:参数个数为2的构造方法
}

// beanDefinition中指定了构造方法的参数值
public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(UserService.class);
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(new OrderService());
    context.registerBeanDefinition("userService", beanDefinition);
    UserService userService = (UserService) context.getBean("userService");
    userService.test(); // 打印:参数个数为2的构造方法
}

// getBean()时指定了构造方法参数
public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    UserService userService = (UserService) context.getBean("userService", new OrderService());
    userService.test(); // 打印:参数个数为1的构造方法
}

Spring 中的一个 Bean,需要实例化才能得到一个对象,而实例化就需要用到构造方法。

一般情况下,一个类只有一个构造方法:

1、要么是无参的构造方法

2、要么是有参的构造方法

如果只有一个无参的构造方法,那么实例化就只能使用这个无参的构造方法了。

如果只有一个有参的构造方法,那么实例化时能使用这个有参的构造方法吗?要分情况讨论:

1、使用 AnnotationConfigApplicationContext,会使用这个构造方法进行实例化,那么 Spring 会根据构造方法的参数信息去寻找 Bean,然后传给构造方法

2、使用 ClassPathXmlApplicationContext,表示使用 XML 的方式来使用 Bean,要么在 XML 中指定构造方法的参数值(手动指定),要么配置autowire=constructor 让 Spring 自动去寻找 Bean 做为构造方法的参数值。

Untitled

上面是只有一个构造方法的情况,那如果有多个构造方法呢?

这里又分为两种情况,多个构造方法中存不存在无参的构造方法。

分析:一个类存在多个构造方法,那么 Spring 在进行实例化之前,该如何去确定到底用哪个构造方法呢?

1、如果程序员指定了想要使用的构造方法,那么就用这个指定的构造方法

2、如果程序员没有指定想要使用的构造方法,则看程序员有没有让 Spring 自动去选择构造方法(AUTOWIRE_CONSTRUCTOR)

3、如果程序员也没有让 Spring 自动去选择构造方法,则 Spring 利用无参的构造方法,如果没有无参的构造方法,则 Spring 会报错

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

1、xml 中的 <constructor-arg> 标签,这个标签表示构造方法参数,所以可以根据这个标签确定想要使用的构造方法的参数个数,从而确定想要使用的构造方法

2、通过 @Autowired 注解,@Autowired 注解可以写在构造方法上,所以哪个构造方法上写了@Autowired 注解,则表示程序员想使用该构造方法进行实例化,当然,它和第一个方式的不同点是,通过 xml 的方式,我们直接指定了构造方法的参数值,而通过 @Autowired 注解的方式,则需要 Spring 通过 byType + byName 的方式去找到符合条件的 Bean 作为构造方法的参数值

再来看第二点

如果程序员没有指定想要使用的构造方法,则看程序员有没有让 Spring 自动去选择构造方法,对于这一点,只能用在 ClassPathXmlApplicationContext,因为通过 AnnotationConfigApplicationContext 没有办法指定某个 bean 可以自动去选择构造方法,而通过 ClassPathXmlApplicationContext 可以在 xml 中指定某个 bean 的 autowire 为 constructor,这个属性表示通过构造方法进行自动注入,所以需要自动去选择一个构造方法进行自动注入,因为是构造方法,所以顺便进行实例化。

注意:自动选择是 xml 方式和注解方式都可以的,但是 xml 需要配置一下 autowire 参数

当然,还有一种情况,就是在多个构造方法上写了 @Autowired 注解,那么此时 Spring 会报错。

但是,因为 @Autowired 还有一个属性 required,默认为 ture,所以在一个类中,只能有一个构造方法标注了 @Autowired 或 @Autowired(required=true),如果有多个则会报错。但可以有多个 @Autowired(required=false),在这种情况下,需要 Spring 从这些构造方法中自动选择一个构造方法。

小结

1、默认情况下用无参的构造方法,或者只有一个构造方法就用那一个

2、程序员指定了构造方法的入参值,通过 getBean() 或者 beanDefinition.getConstructorArgumentValues().addGenericArgumentValue() 指定,那就用所匹配的构造方法

3、程序员想让 Spring 自动选择构造方法以及构造方法的入参值, 则通过 XML 方式的 autowire="constructor"

4、程序员通过 @Autowired 注解指定了某个构造方法,但是希望 Spring 自动找到该构造方法的入参值

3、源码分析

1、AbstractAutowireCapableBeanFactory 类中的 createBeanInstance() 方法会去创建一个 Bean 实例

2、根据当前 BeanDefinition 加载类得到 Class 对象

Untitled

3、如果当前 BeanDefinition 绑定了一个 Supplier,则调用 Supplier 的 get 方法得到一个对象并直接返回

Untitled

4、如果当前 BeanDefinition 中存在 factoryMethodName,则调用该工厂方法得到一个 Bean 对象并返回

Untitled

5、如果当前 BeanDefinition 已经自动构造过了,则调用 autowireConstructor() 自动构造一个对象

Untitled

Untitled

6、调用 SmartInstantiationAwareBeanPostProcessor 的 determineCandidateConstructors() 方法得到哪些构造方法是可以用的

Untitled

Untitled

7、如果存在可用的构造方法,或者当前 BeanDefinition 的 autowired 是 AUTOWIRE_CONSTRUCTOR,或者当前 BeanDefinition 中指定了构造方法的参数值,或者创建 Bean 的时候指定了构造方法的参数值,那就调用 autowireConstructor() 方法自动构造一个对象

8、最后,如果不符合上述情况,则根据无参的构造方法实例化一个对象

autowireConstructor( )

Untitled

Untitled

先检查是否指定了具体的构造方法和构造方法的参数值,或者在 BeanDefinition 中缓存了具体的构造方法或构造方法的参数值,如果存在则直接使用该构造方法进行实例化

如果没有确定的构造方法或构造方法的参数值,那么

1、如果没有确定的构造方法,那么则找出类中所有的构造方法

2、如果只有一个无参的构造方法,那么直接使用无参的构造方法进行实例化

3、如果有多个可用的构造方法或者当前 Bean 需要自动通过构造方法注入

4、根据所指定的构造方法参数值,确定所需要的构造方法的参数值的最少个数

Untitled

5、对所有的构造方法进行排序,参数个数多的排在前面

Untitled

6、遍历每个构造方法

7、如果没有通过调用 getBean() 方法指定构造方法的参数值,那么则根据构造方法的参数类型找值

Untitled

8、如果通过调用 getBean() 方法指定了构造方法的参数值,就直接利用这些值

Untitled

9、如果根据当前构造方法找到了对应的构造方法参数值,那么这个构造方法就是可用的,但是不一定这个构造方法就是最佳的,所以这里会涉及到是否有多个构造方法匹配了同样的值,这个时候就会用值和构造方法类型进行匹配程度的打分,找到一个最匹配的

Untitled

为什么分数越低优先级越高?

主要是计算找到的 Bean 和构造方法的参数类型匹配程度有多高。

假设 Bean 的类型为A,A的父类是B,B的父类是C,同时A实现了接口D

public class A extends B implements D {
}

public class B extends C {
}

public class C {
}

public interface D {
}

1、如果构造方法的参数类型为A,那么完全匹配,得分为0

2、如果构造方法的参数类型为B,那么得分为2

3、如果构造方法的参数类型为C,那么得分为4

4、如果构造方法的参数类型为D,那么得分为1

可以直接使用以下代码进行测试:

public static void main(String[] args) {
    Object[] objects = new Object[]{new A()};
    System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{A.class}, objects)); // 0
    System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{B.class}, objects)); // 2
    System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{C.class}, objects)); // 4
    System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{D.class}, objects)); // 1
}

4、@Bean的情况

首先,Spring 会把 @Bean 修饰的方法解析成 BeanDefinition:

1、如果方法不是 static 修饰的,那么解析出来的 BeanDefinition 中:

  • factoryBeanName 为 AppConfig 所对应的 beanName,比如:“appConfig”
  • factoryMethodName 为对应的方法名,比如:“aService”
  • factoryClass 为 AppConfig.class

2、如果方法是 static 修饰的,那么解析出来的 BeanDefinition 中:

  • factoryBeanName 为 null
  • factoryMethodName 为对应的方法名,比如:“aService”
  • factoryClass 为 AppConfig.class

在由 @Bean 生成的 BeanDefinition 中,有一个重要的属性 isFactoryMethodUnique,表示 factoryMethod 是不是唯一的,在普通情况下 @Bean 生成的BeanDefinition 中的 isFactoryMethodUnique 属性默认是为 true 的,但如果出现了方法重载,那么就是特殊的情况,比如:

@Bean
public static AService aService(){
		return new AService();
}

@Bean
public AService aService(BService bService){
		return new AService();
}

虽然有两个 @Bean,但结果肯定只会生成一个 aService 的 Bean,那么 Spring 在处理 @Bean 时,也只会生成一个 aService 的 BeanDefinition,比如 Spring 先解析到第一个 @Bean,会生成一个 BeanDefinition,此时 isFactoryMethodUnique 属性为 true,但是当解析到第二个 @Bean 时,会判断出来beanDefinitionMap 中已经存在一个 aService 的 BeanDefinition 了,那么会把之前的这个 BeanDefinition 的 isFactoryMethodUnique 属性值修改为 false,并且不会生成新的 BeanDefinition 了。

并且后续在根据 BeanDefinition 创建 Bean 时,会根据 isFactoryMethodUnique 属性来操作,如果为 true,那就表示当前 BeanDefinition 只对应了一个方法,那也就是只能用这个方法来创建 Bean 了,但是如果 isFactoryMethodUnique 属性为 false,那就表示当前 BeanDefition 对应了多个 @Bean 方法,需要和推断构造方法的逻辑一样,去选择用哪个方法来创建 Bean。

5、@Lookup的情况

@Component
public class UserService {

    @Autowired
    private OrderService orderService;

    public void test() {
        System.out.println(a());
    }

    @Lookup("orderService")
    public OrderService a() {
        return null;
    }
}

@Component
@Scope("prototype")
public class OrderService {
}
public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        userService.test();
        userService.test();
        userService.test();
    }

}

Untitled

源码分析

1、org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

Untitled

2、org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance

Untitled

3、org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#determineConstructorsFromBeanPostProcessors

Untitled

4、org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors

Untitled

5、org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#instantiateBean

Untitled

6、org.springframework.beans.factory.support.InstantiationStrategy

Untitled

7、org.springframework.beans.factory.support.SimpleInstantiationStrategy#instantiate(org.springframework.beans.factory.support.RootBeanDefinition, java.lang.String, org.springframework.beans.factory.BeanFactory)

Untitled

8、org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy.CglibSubclassCreator#instantiate

Untitled

9、org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy.LookupOverrideMethodInterceptor#intercept

Untitled

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/359230.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Word控件Spire.Doc 【Table】教程(17):如何在 C#、VB.NET 中删除 Word 表格中的行和列

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

【CS224图机器学习】task4 图嵌入表示学习

前言&#xff1a;本期学习是由datawhale&#xff08;公众号&#xff09;组织&#xff0c;由子豪兄讲解的202302期CS224图机器学习的学习笔记。本次学习为图嵌入表示学习&#xff0c;主要通过无监督或半监督的方法让图中的信息表示为指定尺寸向量。不用人工完成特征工程&#xf…

CCNP350-401学习笔记(301-350题)

301、Drag and drop the virtual component from the left onto their descriptions on the right. 302、Which two actions, when applied in the LAN network segment, will facilitate Layer 3 CAPWAP discovery for lightweight AP? (Choose two.)A. Utilize DHCP option …

37k*16 薪,年后直接上岗,3年自动化测试历经3轮面试成功拿下阿里Offer....

前言 转眼过去&#xff0c;距离读书的时候已经这么久了吗&#xff1f;&#xff0c;从18年5月本科毕业入职了一家小公司&#xff0c;到现在快4年了&#xff0c;前段时间社招想着找一个新的工作&#xff0c;前前后后花了一个多月的时间复习以及面试&#xff0c;前几天拿到了阿里…

Yield Guild Games:社区更新——2022 年第四季度

在这篇文章中&#xff0c;Yield Guild Games&#xff08;YGG&#xff09;分享了 2022 年第 4 季度社区更新的主要内容&#xff0c;包括公会发展计划&#xff08;GAP&#xff09;的最新细节&#xff0c;公会在电竞领域的持续发展&#xff0c;最新的合作伙伴关系&#xff0c;以及…

git中git push origin master推送远程操作失败,报错解决方案

报错图片如下所示: 解决方案: 使用下面代码进行本地与远程仓库的链接: git remote add origin http://xxxxx///xxx(https://gitee.com/peach-fog/shopping-cart-car-warehouse.git)链接完成之后就会输出:fatal: remote origin already exists. 链接完成之后就需要使用git br…

知识蒸馏论文阅读:FGD算法笔记

标题&#xff1a;Focal and Global Knowledge Distillation for Detectors 会议&#xff1a;CVPR2022 论文地址&#xff1a;https://ieeexplore.ieee.org/document/9879869/ 官方代码&#xff1a;https://github.com/yzd-v/FGD 作者单位&#xff1a;清华大学深圳国际研究生院、…

【mircopython】ESP32配置与烧录版本

下载ESP32的Micropython固件 官方连接https://www.micropython.org/download/esp32/ 看了下描述&#xff0c;上面的是IDF4.x系列编译&#xff0c;下面是IDF3.x系列编译&#xff0c;我们默认选新的 下载安装CP2102驱动 CP210x USB to UART Bridge VCP Drivers - Silicon Labs…

自托管提醒平台Noted Reminders

什么是 Noted Reminders ? Noted 是一个简单的自托管应用程序&#xff0c;用于创建使用 Apprise API 推送到设备的提醒。您可以向几乎每个平台发送消息&#xff0c;包括定时电子邮件&#xff01; 什么是 Apprise API &#xff1f; Apprise 允许您向我们今天可用的几乎所有最流…

OpenSumi 是信创开发云的首选

原文作者&#xff1a;行云创新技术总监 邓冰寒 引言 随着云原生应用的日益普及&#xff0c;开发上云也逐步被越来越多的厂商和开发者接受&#xff0c;在这个赛道国内外有不少玩家&#xff0c;国外的 GitHub Codespaces、CodeSandbox&#xff0c;GitPod、亚马逊 Cloud9&#xf…

Pytorch GPU版本简明下载安装教程

1.根据自己的显卡型号下载显卡驱动并安装。这一步会更新你的显卡驱动&#xff0c;也可忽略第1步&#xff0c;如果第2步出现问题&#xff0c;返回执行第1步。 点击这里下载英伟达显卡驱动 2.安装完成后&#xff0c;wincmd打开命令行&#xff0c;输入nvidia-smi&#xff0c;查看…

分享三个可以在家做的正规兼职工作,看到就是赚到

你可以在家做正式的兼职工作。在线兼职工作值得考虑&#xff0c;时间相对自由。在线兼职收入可能不如线下滴滴和外卖立竿见影&#xff0c;但仍然可以坚持收入。有些人比工作工资发展得更高。当然&#xff0c;天上不会有馅饼&#xff0c;不劳无获。那么有哪些正规的兼职可以在家…

【C++】引用、内联函数、auto关键字、范围for、nullptr

引用什么叫引用引用的特性常引用使用场景传值、传引用效率比较引用和指针的区别内联函数auto关键字(C11)基于范围的for循环(C11)指针空值nullptr(C11)引用 什么叫引用 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内…

《python3网络爬虫开发实战 第二版》之基本库的使用-requests的使用 详解

文章目录1 requests库的使用1.1 准备工作1.2 实例引入1. 3 GET请求1.3.1 基本实例1.3.2 抓取网页1.3.3 抓取二进制数据1.3.4 添加请求头1.4 POST请求1.5 响应1.6 高级用法1.6.1 文件上传1.6.2 Cookie设置1.6.3 Session维持1.6.4 SSL证书验证1.6.5 超时设置1.6.6 身份认证1.6.7 …

旅游地如何搭好影视剧“顺风车”

新春伊始&#xff0c;《满江红》《三体》《狂飙》等影视剧给影片取景地带来的关注和旅游热潮引人瞩目——从太原古县城到襄阳古城墙上的“岳”字砖、从宁波博物馆到江门的历史文化街区乃至地方特产新会陈皮……都被影视剧带上了热搜。影视作品与取景地的相互成全由来已久&#…

JavaWab开发的总括以及HTML知识

一、Web开发的总括在这里我来给大家介绍一下Wab开发需要配合哪些前后端的对应语言:首先是Java(Java通常的工作):Wab开发android开发大数据开发另外,Wab开发想要学好就需要配合之前博客中的内容,如:多线程/IO/网络/数据结构/数据库......这里建议学懂前面的内容再往下走.JavaWab…

2023年,都在说软件测试饱和了,大环境不好?为何每年还会增加40万测试员?

最近和一些刚进入软件测试行业的朋友交流&#xff0c;发现了一个有趣的现象&#xff0c;那就是对这个行业很多问题的认识是一致的、片面的&#xff0c;也可以理解为误解。利用你的时间列出他们对这个行业的所有误解&#xff0c;然后结合你多年的工作经验和你交流。毕竟你是从这…

栈的压入,弹出序列-剑指Offer-java

一、题目描述输入两个整数序列&#xff0c;第一个序列表示栈的压入顺序&#xff0c;请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如&#xff0c;序列 {1,2,3,4,5} 是某栈的压栈序列&#xff0c;序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列&a…

2019蓝桥杯真题修改数组 C语言/C++

题目描述 给定一个长度为 N 的数组 A [A_1,A_2, ,A_N]&#xff0c;数组中有可能有重复出现的整数。 现在小明要按以下方法将其修改为没有重复整数的数组。小明会依次修改A_2,A_3, ,A_N当修改 A_i时&#xff0c;小明会检查 A_i是否在 A_1 ∼ A_i−1中出现过。如果出现过&#…

2023年2月访问学者博士后热门国家出入境政策变化汇总

近期关于出国的咨询量日益增多&#xff0c;出入境政策也是其中之一。所以本期知识人网小编汇总了最新访问学者和博士后关注的热门国家及地区入境政策变化&#xff0c;提供给大家。目前各国入境政策大致分为三种&#xff1a;一、 无法入境的国家如&#xff1a;摩洛哥、朝鲜等。二…