【Spring】依赖注入的实现方式对比

Spring 提供 4 种核心注入方式:构造函数注入、Setter 注入、字段注入、方法注入 。每种方式在强制性、可测试性、不变性等方面差异巨大

一、构造函数注入(Constructor Injection)

java 复制代码
@Service
public class UserService {
    private final UserRepository userRepository;  // final 修饰
    private final EmailService emailService;

    // Spring 4.3+:单构造函数可省略 @Autowired
    public UserService(UserRepository userRepository, EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
}

优点
1.强制依赖完整性 :必须提供所有依赖,对象创建后处于完整状态
2.不可变性(Immutability) :字段可声明为 final,线程安全
3.易于单元测试 :无需反射,直接 new UserService(mockRepo, mockEmail) 即可测试
4.清晰的依赖声明 :构造函数参数即依赖清单,一目了然
5.循环依赖检测 :Spring 启动时即报错,避免运行时 NPE
缺点
1.参数过多时臃肿 :依赖超过 5 个时构造函数过长(设计信号:违反单一职责)
2.循环依赖无法解决 :构造函数间的循环依赖会导致 Spring 启动失败
3.稍显冗长:需要显式编写构造器和赋值语句

适用场景
1.必选依赖 :Bean 没有这些依赖无法正常工作
2.追求不变性 :需要线程安全或不可变对象
3.团队协作:代码可读性优先

二、Setter 注入(Setter Injection)

java 复制代码
@Service
public class UserService {
    private UserRepository userRepository;
    private EmailService emailService;
    private Optional<SmsService> smsService = Optional.empty(); // 可选依赖

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Autowired
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }

    @Autowired(required = false)  // 可选依赖
    public void setSmsService(SmsService smsService) {
        this.smsService = Optional.ofNullable(smsService);
    }
}

优点
1.依赖可选性 :通过 required = false 支持可选依赖
2.灵活性高 :可在对象创建后动态修改依赖(不推荐,但可行)
3.解决循环依赖 :结合三级缓存可解决 Setter 循环依赖
4.符合 JavaBean 规范:对旧框架兼容性好

缺点
1.对象状态不完整 :构造后可能缺少依赖,导致运行时 NPE
2.无法使用 final :字段可被修改,失去不可变性保证
3.测试依赖反射 :需要 Mockito 等工具注入 mock 对象
4.依赖关系不清晰 :无法直观看出哪些是必选依赖
5.多次调用风险:可能被意外多次调用,需手动防御

适用场景
1.可选依赖 :如插件式功能、@Autowired(required = false)
2.配置类 :需要动态设置策略的场景
3.遗留系统兼容:老代码迁移的过渡方案

三、字段注入(Field Injection)

java 复制代码
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;  // 非 final

    @Autowired
    private EmailService emailService;
}

优点
1.代码简洁 :无需构造器或 Setter,注解直接打在字段上
2.编写快速 :开发效率最高
3.可读性尚可:依赖一目了然(类级别)

缺点

1.无法使用 final:失去不可变性,线程不安全

2.强依赖反射:单元测试必须启动 Spring 容器或使用 @InjectMocks

java 复制代码
// 测试必须这样
@InjectMocks
private UserService userService;

3.隐藏依赖 :通过反射注入,IDE 无法识别为依赖项
4.循环依赖难检测 :可能延迟到运行时才能发现
5.难以初始化 :无法在构造函数中使用依赖,导致 NPE 风险
6.静态分析困难 :工具无法分析依赖关系(影响架构治理)
致命问题:如果你将项目升级到 Spring Boot 2.2+,会发现 IDEA 会提示 "Field injection is not recommended"。

适用场景
1.快速原型开发 :不关心测试的临时代码
2.集成测试 :测试类本身可以使用
3.不推荐在生产代码中使用

四、方法注入(Method Injection)

1.普通方法注入

java 复制代码
@Service
public class UserService {
    private UserRepository userRepository;

    @Autowired  // 可注入任意方法
    public void prepare(UserRepository userRepository) {
        this.userRepository = userRepository;
        // 可执行额外初始化逻辑
        userRepository.initCache();
    }
}

特点:与 Setter 类似,但方法名不要求 setXxx

2. Lookup 方法注入(解决原型 Bean 依赖)

java 复制代码
@Service
@Scope("singleton")
public abstract class UserService {
    // Spring 会动态实现此方法,每次返回新的 Prototype Bean
    @Lookup
    public abstract PrototypeBean getPrototypeBean();
    
    public void process() {
        PrototypeBean bean = getPrototypeBean(); // 每次都获取新实例
        bean.doWork();
    }
}

用途:Singleton Bean 依赖 Prototype Bean 时,保证每次获取新实例

对比总结

维度 构造函数注入 Setter 注入 字段注入 方法注入
强制性 ✅ 必选 ⚠️ 可选 ❌ 隐藏 ⚠️ 可选
final 字段 ✅ 支持 ❌ 不支持 ❌ 不支持 ❌ 不支持
不可变性 ✅ 线程安全 ❌ 可变 ❌ 可变 ❌ 可变
测试难度 ✅ 简单 ⚠️ 需反射 ❌ 需容器 ⚠️ 需反射
循环依赖 ❌ 无法解决 ✅ 可解决 ⚠️ 可解决但有风险 ✅ 可解决
依赖清晰度 ✅ 极高 ⚠️ 中等 ❌ 隐藏 ⚠️ 中等
IDE 支持 ✅ 完美 ⚠️ 一般 ❌ 困难 ⚠️ 一般
Spring 推荐度 ⭐⭐⭐⭐⭐ ⭐⭐⭐ (不推荐) ⭐⭐
相关推荐
毕设源码-朱学姐7 小时前
【开题答辩全过程】以 基于JavaWeb的网上家具商城设计与实现为例,包含答辩的问题和答案
java
C雨后彩虹8 小时前
CAS与其他并发方案的对比及面试常见问题
java·面试·cas·同步·异步·
sww_10268 小时前
RAG检索增强 ETL最佳实战
人工智能·python·spring
java1234_小锋9 小时前
Java高频面试题:SpringBoot为什么要禁止循环依赖?
java·开发语言·面试
2501_944525549 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 账户详情页面
android·java·开发语言·前端·javascript·flutter
计算机学姐9 小时前
基于SpringBoot的电影点评交流平台【协同过滤推荐算法+数据可视化统计】
java·vue.js·spring boot·spring·信息可视化·echarts·推荐算法
Filotimo_10 小时前
Tomcat的概念
java·tomcat
索荣荣10 小时前
Java Session 全面指南:原理、应用与实践(含 Spring Boot 实战)
java·spring boot·后端
Amumu1213810 小时前
Vue Router(二)
java·前端