别再乱用 @Autowired!Spring官方推荐的构造函数注入详解

别再乱用 @Autowired!Spring官方推荐的构造函数注入详解
在这里插入图片描述
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
👍《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

别再乱用 @Autowired!Spring官方推荐的构造函数注入详解

1. 前言

在我们日常开发SpringBoot项目中,依赖注入(Dependency Injection, DI)是核心机制之一,最常见的写法有两种:

字段注入:在类的属性上直接使用 @Autowired 注解
构造函数注入:通过构造函数参数注入依赖

相信不少小伙伴都喜欢在字段上直接使用@Autowired,因为字段注入用起来更简洁,但是Spring官方却明确推荐优先使用构造函数注入!这两种方式有何本质区别?为什么官方有明确的推荐?

本文博主将从代码对比、构造函数注入优点、实际单元测试场景 三个维度,带小伙伴们彻底搞懂其中的门道

在这里插入图片描述

2. 两种注入方式对比

假设我们有一个UserService,它依赖于UserMapper(数据访问层),我们需要在UserService中注入UserMapper

2.1 字段注入(@Autowired 在字段上)

这是很多小伙伴喜欢的方式,写法相对简洁直接:

@ServicepublicclassUserService{// 直接在字段上使用@Autowired注入@AutowiredprivateUserMapper userMapper;// 业务方法publicStringgetUser(Long id){return userMapper.findUserById(id);}}

2.2 构造函数注入(官方推荐)

通过类的构造方法注入依赖,无需在字段上添加注解:

@ServicepublicclassUserService{// 依赖被声明为final,确保不可变privatefinalUserMapper userMapper;// 通过构造函数注入publicUserService(UserMapper userMapper){this.userMapper = userMapper;}// 业务方法publicStringgetUser(Long id){return userMapper.findUserById(id);}}

使用Lombok 简化写法

如果你的项目中有使用了Lombok,那么构造函数注入的方式可以大大简化我们的写法,因为Lombok@AllArgsConstructor注解会自动为类中的所有 final 或非 static 字段生成一个全参数构造函数

在这里插入图片描述

为了演示 :我们假设还有一个订单Mapper也需要注入

@Service@AllArgsConstructor//Lombok注解publicclassUserService{// 依赖被声明为final,确保不可变privatefinalUserMapper userMapper;privatefinalOrderMapper orderMapper;/** * 无需手动编写构造函数,Lombok会在编译时生成: public UserService(UserMapper userMapper, OrderMapper orderMapper) { this.userMapper = userMapper; this.orderMapper = orderMapper; } **/// 业务方法publicStringgetUser(Long id){return userMapper.findUserById(id);}}

通过Lombok的注解大家会发现写法也简洁了很多!


3. 构造函数注入方式的优点

3.1 依赖不可变

通过上面的例子我们可以看出,使用构造函数注入可以声明把依赖声明为final

privatefinalUserMapper userMapper;

使用了final,字段在对象创建时必须初始化,且初始化后无法修改,这就表示了:

  • 依赖不会被意外篡改(线程安全
  • 强制依赖在对象创建时就必须注入(避免后续使用时为null

@Autowired字段注入无法使用final(因为字段注入是在对象创建后通过反射赋值的),依赖可能被中途修改,存在安全隐患

3.2 依赖明确,避免空指针

还是上述示例代码

publicUserService(UserMapper userMapper,OrderMapper orderMapper){this.userMapper = userMapper;this.orderMapper = orderMapper;}

小伙伴们会发现构造函数注入时,Spring容器会在创建UserService对象时,通过构造方法传入所有依赖(UserMapper、OrderMapper)。
如果依赖缺失(比如UserMapper未被 Spring 管理时),容器启动时就会报错,那么在项目启动阶段就能发现问题。

而字段注入是 “隐式” 的:如果依赖缺失,Spring不会在启动时报错,而只有在调用UserMapper时才抛出NullPointerException,项目运行时才会出现异常,增加了我们调试难度

在这里插入图片描述

3.3 更利于测试

使用字段注入依赖于 Spring 容器,而单元测试的核心原则是 “隔离依赖”,但字段注入严重依赖 Spring 容器,导致测试困难

3.4 避免循环依赖隐患

在构造阶段就能发现依赖关系异常,而不是运行时才出现异常。

如果 A 依赖 B,B 又依赖 A(循环依赖):

构造函数注入时Spring 容器启动阶段就会直接抛出BeanCurrentlyInCreationException,强制开发者解决循环依赖(比如通过拆分服务、引入中间层等),从根源上优化代码设计

字段注入时Spring 会通过 “三级缓存” 暂时解决循环依赖,但仅仅是一种的解决的妥协,本质上是代码设计问题

3.5 总结对比

两种注入方式的对比

特性构造函数注入字段注入(@Autowired)
依赖声明显式(构造方法参数)隐式(类内部字段)
依赖不可变支持(final修饰)不支持(无法用final)
依赖缺失检测启动时检测(快速失败)运行时检测(隐藏风险)
单元测试友好性高(直接传参,无需容器)低(需反射或Spring容器)
循环依赖处理启动时报错(强制解决设计问题)允许存在(依赖容器妥协)
与Spring耦合度低(无注解也可工作)高(必须依赖@Autowired)
代码可读性高(依赖一目了然)低(需通读字段)

4. 两种注入方式单元测试对比

上述讲解了两种注入方式的区别,我们用一个单元测试的示例来进行以下演示

4.1 字段注入的测试问题

假如我们用 字段注入,代码是这样的:

@ServicepublicclassUserService{@AutowiredprivateOrderService orderService;publicvoidcreateUser(){ orderService.createOrder();System.out.println("用户创建成功");}}

测试代码:

importorg.junit.jupiter.api.Test;importorg.mockito.Mockito;publicclassUserServiceFieldInjectionTest{@TestvoidtestCreateUser(){UserService userService =newUserService();// 由于是 private 字段注入,无法直接传入 mock// 只能用反射去强行设置OrderService mockOrderService =Mockito.mock(OrderService.class);try{java.lang.reflect.Field field =UserService.class.getDeclaredField("orderService"); field.setAccessible(true); field.set(userService, mockOrderService);}catch(Exception e){thrownewRuntimeException(e);} userService.createUser();Mockito.verify(mockOrderService).createOrder();}}
问题:
必须使用 反射 修改私有字段,测试代码繁琐、难维护
如果字段名修改了,测试就会失败

4.2 构造函数注入的测试优势

换成 构造函数注入 的写法:

@ServicepublicclassUserService{privatefinalOrderService orderService;// 构造函数注入publicUserService(OrderService orderService){this.orderService = orderService;}publicvoidcreateUser(){ orderService.createOrder();System.out.println("用户创建成功");}}

测试代码:

importorg.junit.jupiter.api.Test;importorg.mockito.Mockito;publicclassUserServiceConstructorInjectionTest{@TestvoidtestCreateUser(){// ✅ 直接通过构造函数传入 mockOrderService mockOrderService =Mockito.mock(OrderService.class);UserService userService =newUserService(mockOrderService); userService.createUser();Mockito.verify(mockOrderService).createOrder();}}
优势:
不需要反射,测试代码简洁。
依赖关系在构造函数中显式体现。
如果 UserService 有多个依赖,一眼就能看出它需要什么。

5. 题外话:字段注入真的就无用武之地?

并非所有场景都必须严格禁止字段注入。在一些非核心业务类(如配置类、工具类)中,若依赖简单且无需频繁测试,博主也推荐字段注入,例如:

@ConfigurationpublicclassAppConfig{@AutowiredprivateDataSource dataSource;// 简单配置类,偶尔使用字段注入也可接受// ...}

但核心业务逻辑类(Service、Mapper等),博主还是强烈建议使用构造函数注入!


6. 结语

本文详细介绍了Spring依赖注入的两种方式,从代码对比、构造函数注入优点、实际单元测试场景, 并给小伙伴们两种注入方式的使用建议,希望小伙伴们通过本文能测底理解Spring官方为什么推荐的构造函数注入。

总之下次写依赖注入时,不妨多敲几行构造函数 —— 未来的你会感谢这个决定。

如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家一键三连给博主一点点鼓励!


专栏最新回顾
【01】ThreadLocal的原理以及实际应用技巧详解 - 如何在身份认证场景Token中传递获取用户信息
【02】基于MyBatis-Plus Dynamic-Datasource实现 SaaS 系统动态租户数据源管理
【03】基于nacos实现动态线程池设计与实践:告别固定配置,拥抱弹性调度
【04】Java常用加密算法详解与实战代码 - 附可直接运行的测试示例
【05】Java synchronized 锁机制深度解析与实战指南 - 银行转账案例
【06】还在为线上BUG苦苦找寻?试试IntelliJ IDEA远程调试线上Java程序
【07】使用 Apache Commons Exec 自动化脚本执行实现 MySQL 数据库备份
【08】JAVA开发中几个常用的lambda表达式!记得收藏起来哦~
【09】看完!我不允许你还不知道 Spring Boot如何读取Resource目录文件
【10】分词搜索必须上Elasticsearch?试试MySQL分词查询,轻松满足大多数搜索场景的需求
【11】Java 状态机详解 - 三种状态机实现方式优雅消灭 if-else 嵌套
【12】别再踩坑!Spring事务@Transactional失效?一文读懂参数与8大失效场景

Read more

前端知识点梳理,前端面试复习

一:从输入 URL 到页面渲染是一个经典的综合性考题 1.URL 的标准组成部分 一个完整的 URL 结构如下: scheme://host:port/path?query#fragment URI 用字符串标识某一互联网资源,而URL 表示资源的地点(互 联网上所处的位置)。可见URL是URI 的子集。 URI 和 URL 的区别? * URI (Uniform Resource Identifier) 是统一资源标识符,是一个大概念。 * URL (Uniform Resource Locator) 是统一资源定位符,它不仅标识资源,还提供了找到资源的方式(比如协议)。可以理解为 URL 是 URI 的子集。 为什么 URL 中有些字符会被转义(

By Ne0inhk

OpenClaw Skills扩展:nanobot通过webhook对接钉钉/飞书,实现跨平台消息同步

OpenClaw Skills扩展:nanobot通过webhook对接钉钉/飞书,实现跨平台消息同步 1. nanobot简介 nanobot是一款受OpenClaw启发的超轻量级个人人工智能助手,仅需约4000行代码即可提供核心代理功能。相比传统方案,代码量减少了99%,但功能依然强大。 这个轻量级助手内置了vllm部署的Qwen3-4B-Instruct-2507模型,使用chainlit进行推理交互。最吸引人的是,你可以轻松配置它作为QQ聊天机器人使用,或者通过webhook对接企业通讯工具如钉钉和飞书。 2. 基础环境验证 2.1 检查模型服务状态 在开始扩展功能前,我们需要确认基础服务运行正常。通过以下命令检查模型部署状态: cat /root/workspace/llm.log 如果看到服务启动成功的日志信息,说明模型已准备就绪。常见的成功标志包括"Model loaded successfully"或"Service started on port xxxx"等提示。 2.2 测试基础问答功能

By Ne0inhk
计算机毕业设计springboot礼物商城的设计与实践 基于SpringBoot的个性化礼品电商平台的设计与实现 基于Java Web的创意礼物在线销售系统的设计与开发

计算机毕业设计springboot礼物商城的设计与实践 基于SpringBoot的个性化礼品电商平台的设计与实现 基于Java Web的创意礼物在线销售系统的设计与开发

计算机毕业设计springboot礼物商城的设计与实践917jxi80(配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。 1. 随着消费升级和社交需求的多元化发展,礼品经济正迎来前所未有的增长机遇。传统礼品采购模式存在选品单一、缺乏个性、购买不便等痛点,难以满足当代消费者对情感表达和独特体验的追求。与此同时,电子商务技术的成熟为礼品行业数字化转型提供了坚实基础,个性化定制与线上购物的深度融合成为行业发展的新趋势。本系统正是在此背景下应运而生,旨在构建一个集礼品展示、个性定制、便捷交易于一体的综合性电商平台,通过技术手段赋能传统礼品行业,提升用户送礼体验,推动礼品消费向品质化、个性化方向发展。 本系统采用SpringBoot作为核心开发框架,结合Vue前端技术实现前后端分离架构,选用MySQL数据库存储业务数据,B/S架构确保系统的可访问性和易维护性。系统围绕用户购物体验和管理者运营需求展开设计,涵盖从商品浏览到订单完成的全流程业务闭环。前台为用户提供礼品信息浏览、个性化搜索筛选、购物车管理、在线支付、订单跟踪

By Ne0inhk
【JavaWeb12】数据交换与异步请求:JSON与Ajax的绝妙搭配是否塑造了Web的交互革命?

【JavaWeb12】数据交换与异步请求:JSON与Ajax的绝妙搭配是否塑造了Web的交互革命?

文章目录🌍一. 数据交换--JSON❄️1. JSON介绍❄️2. JSON 快速入门❄️3. JSON 对象和字符串对象转换❄️4. JSON 在 java 中使用❄️5. 代码演示🌍二. 异步请求--Ajax❄️1. 基本介绍❄️2. JavaScript 原生 Ajax 请求❄️3. JQuery 的 Ajax 请求🌍三. 线程数据共享和安全 -ThreadLocal❄️1. ThreadLocal基本介绍❄️2. 源码分析 🙋‍♂️ 作者:@whisperrr.🙋‍♂️ 👀 专栏:JavaWeb👀 💥 标题:【JavaWeb12】数据交换与异步请求:JSON与Ajax的绝妙搭配是否塑造了Web的交互革命?💥 ❣️ 寄语:比较是偷走幸福的小偷❣️ 前言:

By Ne0inhk