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" 警告字段注入

相关推荐
青柠代码录2 小时前
【Spring】@Component VS @Configuration
后端
爱吃烤鸡翅的酸菜鱼2 小时前
Java 事件发布-订阅机制全解析:从原生实现到主流中间件
java·中间件·wpf·事件·发布订阅
无限码力2 小时前
华为OD技术面真题 - JAVA开发- spring框架 - 7
java·开发语言·华为od·华为od面试真题·华为odjava八股文·华为odjava开发题目·华为odjava开发高频题目
Lyyaoo.2 小时前
【JAVA基础面经】JAVA中的异常
java·开发语言
一定要AK2 小时前
JVM 全体系深度解析笔记
java·jvm·笔记
coder阿龙2 小时前
基于SpringAI+Qdrant+Ollama本地模型和向量数据库开发问答和RAG检索
java·数据库·spring boot·ai·数据库开发
Gofarlic_OMS2 小时前
HyperWorks用户仿真行为分析与许可证资源分点配置
java·大数据·运维·服务器·人工智能
徒 花2 小时前
Python知识学习08
java·python·算法
Lyyaoo.2 小时前
【JAVA基础面经】== 和 equals() 的区别
java·开发语言·jvm