当前位置: 首页 > news >正文

Spring Bean 为何“难产”?攻克构造器注入的依赖与歧义

本文已收录在Github,关注我,紧跟本系列专栏文章,咱们下篇再续!

  • 🚀 魔都架构师 | 全网30W技术追随者
  • 🔧 大厂分布式系统/数据中台实战专家
  • 🏆 主导交易系统百万级流量调优 & 车联网平台架构
  • 🧠 AIGC应用开发先行者 | 区块链落地实践者
  • 🌍 以技术驱动创新,我们的征途是改变世界!
  • 👉 实战干货:编程严选网

1 案例:定义的 Bean 缺少隐式依赖

有时把一个类定义成 Bean,又觉得这Bean定义除了加些 Spring 注解,好像平淡无奇!以致在后续使用时随心定义:

package com.javaedge.spring.service;@Service
public class MyService {private String myServiceName;public MyService(String myServiceName) {this.myServiceName = myServiceName;}public String sayHello() {return "hello Java";}
}

MyService显式定义了一个构造器。但上面代码不是永远都能正确运行,有时报错:

***********************************
APPLICATION FAILED TO START
***********************************Description:Parameter 0 of constructor in com.javaedge.spring.service.MyService required a bean of type 'java.lang.String' that could not be found.The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)Action:Consider defining a bean of type 'java.lang.String' in your configuration.

2 解惑

创建一个 Bean 时,调AbstractAutowireCapableBeanFactory的

2.1 createBeanInstance

  1. 寻找构造器
  2. 通过反射调用构造器创建实例
// Candidate constructors for autowiring?
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {return autowireConstructor(beanName, mbd, ctors, args);
}

2.2 determineConstructorsFromBeanPostProcessors

Spring先执行它获取构造器:

仅一个有效实现类。然后通过 autowireConstructor 带着构造器去创建实例。

看ConstructorResolver的

2.3 instantiate

autowireConstructor方法要创建实例:

  • 不仅要知道是啥构造器
  • 还要知道构造器对应参数

从最后创建实例的方法名也可看出:

private Object instantiate(String beanName, RootBeanDefinition mbd, Constructor<?> constructorToUse, Object[] argsToUse) 

argsToUse咋获取?即当已知构造器ServiceImpl(String serviceName),要创建 ServiceImpl 实例,咋确定serviceName值?

使用 Spring,不能直接显式 new 创建实例。Spring只能寻找依赖作为构造器调用参数。那这参数咋获取?

看ConstructorResolver的

2.4 autowireConstructor

argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);

可调用 createArgumentArray 构建调用构造器的参数数组,最终从 BeanFactory 获取 Bean:

即根据参数寻找对应 Bean。本案例中,如找不到对应 Bean,抛异常,提示装配失败。

3 修正

定义一个类为 Bean,若再显式定义构造器,则该 Bean 在构建时,会自动根据构造器参数定义寻找对应 Bean,反射创建该 Bean。可直接定义一个能让 Spring 装配给 MyService 构造器参数的 Bean,如:

package com.javaedge.spring.service;@Service
public class MyService {private String myServiceName;public String sayHello() {return "hello Java";}// 该bean装配给MyService的构造器参数-myServiceName@Beanpublic String myServiceName() {return "MyServiceName";}
}

综上,使用 Spring 时,别想当然认为定义的 Bean 也可在非 Spring 场合直接new!

若不精通 Spring 的隐式规则,在修正问题后,可能写出更多看起来好像可运行的程序:

@Service
public class MyService {private String myServiceName;public MyService(String myServiceName) {this.myServiceName = myServiceName;}public MyService(String myServiceName, String otherParam) {this.myServiceName = myServiceName;}

review这段代码,可能不会发现什么问题,毕竟 String 类型可自动装配,无非加个 String 参数。但若你知 Spring 内部用反射构建 Bean,就发现问题:存在两个构造器,都可调用时,到底调用哪个?最终 Spring 无从选择,只能尝试调用默认构造器,而默认构造器不存在:

所以报错:


文章转载自:

http://T59sHS1b.mxgpp.cn
http://7n6kDunu.mxgpp.cn
http://DFeLGm48.mxgpp.cn
http://qoyvTM6j.mxgpp.cn
http://YeEZPwzV.mxgpp.cn
http://qv3vjlm0.mxgpp.cn
http://L7qxeci4.mxgpp.cn
http://w528YPRx.mxgpp.cn
http://szo60pHO.mxgpp.cn
http://PHEJcQ05.mxgpp.cn
http://LpnTbs4G.mxgpp.cn
http://fNFw4H4b.mxgpp.cn
http://8G62mYlh.mxgpp.cn
http://nUv9IN6E.mxgpp.cn
http://mxaNOX8G.mxgpp.cn
http://GuTZFC0u.mxgpp.cn
http://lYMqBZRQ.mxgpp.cn
http://Owmrn61j.mxgpp.cn
http://cTVW3Qgq.mxgpp.cn
http://jbLRQYmo.mxgpp.cn
http://mpIVo6v8.mxgpp.cn
http://j3xyWubD.mxgpp.cn
http://EPx8Hufm.mxgpp.cn
http://qBvdILb1.mxgpp.cn
http://mreon7ky.mxgpp.cn
http://6ukgZ0Y3.mxgpp.cn
http://w5sciGbt.mxgpp.cn
http://LBAX44pv.mxgpp.cn
http://p7WxXbD0.mxgpp.cn
http://yk0EiIR2.mxgpp.cn
http://www.dtcms.com/a/229390.html

相关文章:

  • Q:知识库-文档的搜索框逻辑是怎样的?
  • 【论文解读】ReAct:从思考脱离行动, 到行动反馈思考
  • CAMEL-AI开源自动化任务执行助手OWL一键整合包下载
  • 普中STM32F103ZET6开发攻略(三)
  • 什么是 /proc/buddyinfo
  • redis缓存常见问题
  • 12.7 LangChain实战:1.2秒响应!用LCEL构建高效RAG系统,准确率提升41%
  • 力扣 88.合并两个有序数组
  • vscode配置lua
  • PowerShell脚本编程基础指南
  • 《认知觉醒》第二章——驯服你的“脑内大象”:理智、本能与情绪的共生之道
  • 【Harmony OS】数据存储
  • Modbus转Ethernet IP网关助力罗克韦尔PLC数据交互
  • 项目目标和期望未被清晰传达,如何改进?
  • 【计算机网络】第七章 运输层
  • 动态规划-数位DP
  • 【学习笔记】深度学习-过拟合解决方案
  • 基于Halcon深度学习之分类
  • 【bpmn.js 使用总结】最简单实现Palette
  • 在Mathematica中实现Newton-Raphson迭代
  • 从零打造AI面试系统全栈开发
  • 生成JavaDoc文档
  • [Java 基础]运算符,将盒子套起来
  • Qiskit:量子计算模拟器
  • 01-python爬虫-第一个爬虫程序
  • VueUse:组合式API实用函数全集
  • Spring Boot 自动配置原理:从入门到精通
  • 视频监控管理平台EasyCVR安防小知识:监控摄像头异响问题排查与处理
  • 嵌入式学习 D31:系统编程--Framebuf帧缓冲
  • 使用 Version Catalogs统一配置版本 (Gradle 7.0+ 特性)