pring 的依赖注入(Dependency Injection, DI)是其核心特性之一,通过将对象依赖关系的创建和管理交给容器,实现松耦合和高扩展性。以下是依赖注入的 方式 、底层原理 及 核心实现细节 的全面解析。
一、依赖注入的三种方式
1. 构造器注入(Constructor Injection)
-
定义:通过构造函数参数传递依赖。
-
使用场景:
- 强制依赖(必须的依赖项)。
- 不可变字段(
final
修饰的字段)。 - 推荐用于解决循环依赖问题。
-
代码示例:
kotlin@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
)。 - 需要动态重新配置依赖的场景。
- 可选依赖(依赖可为
-
代码示例:
typescript@Component public class PaymentService { private PaymentGateway paymentGateway; @Autowired public void setPaymentGateway(PaymentGateway paymentGateway) { this.paymentGateway = paymentGateway; } }
-
优点:
- 灵活性高,允许依赖变更。
- 符合 JavaBean 规范。
3. 字段注入(Field Injection)
-
定义:直接通过字段注入依赖(借助反射)。
-
使用场景:
- 快速开发(代码简洁)。
- 非强制依赖(但需谨慎使用)。
-
代码示例:
kotlin@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。 -
示例代码(依赖解析) :
typescriptpublic 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。
- 实例化 B,将 B 的 ObjectFactory 放入
-
获取 Bean A 的早期引用:
- 从
singletonFactories
获取 A 的 ObjectFactory,生成 A 的代理或原始对象,放入earlySingletonObjects
。 - 将 A 的早期引用注入到 B 中。
- 从
-
完成 Bean B 的初始化:
- B 完成属性填充和初始化,放入
singletonObjects
。
- B 完成属性填充和初始化,放入
-
完成 Bean A 的初始化:
- 将 B 的实例注入 A,A 完成初始化,放入
singletonObjects
。
- 将 B 的实例注入 A,A 完成初始化,放入
3. 无法解决的循环依赖场景
- 构造器循环依赖 : 若两个 Bean 均通过构造器注入对方,Spring 无法解决 ,抛出
BeanCurrentlyInCreationException
。 - 原型(Prototype)Bean 的循环依赖: Spring 不缓存原型 Bean,因此无法处理其循环依赖。
四、高级依赖注入技术
1. 延迟注入(Lazy Injection)
-
使用
@Lazy
: 延迟初始化依赖,直到首次访问。less@Component public class OrderService { @Lazy @Autowired private InventoryService inventoryService; }
-
使用
ObjectProvider
: 按需获取 Bean,避免过早初始化。java@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。less@Configuration public class AppConfig { @Bean @Conditional(OnProductionEnvCondition.class) public DataSource productionDataSource() { return new ProductionDataSource(); } }
3. 自定义依赖注入逻辑
通过实现 BeanPostProcessor
或 BeanFactoryPostProcessor
干预依赖注入过程:
-
示例:自定义注解处理器:
typescriptpublic class CustomAnnotationProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { // 解析自定义注解并注入依赖 if (bean instanceof MyService) { ((MyService) bean).setCustomDependency(...); } return bean; } }
五、总结
Spring 的依赖注入通过以下核心机制实现:
- 多种注入方式:构造器注入(推荐)、Setter 注入、字段注入。
- 注解驱动 :
@Autowired
、@Resource
等结合后置处理器实现自动化注入。 - 依赖解析 :按类型或名称匹配 Bean,支持
@Primary
和@Qualifier
解决冲突。 - 循环依赖处理:三级缓存机制解决单例 Bean 的循环依赖。
- 扩展性 :通过
BeanPostProcessor
和自定义条件实现灵活控制。
理解这些原理不仅能帮助开发者避免常见的依赖问题(如循环依赖),还能优化 Bean 设计(如选择最佳注入方式),并提升应用的可维护性和可测试性。