导航
📖 本讲目标
- 理解依赖注入的原理
- 实现 @Autowired 注解
- 使用反射实现字段注入
- 实现按类型自动匹配
- 理解循环依赖检测机制
🎯 什么是依赖注入?
依赖注入的概念
依赖注入(Dependency Injection,DI) 是 IoC 的一种实现方式,它通过外部容器将依赖对象注入到目标对象中,而不是由目标对象自己创建依赖。
依赖注入的方式
Spring 框架支持三种依赖注入方式:
- 字段注入:通过反射直接设置字段值
- 构造函数注入:通过构造函数参数注入
- 方法注入:通过 setter 方法注入
本讲重点讲解字段注入。
🏷️ @Autowired 注解实现
注解定义
创建 src/main/java/com/minispring/beans/factory/annotation/Autowired.java:
package com.minispring.beans.factory.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Autowired 注解用于标记需要自动注入的依赖
* 支持字段注入、构造函数注入和方法注入
*/
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
/**
* 是否必须注入,默认为 true
* 如果为 true 且找不到匹配的 Bean,则抛出异常
* 如果为 false 且找不到匹配的 Bean,则注入 null
*/
boolean required() default true;
/**
* 指定要注入的 Bean 名称
* 如果不指定,则按类型自动匹配
*/
String value() default "";
}
注解说明
- @Target:指定注解可以应用在字段、构造函数和方法上
- @Retention(RUNTIME):运行时可以通过反射获取
- required:控制依赖是否必须
- value:指定 Bean 名称(用于按名称注入)
🔍 @Qualifier 注解实现
注解定义
创建 src/main/java/com/minispring/beans/factory/annotation/Qualifier.java:
package com.minispring.beans.factory.annotation;
import java.lang.annotation.*;
/**
* 用于指定注入 Bean 的名称
* 与 @Autowired 配合使用,实现按名称注入
*/
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Qualifier {
/**
* Bean 的名称
*/
String value();
}
使用场景
当容器中存在多个相同类型的 Bean 时,使用 @Qualifier 指定要注入的 Bean 名称:
@Component("userRepository1")
public class UserRepository1 implements IUserRepository { }
@Component("userRepository2")
public class UserRepository2 implements IUserRepository { }
@Component
public class UserService {
@Autowired
@Qualifier("userRepository1") // 指定注入 userRepository1
private IUserRepository repository;
}
🔧 字段注入实现
实现思路
字段注入的核心流程:
- 扫描目标对象的所有字段
- 检查字段是否有 @Autowired 注解
- 获取字段类型
- 检查是否有 @Qualifier 注解(按名称注入)
- 从容器中获取依赖 Bean
- 通过反射设置字段值
实现代码
在 ApplicationContext 中添加字段注入方法:
/**
* 属性注入:处理 @Autowired 字段
* @param bean Bean 实例
*/
private void populateBean(Object bean) throws Exception {
Class<?> clazz = bean.getClass();
// 获取所有字段(包括父类字段)
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 检查字段是否有 @Autowired 注解
if (!field.isAnnotationPresent(Autowired.class)) {
continue;
}
Autowired autowired = field.getAnnotation(Autowired.class);
boolean required = autowired.required();
// 获取字段类型
Class<?> fieldType = field.getType();
// 检查是否有 @Qualifier 注解(按名称注入)
String beanName = null;
if (field.isAnnotationPresent(Qualifier.class)) {
Qualifier qualifier = field.getAnnotation(Qualifier.class);
beanName = qualifier.value();
System.out.println(" ↳ 字段注入: " + field.getName() +
" (按名称: " + beanName + ")");
} else {
System.out.println(" ↳ 字段注入: " + field.getName() +
" (按类型: " + fieldType.getSimpleName() + ")");
}
// 获取依赖的 Bean
Object dependency = null;
try {
if (beanName != null) {
// 按名称注入
dependency = getBean(beanName);
} else {
// 按类型注入
dependency = getBean(fieldType);
}
} catch (Exception e) {
if (required) {
throw new RuntimeException("无法注入字段 " + field.getName() +
" 在类 " + clazz.getName() + ": " + e.getMessage(), e);
} else {
System.out.println(" ⚠ 可选依赖未找到,注入 null");
dependency = null;
}
}
// 设置字段可访问(即使是 private)
field.setAccessible(true);
// 通过反射设置字段值
field.set(bean, dependency);
if (dependency != null) {
System.out.println(" ✓ 注入成功: " +
(dependency.getClass().getSimpleName()));
}
}
}
更新 createBean 方法
在创建 Bean 后调用字段注入:
private Object createBean(String beanName, BeanDefinition bd) throws Exception {
// 1. 检查循环依赖
if (singletonsCurrentlyInCreation.contains(beanName)) {
throw new RuntimeException("检测到循环依赖: " + beanName +
" -> " + singletonsCurrentlyInCreation);
}
// 2. 标记为正在创建
singletonsCurrentlyInCreation.add(beanName);
try {
System.out.println("创建 Bean: " + beanName +
" (" + bd.getBeanClass().getSimpleName() + ")");
// 3. 实例化 Bean(使用无参构造函数)
Object bean = bd.getBeanClass().getDeclaredConstructor().newInstance();
// 4. 字段注入
populateBean(bean);
// TODO: 后续将实现初始化回调
return bean;
} finally {
// 5. 移除创建中标记
singletonsCurrentlyInCreation.remove(beanName);
}
}
🔄 循环依赖检测
循环依赖问题
循环依赖是指两个或多个 Bean 相互依赖,形成循环引用:
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA; // 形成循环依赖
}
检测机制
使用 singletonsCurrentlyInCreation 集合记录正在创建的 Bean:
// 正在创建中的 Bean 集合
private final Set<String> singletonsCurrentlyInCreation = ConcurrentHashMap.newKeySet();
private Object createBean(String beanName, BeanDefinition bd) throws Exception {
// 1. 检查循环依赖
if (singletonsCurrentlyInCreation.contains(beanName)) {
throw new RuntimeException("检测到循环依赖: " + beanName +
" -> " + singletonsCurrentlyInCreation);
}
// 2. 标记为正在创建
singletonsCurrentlyInCreation.add(beanName);
try {
// 创建 Bean...
// 在创建过程中,如果依赖的 Bean 也需要创建,
// 会递归调用 createBean,此时可以检测到循环依赖
} finally {
// 3. 移除创建中标记
singletonsCurrentlyInCreation.remove(beanName);
}
}
检测流程
- 创建 ServiceA 时,将其加入
singletonsCurrentlyInCreation - 注入 ServiceB 时,发现需要创建 ServiceB
- 创建 ServiceB 时,将其加入
singletonsCurrentlyInCreation - 注入 ServiceA 时,发现 ServiceA 正在创建中 → 检测到循环依赖
🧪 测试示例
测试类
package com.minispring.demo;
import com.minispring.beans.factory.annotation.Autowired;
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 {
@Autowired
private UserRepository userRepository;
public UserRepository getUserRepository() {
return userRepository;
}
}
public class FieldInjectionTest {
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());
}
}
📝 本讲总结
本讲我们完成了:
- ✅ 实现了 @Autowired 注解
- ✅ 实现了 @Qualifier 注解
- ✅ 使用反射实现了字段注入
- ✅ 实现了按类型和按名称的自动匹配
- ✅ 实现了循环依赖检测机制
- ✅ 支持可选依赖(required=false)
🎯 关键点回顾
- 反射机制:使用
Field.setAccessible(true)访问私有字段 - 注解扫描:通过
field.isAnnotationPresent()检查注解 - 类型匹配:使用
getBean(Class<T>)按类型查找 - 循环依赖:通过创建中集合检测循环引用
⚠️ 注意事项
- 字段访问权限:必须调用
setAccessible(true)才能访问私有字段 - 循环依赖:当前实现会抛出异常,Spring 框架使用三级缓存解决
- 性能考虑:反射操作有一定性能开销,但可以接受
🚀 下一讲预告
在下一讲中,我们将实现 依赖注入 – 构造函数注入,包括:
- 构造函数注入实现
- @Qualifier 在构造函数参数上的使用
- 可选依赖处理
- 构造函数注入 vs 字段注入的对比
准备好了吗?让我们继续 第 05 讲:依赖注入 – 构造函数注入!
思考题:
- 字段注入和构造函数注入各有什么优缺点?
- 为什么需要循环依赖检测?
- 如何优化字段注入的性能?
