Spring 支持三种主要的依赖注入方式:构造函数注入、Setter 方法注入和字段注入。Spring 框架内部通过反射机制以及 BeanWrapper
接口等来实现这些注入方式。
1. 构造函数注入 (Constructor Injection):
-
原理: Spring 通过调用 Bean 类的构造函数来创建 Bean 实例,并将依赖的 Bean 作为构造函数的参数传入。
-
优点:
- 强制依赖: 构造函数注入可以确保 Bean 在创建时就拥有所有必需的依赖,避免了部分初始化状态。
- 不可变性: 可以将依赖声明为
final
,保证依赖在 Bean 的生命周期内不会被修改。 - 易于测试: 可以轻松地通过构造函数传递模拟对象 (Mock Objects) 进行单元测试。
-
缺点:
- 循环依赖问题: 如果存在循环依赖(A 依赖 B,B 又依赖 A),构造函数注入可能会导致问题(可以通过 Setter 注入或
@Lazy
注解解决)。 - 构造函数参数过多: 如果依赖很多,构造函数参数列表可能会很长,不够优雅(可以使用构建器模式或参数对象来改善)。
- 循环依赖问题: 如果存在循环依赖(A 依赖 B,B 又依赖 A),构造函数注入可能会导致问题(可以通过 Setter 注入或
-
XML 配置:
<bean id="userService" class="com.example.UserService"> <constructor-arg ref="userRepository"/> </bean> <bean id="userRepository" class="com.example.UserRepository"/>
-
Java 注解:
@Service public class UserService { private final UserRepository userRepository; @Autowired // 可以省略 (从 Spring 4.3 开始,如果只有一个构造函数,@Autowired 可以省略) public UserService(UserRepository userRepository) { this.userRepository = userRepository; } } @Repository public class UserRepository { }
-
Java 配置类:
@Configuration public class AppConfig { @Bean public UserService userService(UserRepository userRepository) { return new UserService(userRepository); } @Bean public UserRepository userRepository() { return new UserRepository(); } }
2. Setter 方法注入 (Setter Injection):
-
原理: Spring 通过调用 Bean 类的 Setter 方法来注入依赖。
-
优点:
- 灵活性: 可以在 Bean 创建后修改依赖。
- 可选依赖: Setter 方法可以有默认值,或者可以不调用,因此可以支持可选的依赖。
- 解决循环依赖: Setter 注入可以解决构造函数注入可能导致的循环依赖问题。
-
缺点:
- 可能部分初始化: Bean 在创建时可能没有所有必需的依赖,需要在调用 Setter 方法后才能完全初始化。
- 可变性: 依赖可以被修改,可能导致意外的行为。
- 测试稍复杂: 需要手动调用 Setter 方法来设置依赖。
-
XML 配置:
<bean id="userService" class="com.example.UserService"> <property name="userRepository" ref="userRepository"/> </bean> <bean id="userRepository" class="com.example.UserRepository"/>
-
Java 注解:
@Service public class UserService { private UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } } @Repository public class UserRepository { }
-
Java 配置类: (不常用,通常直接在
@Bean
方法中使用构造函数注入)@Configuration public class AppConfig { @Bean public UserService userService() { UserService userService = new UserService(); userService.setUserRepository(userRepository()); // 手动调用 Setter 方法 return userService; } @Bean public UserRepository userRepository() { return new UserRepository(); } }
3. 字段注入 (Field Injection):
-
原理: Spring 直接通过反射设置 Bean 的字段值。
-
优点:
- 简洁: 代码最简洁,不需要构造函数或 Setter 方法。
-
缺点:
- 破坏封装性: 直接操作字段,绕过了 Setter 方法可能存在的逻辑。
- 难以测试: 无法通过构造函数或 Setter 方法注入模拟对象,测试困难。
- 反射开销: 反射操作通常比直接调用方法要慢。
- 与依赖注入容器紧耦合: 只能通过依赖注入容器注入依赖,无法手动注入。
- 不可变性无法保证
- 容易导致循环依赖
-
不推荐使用字段注入。
-
XML 配置: (不支持,需要使用
<property>
) -
Java 注解:
@Service public class UserService { @Autowired private UserRepository userRepository; // 直接注入到字段 } @Repository public class UserRepository { }
-
Java 配置类: (不支持,通常使用
@Bean
方法的构造函数注入)
Spring 内部如何实现:
-
解析 Bean 定义: Spring 解析 XML 配置、注解或 Java 配置类,生成
BeanDefinition
对象,其中包含了 Bean 的类名、构造函数参数、属性值、依赖关系等信息。 -
创建 Bean 实例: Spring 根据
BeanDefinition
中的信息,通过反射创建 Bean 的实例(通常是调用构造函数)。 -
属性填充 (Populate Bean): Spring 根据
BeanDefinition
中的属性配置,为 Bean 的属性赋值。BeanWrapper
: Spring 使用BeanWrapper
接口及其实现类(如BeanWrapperImpl
)来访问和操作 Bean 的属性。BeanWrapper
内部使用反射来调用 Setter 方法或设置字段值。PropertyAccessor
:BeanWrapper
实现了PropertyAccessor
接口,提供了更底层的属性访问方法。- 类型转换: 如果配置值的类型与属性的类型不匹配,Spring 会尝试进行类型转换(使用
TypeConverter
、ConversionService
或自定义的PropertyEditor
)。 - 自动装配: 如果启用了自动装配 (byType, byName, constructor),Spring 会根据类型或名称自动查找依赖的 Bean,并注入。
AutowiredAnnotationBeanPostProcessor
: 这是一个BeanPostProcessor
,它负责处理@Autowired
、@Value
、@Inject
、@Resource
等注解,实现依赖注入。 它实现了InstantiationAwareBeanPostProcessor
接口, 会在属性注入阶段被调用.
-
依赖解析: Spring 容器会解析 Bean 之间的依赖关系,并确保依赖的 Bean 在需要时被创建和注入。
示例(Spring 内部处理流程的简化描述):
假设有以下 Java 类:
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
构造函数注入:
- Spring 解析
UserService
的BeanDefinition
。 - Spring 发现
UserService
有一个带参数的构造函数。 - Spring 从容器中查找类型为
UserRepository
的 Bean(假设已存在)。 - Spring 使用反射调用
UserService
的构造函数,并将UserRepository
的实例作为参数传入。
Setter 方法注入:
- Spring 解析
UserService
的BeanDefinition
。 - Spring 创建
UserService
的实例(使用无参构造函数)。 - Spring 发现
UserService
有一个setUserRepository
方法。 - Spring 从容器中查找类型为
UserRepository
的 Bean。 - Spring 使用
BeanWrapper
和反射调用setUserRepository
方法,并将UserRepository
的实例作为参数传入。
字段注入:
- Spring 解析
UserService
的BeanDefinition
。 - Spring 创建
UserService
的实例。 AutowiredAnnotationBeanPostProcessor
检测到userRepository
字段上有@Autowired
注解。- Spring 从容器中查找类型为
UserRepository
的 Bean。 - Spring 使用
BeanWrapper
和反射直接设置userRepository
字段的值。
总结:
Spring 支持构造函数注入、Setter 方法注入和字段注入三种依赖注入方式。 推荐使用构造函数注入(强制依赖、不可变性、易于测试)。 Spring 内部使用反射、BeanWrapper
、AutowiredAnnotationBeanPostProcessor
等机制来实现依赖注入。