【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 推荐度 ⭐⭐⭐⭐⭐ ⭐⭐⭐ (不推荐) ⭐⭐
相关推荐
Zzzzzxl_1 小时前
互联网大厂Java/Agent面试实战:Spring Boot、JVM、微服务与AI Agent/RAG场景问答
java·jvm·spring boot·ai·agent·rag·microservices
未若君雅裁1 小时前
JVM高级篇总结笔记
java·jvm·笔记
Ttang231 小时前
【AI篇3】在Java项目中调用大模型API
java·人工智能·microsoft·ai·api
豐儀麟阁贵1 小时前
9.4字符串操作
java·linux·服务器·开发语言
武子康1 小时前
Java-181 OSS 实战指南:Bucket/外链/防盗链/计费与常见坑
java·大数据·分布式·oss·云存储·fastdfs·ali
聆风吟º1 小时前
【Spring Boot 报错已解决】告别“Whitelabel Error Page”:Spring Boot 404报错的排查指南
java·spring boot·后端
w10463672p1 小时前
java解析CSV文件(一)——Java使用Apache.Commons.CSV解析CSV文件应用实践
java·apache·springboot·csv
weixin_307779131 小时前
Jenkins Gson API插件:统一JSON处理的基础库
java·运维·开发语言·架构·jenkins
Tony6666888881 小时前
Webservic 服务注册发布及参数封装-实际项目应用
java·spring·servlet