Spring 依赖注入方式及原理
一、依赖注入的三种方式
1. 构造器注入(Constructor Injection)
定义:通过构造函数参数传递依赖。
使用场景:
- 强制依赖(必须的依赖项)。
- 不可变字段(final 修饰的字段)。
- 推荐用于解决循环依赖问题。
代码示例:
@Component
public class OrderService {
private final UserService userService;
@Autowired // Spring 4.3+ 可省略
public OrderService(UserService userService) {
this.userService = userService;
}
}
优点:
- 保证依赖不可变。
- 避免
NullPointerException
(依赖必须在对象创建时提供)。 - 易于单元测试(通过构造器直接传入
Mock
对象)。
2. Setter 注入(Setter Injection)
定义:通过 Setter 方法传递依赖。
使用场景:
- 可选依赖(依赖可为 null)。
- 需要动态重新配置依赖的场景。
代码示例:
@Component
public class PaymentService {
private PaymentGateway paymentGateway;
@Autowired
public void setPaymentGateway(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
}
优点:
- 灵活性高,允许依赖变更。
- 符合 JavaBean 规范。
3. 字段注入(Field Injection)
定义:直接通过字段注入依赖(借助反射)。
使用场景:
- 快速开发(代码简洁)。
- 非强制依赖(但需谨慎使用)。
代码示例:
@Component
public class NotificationService {
@Autowired
private EmailService emailService;
}
缺点:
- 破坏封装性(字段通常应为 private)。
- 难以测试(需通过反射或 Spring 容器注入依赖)。
- 隐藏依赖关系(类签名中不直接体现依赖)。
4.对比
特性 | 构造器注入 | Setter注入 | 字段注入 |
---|---|---|---|
依赖可见性 | 显式(构造函数参数) | 显式(Setter方法) | 隐式(字段声明) |
不可变性 | 支持(final字段) | 不支持 | 不支持 |
循环依赖支持 | 不支持 | 支持(单例模式) | 支持(单例模式) |
测试友好性 | 高(无需容器即可注入依赖) | 中(需调用Setter) | 低(需反射或容器) |
代码侵入性 | 低(天然支持) | 中(需显式Setter) | 无 |
推荐场景 | 必需依赖、线程安全需求 | 可选依赖、动态配置 | 快速原型开发,不推荐生产环境 |
二、依赖注入的底层原理
Spring 的依赖注入实现依赖于 Bean 生命周期管理 和 后置处理器(BeanPostProcessor) ,核心流程如下:
1. Bean 的生命周期与注入时机
-
Bean 定义加载: 通过
BeanDefinitionReader
解析配置(XML、注解、Java Config),生成BeanDefinition
。 -
Bean 实例化: 调用构造函数或工厂方法创建对象(
AbstractAutowireCapableBeanFactory.createBeanInstance()
)。 -
属性填充(依赖注入): 通过
populateBean()
方法注入依赖(字段、Setter 方法或构造器参数)。
初始化: 调用@PostConstruct
方法、InitializingBean.afterPropertiesSet()
或自定义初始化方法。 -
销毁: 容器关闭时调用
@PreDestroy
方法或DisposableBean.destroy()
。
2. 注解驱动的依赖注入实现
Spring 通过 BeanPostProcessor
实现注解解析和依赖注入:
关键类:
AutowiredAnnotationBeanPostProcessor
:处理@Autowired
和@Value
。CommonAnnotationBeanPostProcessor
:处理@Resource
、@PostConstruct
等 JSR-250 注解。
注入流程:
- 后置处理器注册: 在容器初始化时,将
BeanPostProcessor
注册到BeanFactory
。 - 元数据解析: 在
postProcessMergedBeanDefinition()
阶段,扫描Bean
的字段、方法,提取注入点(InjectionMetadata
)。 - 依赖注入: 在
postProcessProperties()
阶段,通过反射注入具体值。
3. 依赖解析机制
Spring 通过 DefaultListableBeanFactory
解析依赖:
- 按类型注入: 查找所有匹配类型的 Bean,若存在多个候选 Bean,需结合 @Primary、@Qualifier 解决歧义。
- 按名称注入: 若使用 @Resource(name = "beanName"),直接按名称查找 Bean。
示例代码(依赖解析) :
public class DefaultListableBeanFactory {
public Object resolveDependency(DependencyDescriptor descriptor, String beanName) {
// 1. 按类型查找候选 Bean
Map<String, Object> candidates = findAutowireCandidates(beanName, descriptor.getDependencyType());
// 2. 根据 @Primary、@Priority、名称等确定最终 Bean
Object result = determineAutowireCandidate(candidates, descriptor);
return result;
}
}
三、循环依赖的处理机制
Spring 通过 三级缓存 解决单例 Bean 的循环依赖问题:
1. 三级缓存结构
缓存名称 | 存储内容 | 作用 |
---|---|---|
singletonObjects | 完全初始化好的单例 Bean | 直接获取最终可用的 Bean。 |
earlySingletonObjects | 提前暴露的 Bean(未完成属性填充和初始化) | 解决循环依赖中的早期引用。 |
singletonFactories | 生成早期 Bean 的 ObjectFactory | 延迟创建 Bean 的早期引用。 |
2. 循环依赖解决流程
以 Bean A → Bean B → Bean A 为例:
- 创建 Bean A:
- 实例化 A(调用构造函数),生成一个原始对象。
- 将 A 的 ObjectFactory 放入
singletonFactories
。
- 填充 Bean A 的属性:
- 发现 A 依赖 B,尝试获取 Bean B。
- 创建 Bean B:
- 实例化 B,将 B 的 ObjectFactory 放入
singletonFactories
。 - 填充 B 的属性时,发现 B 依赖 A。
- 获取 Bean A 的早期引用:
- 从
singletonFactories
获取 A 的ObjectFactory
,生成 A 的代理或原始对象,放入earlySingletonObjects
。 - 将 A 的早期引用注入到 B 中。
- 完成 Bean B 的初始化:
- B 完成属性填充和初始化,放入
singletonObjects
。
- 完成 Bean A 的初始化:
- 将 B 的实例注入 A,A 完成初始化,放入
singletonObjects
。
3. 无法解决的循环依赖场景
- 构造器循环依赖: 若两个 Bean 均通过构造器注入对方,Spring 无法解决,抛出
BeanCurrentlyInCreationException
。 - 原型(Prototype)Bean 的循环依赖: Spring 不缓存原型 Bean,因此无法处理其循环依赖。
四、高级依赖注入技
1. 延迟注入(Lazy Injection)
- 使用
@Lazy
: 延迟初始化依赖,直到首次访问。
@Component
public class OrderService {
@Lazy
@Autowired
private InventoryService inventoryService;
}
- 使用
ObjectProvider
: 按需获取 Bean,避免过早初始化。
@Component
public class OrderService {
private final ObjectProvider<InventoryService> inventoryServiceProvider;
public OrderService(ObjectProvider<InventoryService> inventoryServiceProvider) {
this.inventoryServiceProvider = inventoryServiceProvider;
}
public void processOrder() {
InventoryService inventoryService = inventoryServiceProvider.getIfAvailable();
// ...
}
}
2. 条件化注入
@Conditional
注解: 根据条件动态决定是否注册 Bean。
@Configuration
public class AppConfig {
@Bean
@Conditional(OnProductionEnvCondition.class)
public DataSource productionDataSource() {
return new ProductionDataSource();
}
}
3. 自定义依赖注入逻辑
通过实现 BeanPostProcessor
或 BeanFactoryPostProcessor
干预依赖注入过程:
示例:自定义注解处理器:
public class CustomAnnotationProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// 解析自定义注解并注入依赖
if (bean instanceof MyService) {
((MyService) bean).setCustomDependency(...);
}
return bean;
}
}
五、Autowired 与 Resource区别
@Autowired
注解是Spring提供,只按照byType进行注入。@Autowired
如果想要按照byName
方式需要加@Qualifier
,Qualifier
意思是合格者,一般跟Autowired
配合使用,需要指定一个bean
的名称,通过bean
名称就能找到需要装配的bean
。
//定义一个服务,添加@Service交给Spring Boot管理
@Service
public class Service1 {
}
//在另一个Service中需要依赖,使用方法如下。
@Service
public class Service2 {
@Autowired
private Service1 service1;
}
Autowired
注解的加载流程图如下:
flowchart LR
A["按类型查找bean"] --> B{"是否找到?"}
B --> |否|C["抛异常"]
B --> |是|D{"只找到一个?"}
D --> |只一个|F["自动装配"]
D --> |不只一个|G{"是否配置了Qualifier?"}
G --> |是|H["按照Qualifier参数查找bean"]
H --> I{"是否找到?"}
I --> |否|C
I --> |是|F
G --> |否|K["按照名称查找bean"]
K --> |否|C
K --> |是|F
@Resource
注解是Java标准库提供。默认采用byName
方式进行注入,如果找不到则使用byType
。可通过注解参数进行改变。比起Autowired好处在于跟Spring的耦合度没有那么高。
flowchart LR
A["按名称查找bean"] --> B{"是否找到?"}
B --> |否|C["按类型查找bean"]
B --> |是|D["自动装配"]
C --> E{"是否找到?"}
E --> |只找到一个|D
E --> |没有找到或找到多个|F["抛异常"]
具体例子如下:
//定义一个服务,添加@Service交给Spring Boot管理
@Service
public class Service1 {
}
//在另一个Service中需要依赖,使用方法如下。
@Service
public class Service2 {
@Resource
private Service1 service1;
}
构造参数注入方式! 比起以上两种方式,Spring更推荐以下方式进行注入,因为以上两种通过反射将对象直接注入私有属性,会破环封装性。
@Service
public class Service2 {
private final Service1 service1;
//Spring Boot在进行解析时,会自动将Service1的单例对象进行注入
public Service2(Service1 service1) {
this.service1 = service1;
}
}
六、总结
Spring 的依赖注入通过以下核心机制实现:
- 多种注入方式:构造器注入(推荐)、
Setter 注入
、字段注入。 - 注解驱动:
@Autowired
、@Resource
等结合后置处理器实现自动化注入。 - 依赖解析:按类型或名称匹配
Bean
,支持@Primary
和@Qualifier
解决冲突。 - 循环依赖处理:三级缓存机制解决单例
Bean
的循环依赖。 - 扩展性:通过
BeanPostProcessor
和自定义条件实现灵活控制。
理解这些原理不仅能帮助开发者避免常见的依赖问题(如循环依赖),还能优化 Bean 设计(如选择最佳注入方式),并提升应用的可维护性和可测试性。