Spring 依赖注入方式全景解析

一、概念介绍

Spring 提供了 4 种主要的依赖注入(DI)方式,核心目标是将对象的创建和依赖关系交给 Spring 容器管理,实现控制反转(IoC)。

1.构造函数注入(Constructor Injection)⭐推荐

用法示例

java 复制代码
@Service
public class OrderService {
    
    private final UserRepository userRepository;
    private final PaymentService paymentService;

    // Spring 自动识别构造函数进行注入
    public OrderService(UserRepository userRepository, 
                       PaymentService paymentService) {
        this.userRepository = userRepository;
        this.paymentService = paymentService;
    }
}

// Lombok 简化写法
@Service
@RequiredArgsConstructor
public class OrderService {
    private final UserRepository userRepository;
    private final PaymentService paymentService;
}

特点

✅ 强制依赖:对象创建时必须提供所有依赖

✅ 不可变性:字段可用 final 修饰

✅ 易测试:可直接 new 对象,无需 Spring 容器

❌ 循环依赖:无法直接解决(需配合 @Lazy)

2.Setter 注入(Setter Injection)

用法示例

java 复制代码
@Service
public class NotificationService {
    
    private EmailService emailService;
    private SmsService smsService;

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

特点

✅ 可选依赖:可标注 required = false

✅ 灵活配置:可在运行时重新设置依赖

⚠️ 可变性:依赖可能被修改

❌ 代码冗长:每个依赖需要一个 setter 方法

3.字段注入(Field Injection)❌不推荐

用法示例

java 复制代码
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    @Lazy  // 解决循环依赖
    private OrderService orderService;
}

特点

✅ 代码简洁:最少样板代码

❌ 隐藏依赖:无法从外部看出类需要哪些依赖

❌ 难测试:必须启动 Spring 容器或使用反射

❌ 违反设计原则:无法使用 final,违背单一职责

4.接口注入(Interface Injection)

用法示例

java 复制代码
// 定义注入接口
public interface BeanNameAware {
    void setBeanName(String name);
}

@Service
public class MyService implements BeanNameAware {
    
    private String beanName;
    
    @Override
    public void setBeanName(String name) {
        this.beanName = name;  // Spring 容器调用
    }
}

特点

⚠️ 侵入性强:类必须实现特定接口

⚠️ 使用场景少:主要用于 Spring 内部机制

❌ 不常用:现代 Spring 开发几乎不用

二、对比总结

三、实际项目中的选择策略

1.默认使用构造函数注入

java 复制代码
@Service
@RequiredArgsConstructor  // Lombok 自动生成构造函数
public class StrategyService {
    private final StrategyMapper strategyMapper;
    private final VentureService ventureService;
}

2.可选依赖用 Setter 注入

java 复制代码
@Service
public class ReportService {
    
    private CacheService cacheService;  // 缓存是可选的
    
    @Autowired(required = false)
    public void setCacheService(CacheService cacheService) {
        this.cacheService = cacheService;
    }
    
    public void generateReport() {
        if (cacheService != null) {
            cacheService.put(...);  // 有缓存就用
        }
    }
}

3.循环依赖时配合 @Lazy

java 复制代码
@Service
@RequiredArgsConstructor
public class ServiceA {
    private final ServiceB serviceB;
}

@Service
@RequiredArgsConstructor
public class ServiceB {
    @Lazy  // 打破循环
    private final ServiceA serviceA;
}

4.避免字段注入的原因

java 复制代码
// ❌ 坏味道:依赖太多却看不出来
@Service
public class BadExample {
    @Autowired private Service1 s1;
    @Autowired private Service2 s2;
    @Autowired private Service3 s3;
    @Autowired private Service4 s4;
    @Autowired private Service5 s5;
    // ... 违反了单一职责原则
}

// ✅ 好实践:构造函数参数过多提示需要重构
@Service
@RequiredArgsConstructor
public class GoodExample {
    private final Service1 s1;
    private final Service2 s2;
    // 如果超过 7 个参数,考虑拆分类
}

四、Spring 注入底层原理

java 复制代码
// Spring 容器启动时的处理流程
1. 扫描 @Component/@Service 等注解,注册 BeanDefinition
2. 实例化 Bean(调用构造函数或无参构造)
3. 属性填充(populateBean)
   - 如果是构造函数注入:在第 2 步完成
   - 如果是字段/Setter 注入:在这一步通过反射赋值
4. 初始化(@PostConstruct、InitializingBean)
5. 放入单例池(singletonObjects)

五、最佳实践建议

1.统一团队规范

yaml 复制代码
   # .editorconfig 或团队文档
   spring-injection-style: constructor  # 强制使用构造函数注入

2.IDE 配置检查

IntelliJ IDEA:Settings → Editor → Inspections → Spring → Spring Core → Code

启用 "Autowiring for bean class" 警告字段注入

相关推荐
yaoxin5211239 分钟前
434. Java 日期时间 API - Period 基于日期的时间段
java·开发语言·python
Hommy8820 分钟前
【剪映小助手】添加图片接口(Add Images)
后端·github·剪映小助手·视频剪辑自动化
GetcharZp1 小时前
别再盲目用 OpenCV 读图了,这才是 CV 预处理的终极杀手锏!
后端
何极光1 小时前
IDEA集成Maven
java·maven·intellij-idea
程序员二叉1 小时前
【JUC】ThreadLocal底层原理|内存泄漏|弱引用|跨线程传递方案
java·开发语言·面试·职场和发展·juc
程序员二叉1 小时前
【JUC】线程池全套深度详解|参数|流程|拒绝策略|调优|异常处理
java·开发语言·jvm·算法·面试·juc
老马识途2.02 小时前
在AI的帮助下理解spring的启动过程
java·前端·spring
青山木2 小时前
Hot 100 --- 轮转数组
java·数据结构·算法
Qt程序员2 小时前
掌握 Linux 内核调度:从原理到实现(进程篇)
java·开发语言
code bean2 小时前
【LangChain】检索器完全指南:从向量检索到生产级 RAG 架构
java·开发语言·微服务