导航
📖 本讲目标
- 理解构造函数注入的原理
- 实现构造函数注入功能
- 支持 @Qualifier 在构造函数参数上的使用
- 理解构造函数注入 vs 字段注入的区别
🎯 构造函数注入概述
什么是构造函数注入?
构造函数注入是通过构造函数参数来注入依赖的方式。Spring 框架推荐使用构造函数注入,因为它具有以下优势:
- 不可变性:依赖可以声明为 final,确保不可变
- 强制依赖:构造函数注入确保所有必需依赖都被提供
- 易于测试:可以轻松创建测试对象
- 避免空指针:依赖在对象创建时就已经注入
使用示例
@Component
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository; // 通过构造函数注入
}
}
🔧 构造函数注入实现
实现思路
构造函数注入的核心流程:
- 获取类的所有构造函数
- 查找带 @Autowired 注解的构造函数
- 解析构造函数参数
- 检查参数是否有 @Qualifier 注解
- 从容器中获取依赖 Bean
- 使用依赖创建对象实例
实现代码
在 ApplicationContext 中添加构造函数注入方法:
/**
* 实例化 Bean(支持构造函数注入)
* @param beanName Bean 名称
* @param bd Bean 定义
* @return Bean 实例
*/
private Object instantiateBean(String beanName, BeanDefinition bd) throws Exception {
Class<?> clazz = bd.getBeanClass();
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
// 查找带 @Autowired 注解的构造函数
Constructor<?> autowiredConstructor = null;
for (Constructor<?> constructor : constructors) {
if (constructor.isAnnotationPresent(Autowired.class)) {
if (autowiredConstructor != null) {
throw new BeansException("类 " + clazz.getName() + " 有多个 @Autowired 构造函数");
}
autowiredConstructor = constructor;
}
}
if (autowiredConstructor != null) {
// 使用 @Autowired 构造函数
autowiredConstructor.setAccessible(true);
Parameter[] parameters = autowiredConstructor.getParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter param = parameters[i];
Class<?> paramType = param.getType();
// 检查是否有 @Qualifier 注解
String dependencyBeanName = null;
if (param.isAnnotationPresent(Qualifier.class)) {
Qualifier qualifier = param.getAnnotation(Qualifier.class);
dependencyBeanName = qualifier.value();
}
// 获取依赖的 Bean
try {
if (dependencyBeanName != null) {
args[i] = getBean(dependencyBeanName);
} else {
args[i] = getBean(paramType);
}
System.out.println(" ↳ 构造函数注入: " + paramType.getSimpleName() +
(dependencyBeanName != null ? " (按名称: " + dependencyBeanName + ")" : ""));
} catch (Exception e) {
Autowired autowired = autowiredConstructor.getAnnotation(Autowired.class);
if (autowired.required()) {
throw new BeansException("无法注入构造函数参数 " + param.getName() +
" 在类 " + clazz.getName() + ": " + e.getMessage(), e);
} else {
args[i] = null;
}
}
}
return autowiredConstructor.newInstance(args);
} else {
// 使用默认无参构造函数
return clazz.getDeclaredConstructor().newInstance();
}
}
更新 createBean 方法
private Object createBean(String beanName, BeanDefinition bd) throws Exception {
// 1. 检查循环依赖
if (singletonsCurrentlyInCreation.contains(beanName)) {
throw new RuntimeException("检测到循环依赖: " + beanName);
}
// 2. 标记为正在创建
singletonsCurrentlyInCreation.add(beanName);
try {
System.out.println("创建 Bean: " + beanName +
" (" + bd.getBeanClass().getSimpleName() + ")");
// 3. 实例化 Bean(支持构造函数注入)
Object bean = instantiateBean(beanName, bd);
// 4. 字段注入
populateBean(bean);
// TODO: 后续将实现初始化回调
return bean;
} finally {
// 5. 移除创建中标记
singletonsCurrentlyInCreation.remove(beanName);
}
}
🔍 构造函数选择策略
策略说明
- 优先使用 @Autowired 构造函数:如果存在带 @Autowired 注解的构造函数,优先使用
- 多个 @Autowired 构造函数:抛出异常(不允许)
- 无 @Autowired 构造函数:使用无参构造函数
代码实现
// 查找带 @Autowired 注解的构造函数
Constructor<?> autowiredConstructor = null;
for (Constructor<?> constructor : constructors) {
if (constructor.isAnnotationPresent(Autowired.class)) {
if (autowiredConstructor != null) {
throw new BeansException("类 " + clazz.getName() +
" 有多个 @Autowired 构造函数");
}
autowiredConstructor = constructor;
}
}
🏷️ @Qualifier 在构造函数参数上的使用
使用场景
当存在多个相同类型的 Bean 时,可以在构造函数参数上使用 @Qualifier:
@Component
public class OrderService {
private final IUserService userService;
@Autowired
public OrderService(@Qualifier("userService") IUserService userService) {
this.userService = userService;
}
}
实现代码
// 检查参数是否有 @Qualifier 注解
String dependencyBeanName = null;
if (param.isAnnotationPresent(Qualifier.class)) {
Qualifier qualifier = param.getAnnotation(Qualifier.class);
dependencyBeanName = qualifier.value();
}
// 获取依赖的 Bean
if (dependencyBeanName != null) {
args[i] = getBean(dependencyBeanName); // 按名称注入
} else {
args[i] = getBean(paramType); // 按类型注入
}
📊 构造函数注入 vs 字段注入
对比表格
| 特性 | 构造函数注入 | 字段注入 |
|---|---|---|
| 不可变性 | ✅ 可以声明 final | ❌ 不能声明 final |
| 强制依赖 | ✅ 必须提供所有依赖 | ⚠️ 可能忘记注入 |
| 测试友好 | ✅ 易于创建测试对象 | ⚠️ 需要反射或框架 |
| 循环依赖 | ⚠️ 难以解决 | ✅ 相对容易解决 |
| 代码简洁 | ⚠️ 构造函数较长 | ✅ 代码简洁 |
| Spring 推荐 | ✅ 推荐使用 | ⚠️ 不推荐 |
最佳实践
Spring 官方推荐:优先使用构造函数注入
原因:
- 依赖不可变(final)
- 强制提供所有依赖
- 易于单元测试
- 避免空指针异常
示例:
// ✅ 推荐:构造函数注入
@Component
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
// ⚠️ 不推荐:字段注入
@Component
public class UserService {
@Autowired
private UserRepository userRepository;
}
🧪 测试示例
测试类
package com.minispring.demo;
import com.minispring.beans.factory.annotation.Autowired;
import com.minispring.beans.factory.annotation.Qualifier;
import com.minispring.stereotype.Component;
import com.minispring.beans.factory.config.BeanDefinition;
import com.minispring.context.ApplicationContext;
@Component
class UserRepository {
public String getName() {
return "UserRepository";
}
}
@Component
class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
System.out.println("UserService 构造函数被调用");
}
public UserRepository getUserRepository() {
return userRepository;
}
}
public class ConstructorInjectionTest {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ApplicationContext();
// 手动注册 Bean
BeanDefinition repoBd = new BeanDefinition(UserRepository.class);
context.registerBeanDefinition("userRepository", repoBd);
BeanDefinition serviceBd = new BeanDefinition(UserService.class);
context.registerBeanDefinition("userService", serviceBd);
// 获取 Bean(会自动进行构造函数注入)
UserService service = context.getBean("userService", UserService.class);
// 验证注入是否成功
UserRepository repo = service.getUserRepository();
System.out.println("注入成功: " + repo.getName());
}
}
📝 本讲总结
本讲我们完成了:
- ✅ 理解了构造函数注入的原理和优势
- ✅ 实现了构造函数注入功能
- ✅ 支持 @Qualifier 在构造函数参数上的使用
- ✅ 实现了构造函数选择策略
- ✅ 理解了构造函数注入 vs 字段注入的区别
🎯 关键点回顾
- 构造函数注入:通过构造函数参数注入依赖
- @Autowired 构造函数:优先使用带注解的构造函数
- 参数解析:使用
Parameter获取参数信息 - 依赖获取:支持按类型和按名称注入
⚠️ 注意事项
- 多个 @Autowired 构造函数:不允许,会抛出异常
- 无参构造函数:如果没有 @Autowired 构造函数,使用无参构造函数
- 参数名称:Java 8+ 可以通过反射获取参数名称(需要编译时保留)
🚀 下一讲预告
在下一讲中,我们将实现 Bean 作用域,包括:
- Singleton 单例模式
- Prototype 原型模式
- @Scope 注解实现
- 作用域缓存策略
准备好了吗?让我们继续 第 06 讲:Bean 作用域!
思考题:
- 为什么 Spring 推荐使用构造函数注入?
- 构造函数注入和字段注入可以同时使用吗?
- 如何解决构造函数注入的循环依赖问题?
