依赖注入(dependency injection,di)是 spring 框架的核心特性之一,也是实现 ioc(控制反转)的具体方式。spring 提供了多种依赖注入方式,每种方式各有优劣。本文系统讲解构造器注入、字段注入、setter 注入、方法注入的用法与区别,并涵盖 @autowired 与 @resource 的差异、循环依赖问题、最佳实践等内容。
一、依赖注入的基本概念
1.1 什么是依赖注入
依赖注入是指对象不再自行创建其依赖项,而是由容器(spring ioc 容器)在运行时将依赖项注入到对象中。
传统方式(手动创建依赖):controller ──new──→ service ──new──→ repository,对象自己控制依赖的创建,耦合度高
依赖注入(容器管理依赖):
- container ──注入──→ controller
- container ──注入──→ service
- container ──注入──→ repository
容器统一管理对象的创建和装配,对象只声明需要的依赖
1.2 依赖注入的三个角色
| 角色 | 说明 | 示例 |
|---|---|---|
| 依赖者 | 需要其他组件的对象 | usercontroller |
| 依赖项 | 被需要的组件 | userservice |
| 注入者 | 负责将依赖项注入依赖者 | spring ioc 容器 |
1.3 spring 依赖注入方式总览
| 注入方式 | 注解 / 配置 | 特点 |
|---|---|---|
| 构造器注入 | 构造方法 + @autowired(可省略) | 官方推荐,不可变,可保证完全初始化 |
| 字段注入 | @autowired 标注在字段上 | 简洁,但不推荐 |
| setter 注入 | @autowired 标注在 setter 方法上 | 可选依赖,可重新注入 |
| 方法注入 | @autowired 标注在普通方法上 | 灵活,可注入多个依赖 |
| lookup 方法注入 | @lookup | 解决单例注入原型的问题 |
二、构造器注入
2.1 基本用法
通过构造方法将依赖传入类中。spring 4.3+ 如果类只有一个构造方法,@autowired 可以省略。
import org.springframework.stereotype.service;
/**
* 订单服务
*/
@service
public class orderservice {
private final userservice userservice;
private final orderrepository orderrepository;
/**
* 构造器注入(spring 4.3+ 单构造方法可省略 @autowired)
*/
public orderservice(userservice userservice, orderrepository orderrepository) {
this.userservice = userservice;
this.orderrepository = orderrepository;
}
public void createorder(long userid) {
// 使用注入的依赖
userservice.getuser(userid);
orderrepository.save(null);
}
}
2.2 结合 lombok 简化
实际项目中最常见的写法,使用 @requiredargsconstructor 自动生成构造方法:
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.service;
/**
* 订单服务
*/
@slf4j
@service
@requiredargsconstructor
public class orderservice {
private final userservice userservice; // final 字段
private final orderrepository orderrepository; // final 字段
public void createorder(long userid) {
userservice.getuser(userid);
orderrepository.save(null);
}
}
关键:@requiredargsconstructor 只为 final 字段和 @nonnull 标注的字段生成构造方法。非 final 字段不会包含在构造方法中。
2.3 多构造方法场景
当类有多个构造方法时,必须用 @autowired 指定主构造方法:
@service
public class orderservice {
private final userservice userservice;
private final orderrepository orderrepository;
private final logservice logservice; // 可选依赖
/**
* 主构造方法(spring 使用此构造方法实例化)
*/
@autowired
public orderservice(userservice userservice, orderrepository orderrepository) {
this.userservice = userservice;
this.orderrepository = orderrepository;
this.logservice = null;
}
/**
* 辅助构造方法(不参与 spring 注入)
*/
public orderservice(userservice userservice, orderrepository orderrepository,
logservice logservice) {
this.userservice = userservice;
this.orderrepository = orderrepository;
this.logservice = logservice;
}
}
2.4 优缺点
| 优点 | 缺点 |
|---|---|
依赖不可变(final 字段),线程安全 | 依赖较多时构造方法参数过多 |
| 对象创建即完全初始化,不会出现 npe | 无法注入循环依赖(但这本身是好事) |
| 易于单元测试(可直接 new 传入 mock) | 需要配合 lombok 减少样板代码 |
| 明确声明所有必需依赖 | |
| spring 官方推荐 |
三、字段注入
3.1 基本用法
直接在字段上标注 @autowired,spring 通过反射注入依赖:
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;
/**
* 订单服务(字段注入 - 不推荐)
*/
@service
public class orderservice {
@autowired
private userservice userservice;
@autowired
private orderrepository orderrepository;
public void createorder(long userid) {
userservice.getuser(userid);
orderrepository.save(null);
}
}
3.2 优缺点
| 优点 | 缺点 |
|---|---|
| 代码简洁,最少样板代码 | 无法使用 final,依赖可变,不安全 |
| 不需要构造方法或 setter | 单元测试困难(无法通过构造方法传入 mock) |
| 隐藏了依赖关系(类表面看不到依赖) | |
| 违反单一职责(加依赖太容易,容易过度膨胀) | |
| 依赖注入发生在对象创建之后,可能存在半初始化状态 | |
| spring 官方不推荐 |
spring 官方建议:从 spring 4.0 开始,官方文档明确推荐构造器注入,不推荐字段注入。
3.3 为什么字段注入仍被广泛使用
尽管不推荐,字段注入在实际项目中仍很常见,原因:
- 代码量少,开发效率高
- 早期 spring 教程普遍使用字段注入
- 依赖较少时,缺点不明显
但这是技术债务,建议在新项目中避免使用。
四、setter 注入
4.1 基本用法
通过 setter 方法注入依赖:
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;
/**
* 订单服务(setter 注入)
*/
@service
public class orderservice {
private userservice userservice;
private orderrepository orderrepository;
@autowired
public void setuserservice(userservice userservice) {
this.userservice = userservice;
}
@autowired
public void setorderrepository(orderrepository orderrepository) {
this.orderrepository = orderrepository;
}
public void createorder(long userid) {
userservice.getuser(userid);
orderrepository.save(null);
}
}
4.2 可选依赖
通过 required = false 标记可选依赖,当容器中找不到对应 bean 时不报错:
@autowired(required = false)
public void setlogservice(logservice logservice) {
this.logservice = logservice;
}
注意:构造器注入不支持 required = false。如果需要可选依赖,必须使用 setter 注入。
4.3 优缺点
| 优点 | 缺点 |
|---|---|
支持可选依赖(required = false) | 依赖可变(非 final),不保证不变性 |
| 可以在对象创建后重新注入(热替换) | 对象可能处于未完全初始化状态 |
| 单元测试方便(可调用 setter 传入 mock) | 代码量比字段注入多 |
| 解决循环依赖(与构造器注入配合) |
五、方法注入
5.1 基本用法
在任意方法上标注 @autowired,可以一次注入多个依赖:
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;
/**
* 订单服务(方法注入)
*/
@service
public class orderservice {
private userservice userservice;
private orderrepository orderrepository;
@autowired
public void configuredependencies(userservice userservice, orderrepository orderrepository) {
this.userservice = userservice;
this.orderrepository = orderrepository;
}
public void createorder(long userid) {
userservice.getuser(userid);
orderrepository.save(null);
}
}
5.2 优缺点
| 优点 | 缺点 |
|---|---|
| 灵活,可一次注入多个依赖 | 不如构造器注入清晰 |
| 方法名可自定义,语义更灵活 | 使用场景少 |
| 可以在方法中添加初始化逻辑 |
使用场景:较少使用,通常构造器注入或 setter 注入已足够。适合需要同时设置多个相关依赖并执行初始化逻辑的场景。
六、@autowired 与 @resource 的区别
@autowired 和 @resource 都用于依赖注入,但来源和行为不同。
6.1 基本对比
| 特性 | @autowired | @resource |
|---|---|---|
| 来源 | spring(org.springframework.beans.factory.annotation) | jsr-250 标准(jakarta.annotation) |
| 默认匹配方式 | 按类型(bytype) | 按名称(byname) |
| 找不到时的行为 | required = false 可设为可选 | 无法设为可选,找不到直接报错 |
| 配合注解 | @qualifier 指定 bean 名称 | name 属性直接指定 |
| 适用位置 | 构造器、字段、setter、方法 | 字段、setter(不支持构造器) |
| 框架依赖 | 仅 spring | 标准 jsr-250,其他框架也支持 |
6.2 @autowired 匹配流程
@autowired private userservice userservice;
匹配流程:
1. 按类型查找 userservice 的 bean
2. 找到唯一 → 注入 ✅
3. 找到多个 → 按字段名 "userservice" 匹配 bean 名称
4. 仍不唯一 → 报错 nouniquebeandefinitionexception
5. 配合 @qualifier("beanname") → 按指定名称注入
示例:
// 多个实现类场景
public interface paymentservice {
void pay(string orderid);
}
@service("alipayservice")
public class alipayserviceimpl implements paymentservice {
@override
public void pay(string orderid) {
system.out.println("支付宝支付: " + orderid);
}
}
@service("wechatpayservice")
public class wechatpayserviceimpl implements paymentservice {
@override
public void pay(string orderid) {
system.out.println("微信支付: " + orderid);
}
}
多实现场景的注入方式:
// 方式1:@autowired + @qualifier(按名称指定)
@autowired
@qualifier("alipayservice")
private paymentservice paymentservice;
// 方式2:字段名与 bean 名称一致(spring 自动匹配)
@autowired
private paymentservice alipayservice; // 字段名 "alipayservice" 匹配 bean 名称
// 方式3:@primary 标注默认实现
@service
@primary // 多个实现时,@autowired 优先注入标注了 @primary 的
public class alipayserviceimpl implements paymentservice { }
6.3 @resource 匹配流程
@resource private userservice userservice;
匹配流程:
1. 如果指定了 name → 只按名称查找
2. 如果未指定 name → 先按字段名 "userservice" 查找
3. 找不到 → 再按类型查找
4. 仍找不到 → 报错 nosuchbeandefinitionexception
示例:
// 方式1:默认按字段名匹配 @resource private paymentservice alipayservice; // 按名称 "alipayservice" 查找 // 方式2:指定 name @resource(name = "wechatpayservice") private paymentservice paymentservice; // 方式3:指定 type @resource(type = alipayserviceimpl.class) private paymentservice paymentservice;
6.4 选型建议
| 场景 | 推荐 |
|---|---|
| spring 项目,构造器注入 | @autowired(@resource 不支持构造器) |
| 需要按名称注入 | @autowired + @qualifier |
| 跨框架兼容 | @resource(jsr-250 标准) |
| 简单项目 | 均可,团队统一即可 |
spring 官方推荐:优先使用 @autowired,构造器注入时 @autowired 可省略。
七、@autowired 的 required 属性
7.1 必需依赖 vs 可选依赖
// 必需依赖(默认 required = true) // 容器中找不到对应 bean 会启动失败 @autowired private userservice userservice; // 可选依赖(required = false) // 容器中找不到对应 bean 不报错,注入 null @autowired(required = false) private logservice logservice;
7.2 各注入方式对 required 的支持
| 注入方式 | 支持 required = false | 说明 |
|---|---|---|
| 构造器注入 | 不支持 | 构造方法参数始终是必需的 |
| 字段注入 | 支持 | @autowired(required = false) |
| setter 注入 | 支持 | @autowired(required = false) |
| 方法注入 | 支持 | @autowired(required = false) |
7.3 optional 替代方案
java 8+ 推荐用 optional 声明可选依赖,语义更清晰:
import java.util.optional;
@service
public class orderservice {
private final userservice userservice;
private final optional<logservice> logservice;
public orderservice(userservice userservice, optional<logservice> logservice) {
this.userservice = userservice;
this.logservice = logservice;
}
public void createorder(long userid) {
userservice.getuser(userid);
// 使用前判断是否存在
logservice.ifpresent(service -> service.log("创建订单: " + userid));
}
}
注意:optional 只能用于构造器注入和 setter 注入,不能用于字段注入。
八、循环依赖问题
8.1 什么是循环依赖
两个或多个 bean 互相依赖,形成闭环:
a 依赖 b,b 依赖 a → 循环依赖
@service
class a {
@autowired
b b; // a 需要 b
}
@service
class b {
@autowired
a a; // b 需要 a
}
8.2 spring 如何解决循环依赖
spring 通过三级缓存机制解决 setter/字段注入的循环依赖:
| 缓存级别 | 名称 | 存储内容 |
|---|---|---|
| 一级缓存 | singletonobjects | 完全初始化好的 bean(成品) |
| 二级缓存 | earlysingletonobjects | 提前暴露的半成品 bean(已实例化,未注入依赖) |
| 三级缓存 | singletonfactories | bean 工厂(objectfactory,用于生成半成品) |
解决循环依赖的流程(a 依赖 b,b 依赖 a):
1. 创建 a:实例化 a → 将 a 的工厂放入三级缓存
2. 注入 a 的依赖 b → 创建 b
3. 创建 b:实例化 b → 将 b 的工厂放入三级缓存
4. 注入 b 的依赖 a → 查找 a
→ 一级缓存没有
→ 二级缓存没有
→ 三级缓存有 a 的工厂 → 生成 a 的半成品 → 放入二级缓存 → 删除三级缓存
5. b 拿到 a 的半成品 → b 注入完成 → b 放入一级缓存
6. a 拿到 b → a 注入完成 → a 放入一级缓存
8.3 构造器注入的循环依赖
构造器注入的循环依赖无法解决!
- a 的构造方法需要 b → 创建 b
- b 的构造方法需要 a → 创建 a
- a 的构造方法需要 b → ... 死循环
spring 会抛出:beancurrentlyincreationexception
8.4 各注入方式的循环依赖支持
| 注入方式 | 是否支持循环依赖 | 说明 |
|---|---|---|
| 构造器注入 | ❌ 不支持 | 无法实例化任何一方,直接报错 |
| 字段注入 | ✅ 支持 | 通过三级缓存解决 |
| setter 注入 | ✅ 支持 | 通过三级缓存解决 |
| 方法注入 | ✅ 支持 | 通过三级缓存解决 |
关键理解:构造器注入不支持循环依赖,这其实是优点而非缺点。循环依赖通常意味着设计有问题,构造器注入强制你面对并解决它。
8.5 循环依赖的解决方案
| 方案 | 说明 | 适用场景 |
|---|---|---|
| 重构消除循环 | 提取公共逻辑到第三个类 | 首选方案 |
| 改为 setter 注入 | 将其中一方改为 setter 注入 | 临时方案,不推荐长期保留 |
| 使用 @lazy | 延迟注入代理对象 | 临时方案 |
| 事件机制解耦 | 用事件代替直接调用 | 适合通知类场景 |
@lazy 方案:
@service
public class orderservice {
private final userservice userservice;
// @lazy 注入代理对象,首次使用时才真正初始化
public orderservice(@lazy userservice userservice) {
this.userservice = userservice;
}
}
九、lookup 方法注入
9.1 问题场景
单例 bean 注入原型 bean 时,原型 bean 只在注入时创建一次,之后不再创建新的:
@service
public class orderservice {
// 希望每次使用都是新的实例,但实际只创建了一次
@autowired
private prototypebean prototypebean; // 始终是同一个对象 ❌
}
@scope("prototype")
@component
public class prototypebean {
// 原型作用域,应该每次获取都创建新实例
}
9.2 解决方案:@lookup
import org.springframework.beans.factory.annotation.lookup;
import org.springframework.stereotype.service;
/**
* 订单服务
*/
@service
public abstract class orderservice {
/**
* lookup 方法注入:每次调用都返回新的 prototypebean 实例
* spring 通过 cglib 动态子类化重写此方法
*/
@lookup
protected abstract prototypebean getprototypebean();
public void processorder(string orderid) {
// 每次调用 getprototypebean() 都返回新实例
prototypebean bean = getprototypebean();
bean.dosomething(orderid);
}
}
注意:@lookup 标注的方法必须是抽象方法(或返回值与 bean 类型匹配的具体方法),spring 通过 cglib 生成子类重写该方法。因此类不能是 final,方法也不能是 final/private。
9.3 替代方案:objectfactory
如果不想用抽象类,可以用 objectfactory:
import org.springframework.beans.factory.objectprovider;
import org.springframework.stereotype.service;
@service
public class orderservice {
private final objectprovider<prototypebean> prototypebeanprovider;
public orderservice(objectprovider<prototypebean> prototypebeanprovider) {
this.prototypebeanprovider = prototypebeanprovider;
}
public void processorder(string orderid) {
// 每次调用 getobject() 都返回新实例
prototypebean bean = prototypebeanprovider.getobject();
bean.dosomething(orderid);
}
}
优势:objectprovider 是构造器注入友好的方案,不需要抽象类,推荐使用。
十、泛型注入
spring 支持泛型类型的依赖注入,通过类型参数自动匹配对应实现。
10.1 泛型基类
/**
* 通用 repository 基类
* @param <t> 实体类型
*/
public abstract class baserepository<t> {
public abstract t findbyid(long id);
public abstract void save(t entity);
}
10.2 具体实现
import org.springframework.stereotype.repository;
@repository
public class userrepository extends baserepository<user> {
@override
public user findbyid(long id) {
return new user(id, "张三");
}
@override
public void save(user entity) {
system.out.println("保存用户: " + entity);
}
}
@repository
public class orderrepository extends baserepository<order> {
@override
public order findbyid(long id) {
return new order(id, 100.0);
}
@override
public void save(order entity) {
system.out.println("保存订单: " + entity);
}
}
10.3 泛型 service 注入
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;
/**
* 通用 service 基类
* spring 根据泛型参数自动匹配对应的 repository
*/
public abstract class baseservice<t> {
protected baserepository<t> repository;
@autowired
public void setrepository(baserepository<t> repository) {
this.repository = repository;
}
public t getbyid(long id) {
return repository.findbyid(id);
}
public void save(t entity) {
repository.save(entity);
}
}
@service
public class userservice extends baseservice<user> {
// spring 自动注入 userrepository(因为 baserepository<user> 匹配 userrepository)
}
@service
public class orderservice extends baseservice<order> {
// spring 自动注入 orderrepository(因为 baserepository<order> 匹配 orderrepository)
}
使用:
@autowired private userservice userservice; userservice.getbyid(1l); // 调用 userrepository.findbyid() userservice.save(new user()); // 调用 userrepository.save()
优势:泛型注入实现了代码复用,避免了为每个实体重复编写 crud 方法。
十一、bean 生命周期回调与注入
11.1 注入时机
bean 生命周期中的注入时机:
1. 实例化(instantiation) → 构造方法调用
2. 属性填充(population) → 依赖注入发生在此阶段
3. 初始化(initialization)
├── @postconstruct → 注入完成后执行
├── initializingbean.afterpropertiesset()
└── @bean(initmethod=...)
4. 使用(in use)
5. 销毁(destruction)
├── @predestroy
├── disposablebean.destroy()
└── @bean(destroymethod=...)
11.2 @postconstruct 中使用注入的依赖
import jakarta.annotation.postconstruct;
import jakarta.annotation.predestroy;
import org.springframework.stereotype.service;
/**
* 订单服务
*/
@service
public class orderservice {
private final userservice userservice;
public orderservice(userservice userservice) {
this.userservice = userservice;
// ⚠️ 此时依赖已注入,但如果通过字段注入则可能还未注入
system.out.println("1. 构造方法");
}
@postconstruct
public void init() {
// ✅ 所有依赖已注入完毕,可以安全使用
system.out.println("2. @postconstruct: 依赖已就绪");
userservice.warmupcache();
}
@predestroy
public void cleanup() {
system.out.println("3. @predestroy: 清理资源");
}
}
关键:@postconstruct 在所有依赖注入完成后执行,是执行初始化逻辑的安全位置。不要在构造方法中调用被注入的依赖的方法(字段注入场景下依赖可能还未注入)。
十二、四种注入方式横向对比
12.1 全面对比
| 维度 | 构造器注入 | 字段注入 | setter 注入 | 方法注入 |
|---|---|---|---|---|
| 不可变性 | ✅ final | ❌ | ❌ | ❌ |
| 完全初始化保证 | ✅ | ❌ | ❌ | ❌ |
| 单元测试便利性 | ✅ 可直接 new | ❌ 需反射 | ✅ 可 setter | ✅ 可调用方法 |
| 可选依赖 | ❌ | ✅ | ✅ | ✅ |
| 循环依赖 | ❌ 不支持 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
| 代码简洁度 | ⭐⭐⭐(lombok) | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
| 依赖可见性 | ✅ 构造方法明确 | ❌ 隐藏 | ⭐ setter 明确 | ⭐ 方法明确 |
| spring 官方推荐 | ✅ 强烈推荐 | ❌ 不推荐 | ⭐ 可选依赖时使用 | ⭐ 特殊场景 |
| 违反单一职责感知 | ✅ 参数过多有警示 | ❌ 加字段太容易 | ⭐ 中等 | ⭐ 中等 |
12.2 适用场景
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 必需依赖 | 构造器注入 | 保证不可变和完全初始化 |
| 可选依赖 | setter 注入 | required = false |
| 简单工具类 | 字段注入(可接受) | 代码简洁,但仅限非核心逻辑 |
| 需要多个相关依赖同时设置 | 方法注入 | 可在方法中添加初始化逻辑 |
| 单例注入原型 | objectprovider / @lookup | 每次获取新实例 |
| 通用泛型基类 | 泛型注入 | 代码复用 |
配置类 @bean 方法 | 方法参数注入 | spring 自动从容器中查找 |
12.3 @bean 方法中的注入
在 @configuration 类中,@bean 方法的参数会自动从容器中注入:
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
@configuration
public class appconfig {
/**
* 参数 userservice 自动从容器中注入
* 无需标注 @autowired(spring 4.3+ 自动注入)
*/
@bean
public orderservice orderservice(userservice userservice, orderrepository orderrepository) {
return new orderservice(userservice, orderrepository);
}
}
十三、最佳实践
13.1 项目中的注入规范
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.service;
/**
* 订单服务 - 推荐写法示范
*/
@slf4j
@service
@requiredargsconstructor
public class orderservice {
// ✅ 必需依赖:final + 构造器注入(lombok 生成构造方法)
private final userservice userservice;
private final orderrepository orderrepository;
// ✅ 可选依赖:objectprovider(构造器友好)
private final objectprovider<logservice> logserviceprovider;
/**
* 创建订单
*/
public void createorder(long userid, ordercreaterequest request) {
// 使用必需依赖
user user = userservice.getbyid(userid);
// 使用可选依赖
logserviceprovider.ifavailable(service ->
service.log("创建订单: userid=" + userid)
);
// 业务逻辑
order order = new order();
order.setuserid(userid);
order.setamount(request.getamount());
orderrepository.save(order);
}
}
13.2 最佳实践清单
| 编号 | 实践 | 说明 |
|---|---|---|
| 1 | 构造器注入优先 | 所有必需依赖用构造器注入,配合 @requiredargsconstructor |
| 2 | 字段加 final | 保证依赖不可变,线程安全 |
| 3 | 避免字段注入 | 除非是遗留代码或快速原型 |
| 4 | 可选依赖用 objectprovider | 比 @autowired(required = false) 语义更清晰 |
| 5 | 多实现用 @qualifier | 明确指定 bean 名称,避免歧义 |
| 6 | 接口编程 | 注入接口类型,而非实现类 |
| 7 | 避免循环依赖 | 构造器注入天然避免,如遇循环则重构 |
| 8 | 不在构造方法中调用依赖方法 | 字段注入场景下依赖可能未就绪 |
| 9 | 初始化逻辑放 @postconstruct | 此时所有依赖已注入 |
| 10 | 团队统一风格 | 全部用构造器注入 + lombok,保持一致性 |
13.3 单元测试对比
构造器注入的测试便利性:
import org.junit.jupiter.api.test;
import org.junit.jupiter.api.extension.extendwith;
import org.mockito.mock;
import org.mockito.junit.jupiter.mockitoextension;
import static org.mockito.mockito.*;
/**
* 构造器注入 → 单元测试极简
*/
@extendwith(mockitoextension.class)
class orderservicetest {
@mock
private userservice userservice;
@mock
private orderrepository orderrepository;
@test
void testcreateorder() {
// 直接 new 传入 mock,无需 spring 容器
orderservice orderservice = new orderservice(userservice, orderrepository);
when(userservice.getbyid(1l)).thenreturn(new user(1l, "张三"));
orderservice.createorder(1l, new ordercreaterequest());
verify(orderrepository, times(1)).save(any());
}
}
字段注入的测试困难:
/**
* 字段注入 → 单元测试困难
* 需要反射或 spring testcontext 才能注入 mock
*/
class orderservicefieldinjectiontest {
@test
void testcreateorder() throws exception {
orderservice service = new orderservice();
// 需要通过反射注入私有字段(非常 hacky)
field field = orderservice.class.getdeclaredfield("userservice");
field.setaccessible(true);
field.set(service, mock(userservice.class));
// ... 测试逻辑
}
}
十四、总结
14.1 核心要点速查
- 构造器注入是官方推荐:不可变、完全初始化、测试友好
- 字段注入不推荐:隐藏依赖、不可变、测试困难
- setter 注入用于可选依赖:
required = false或objectprovider @autowired按类型匹配,配合@qualifier按名称指定@resource按名称匹配,jsr-250 标准,不支持构造器- 多实现场景:
@primary指定默认 +@qualifier精确指定 - 构造器注入不支持循环依赖:这是优点,强制解决设计问题
- spring 三级缓存解决 setter/字段注入的循环依赖
- 单例注入原型用
objectprovider或@lookup - 泛型注入实现代码复用:
baserepository<t>自动匹配实现 @postconstruct是初始化逻辑的安全位置- lombok
@requiredargsconstructor简化构造器注入
14.2 决策树
依赖是否必需?
├── 必需 → 构造器注入(final + @requiredargsconstructor)
└── 可选 → objectprovider(构造器友好)
或 setter 注入(@autowired(required = false))
是否有多个实现?
├── 是 → @primary 指定默认 + @qualifier 精确指定
└── 否 → 直接注入
是否是单例注入原型?
├── 是 → objectprovider 或 @lookup
└── 否 → 正常注入
是否遇到循环依赖?
├── 是 → 重构消除循环(首选)
│ 或 @lazy 延迟注入(临时)
└── 否 → 正常注入
以上就是springboot中依赖注入的常用方式对比详解的详细内容,更多关于springboot依赖注入的资料请关注代码网其它相关文章!
发表评论