依赖注入(Dependency Injection,简称 DI)是 Spring 框架中控制反转(IoC)的核心实现形式。与依赖查找的主动获取不同,依赖注入通过容器动态地将依赖关系注入到组件中,使得代码更简洁、解耦更彻底。在这篇博客中,我们将全面探讨 Spring 依赖注入的原理、模式、方式以及相关实践,帮助您更好地理解和应用这一技术。
什么是依赖注入?
依赖注入是一个过程,通过构造函数、工厂方法参数或对象属性,将依赖对象注入到目标对象中。Spring 容器在创建 Bean 时负责完成这一过程,与传统的通过类直接构造或服务定位器模式获取依赖的方式形成对比(即控制反转,IoC)。
依赖注入的核心优势在于:
- 代码简洁:对象无需主动查找依赖,也无需关心依赖的来源或具体实现。
- 高解耦性:依赖关系由容器管理,组件间耦合度降低。
- 易测试性:通过接口或抽象类注入依赖,便于在单元测试中使用模拟对象。
理解依赖注入的关键在于回答以下问题:
- 谁依赖谁? 应用程序依赖 IoC 容器。
- 为什么需要依赖? 应用程序需要容器提供外部资源(如对象、常量等)。
- 谁注入谁? 容器将依赖对象注入到应用程序的某个对象中。
- 注入了什么? 外部资源,包括对象、配置文件数据等。
依赖注入的模式
Spring 支持两种依赖注入模式:手动注入和自动注入。
手动注入模式
手动注入通过配置或编程方式提前指定注入规则,主要包括:
- XML 配置元信息:通过
<property>
或<constructor-arg>
指定依赖。 - Java 注解配置:如
@Autowired
、@Resource
。 - API 配置:通过编程方式调用容器 API。
自动注入模式
自动注入(Autowiring)由 Spring 容器根据上下文自动解析并装配 Bean 之间的关系。可以通过 <bean>
元素的 autowire
属性配置:
no
:默认值,无自动装配,需手动指定。byName
:根据属性名匹配 Bean 名称。byType
:根据属性类型查找 Bean。constructor
:按构造函数参数类型匹配。
注意:官方不推荐过度依赖自动装配,因其存在局限性(后文详述)。
依赖注入的方式
Spring 提供了多种注入方式,适用于不同场景:
1. 构造器注入
通过构造函数注入依赖,适用于强制性依赖。
- 手动模式:XML、注解或 Java 配置。
- 自动模式:
autowire="constructor"
。 - 示例:
public class SimpleMovieLister {
private final MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
<bean id="movieLister" class="SimpleMovieLister">
<constructor-arg ref="movieFinder"/>
</bean>
参数匹配:
- 类型匹配:
<constructor-arg type="int" value="42"/>
。 - 索引匹配:
<constructor-arg index="0" value="42"/>
。 - 名称匹配:
<constructor-arg name="years" value="42"/>
或使用@ConstructorProperties
。
2. Setter 方法注入
通过 Setter 方法注入,适用于可选依赖。
- 手动模式:XML 或注解。
- 自动模式:
byName
或byType
。 - 示例:
public class SimpleMovieLister {
private MovieFinder movieFinder;
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
3. 字段注入
通过注解直接注入字段,便捷但侵入性较强。
- 示例:
@Autowired private User user;
。
4. 方法注入
通过方法参数注入,适用于特定逻辑。
- 示例:
@Autowired
public void setUser(User user) { ... }
5. 接口回调注入
通过实现 Aware
接口获取容器内置对象,如 BeanFactoryAware
、ApplicationContextAware
。
注入方式选型建议:
- 低依赖:构造器注入。
- 多依赖:Setter 注入。
- 便利性:字段注入。
- 声明类:方法注入。
限定注入与延迟注入
限定注入
当容器中存在多个相同类型的 Bean 时,可通过 @Qualifier
限定:
- 按名称:
@Qualifier("userBean")
。 - 按分组:自定义注解扩展,如
@LoadBalanced
。
延迟注入
避免立即加载依赖:
ObjectFactory
:基础延迟加载。ObjectProvider
:推荐使用,支持getIfAvailable()
等方法。
依赖注入的数据类型
基础类型
- 基本数据类型:
int
、boolean
等。 - 标量类型:
Number
、String
、Enum
等。 - Spring 类型:
Resource
、Formatter
等。
集合类型
- 数组:如
String[]
。 - 集合:
List
、Set
、Map
等。
自动装配的限制与不足
自动装配虽便利,但有以下局限:
- 覆盖问题:显式配置会覆盖自动装配。
- 类型限制:无法自动装配简单类型(如
int
、String
)。 - 歧义风险:多个 Bean 匹配时可能抛出异常。
- 文档缺失:自动装配信息难以记录。
详情见官方文档:Limitations of Autowiring。
依赖注入的处理过程
依赖注入的核心入口是 DefaultListableBeanFactory#resolveDependency
,涉及:
- 依赖描述符:
DependencyDescriptor
。 - 处理器:
@Autowired
和@Value
:AutowiredAnnotationBeanPostProcessor
。- JSR-250 注解:
CommonAnnotationBeanPostProcessor
。
依赖查找 vs. 依赖注入
类型 | 依赖处理 | 实现复杂度 | 代码侵入性 | API 依赖性 | 可读性 |
---|---|---|---|---|---|
依赖查找 | 主动 | 较高 | 高 | 依赖容器 API | 良好 |
依赖注入 | 被动 | 较低 | 低 | 无需 API | 一般 |
总结
Spring 的依赖注入通过多种方式(构造器、Setter、字段等)和模式(手动、自动)实现了灵活的组件装配。无论是强制依赖还是可选依赖,Spring 都提供了强大的支持。然而,在使用自动装配时需注意其局限性,并在实际项目中结合场景选择合适的注入方式。