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

相关推荐
哦虎!24 分钟前
【JavaEE进阶】SpringBoot
spring
实名上网宋凯宣9 小时前
Django
后端·python·django
天河归来10 小时前
第三方接口不稳定,如何让核心任务继续完成
java·大数据
Knight_AL10 小时前
Java 文件下载时文件名中的 `#` 被转换成 `%23`?一次 URL 编码与解码问题排查
java·开发语言
利威尔xu11 小时前
Go IM 源码实战(14):本地缓存快 3000 倍,失效却难 10 倍
后端·缓存·微服务·面试·golang
逃逸线LOF11 小时前
Ribbon负载均衡原理(Irule组件、源码分析)
后端·spring cloud·ribbon
砚底藏山河12 小时前
python量化自定义周期 K 线合成
java·开发语言·数据库·python·金融
慢云智慧空间12 小时前
从设备智能到空间理解,空间智能终端解决方案商如何重新定义行业标准
后端
利威尔xu12 小时前
Go IM 源码实战(13):30 个会话只有 2 个红点,未读数错在三条路径
redis·后端·微服务·面试·golang
猿究院_夏燊12 小时前
MySQL 的四种隔离级别
java·数据库·mysql