揭秘 @Autowired:从零手写一个迷你版依赖注入框架
作为 Java 开发者,@Autowired
就像我们的老朋友,每天见面,却又似乎从未真正了解过它。我们知道它能“自动”注入依赖,但在这份“自动”的背后,Spring 究竟施展了怎样的魔法?
依赖注入(DI)是 Spring 框架的基石,而理解其原理最好的方式,莫过于亲手实现一遍。今天,就让我们扮演一次框架的设计者,从零开始,构建一个极简但核心思想完备的依赖注入框架,以此来彻底揭开 @Autowired
的神秘面纱。
Chapter 1: The Grand Design - 蓝图规划
在动手之前,我们需要明确我们的目标。我们将要构建一个迷你版的 IoC 容器,它需要具备 Spring 的三大核心能力:
- 组件扫描 (Component Scanning): 能够识别哪些类需要被容器管理。
- Bean 实例化 (Bean Instantiation): 负责创建这些类的实例,并存放在容器中。
- 依赖注入 (Dependency Injection): 自动将所需的依赖项注入到相应的 Bean 中。
更重要的是,我们的实现将模仿 Spring 的一个关键性能优化:缓存注入元数据。这意味着,我们将遵循 Spring AutowiredAnnotationBeanPostProcessor
的设计,将“发现注入点”和“执行注入”分离,避免不必要的反射开销。
我们的建筑材料清单:
@MiniComponent
: 一个自定义注解,用于标记需要被管理的类。@MiniAutowired
: 我们的主角,用于标记需要被注入的字段。UserService
&OrderService
: 两个业务类,用于演示依赖关系。MiniApplicationContext
: 我们迷你框架的大脑,即 IoC 容器本身。
Chapter 2: The Building Blocks - 定义注解与元数据
万丈高楼平地起,我们先从最基础的注解和元数据结构开始。
2.1 自定义注解
这两个注解是框架与用户代码之间的“约定”。
@MiniComponent
: 告诉我们的容器,“请管理我!”
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) // 作用于类上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,以便反射读取
public @interface MiniComponent {
}
@MiniAutowired
: 告诉我们的容器,“请在这里给我注入一个依赖!”
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD) // 作用于字段上
@Retention(RetentionPolicy.RUNTIME)
public @interface MiniAutowired {
}
2.2 注入元数据
这是我们模仿 Spring 设计思想的关键。我们不直接操作字段,而是先为它们创建“图纸”(元数据)。
MiniInjectedField.java
: 封装一个需要注入的字段。
import java.lang.reflect.Field;
public class MiniInjectedField {
private final Field field;
public MiniInjectedField(Field field) { this.field = field; }
public Field getField() { return field; }
}
MiniInjectionMetadata.java
: 封装一个类中所有的注入点信息,并负责执行注入。
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
public class MiniInjectionMetadata {
private final Class<?> targetClass;
private final List<MiniInjectedField> injectedFields;
public MiniInjectionMetadata(Class<?> targetClass, List<MiniInjectedField> injectedFields) {
this.targetClass = targetClass;
this.injectedFields = injectedFields;
}
public void inject(Object beanInstance, Map<Class<?>, Object> beanMap) throws IllegalAccessException {
if (injectedFields.isEmpty()) return;
for (MiniInjectedField injectedField : injectedFields) {
Field field = injectedField.getField();
Class<?> dependencyType = field.getType();
Object dependency = beanMap.get(dependencyType);
if (dependency != null) {
field.setAccessible(true);
field.set(beanInstance, dependency);
System.out.printf(" [Inject] 成功将 [%s] 注入到 [%s] 的字段 [%s]\n",
dependency.getClass().getSimpleName(),
beanInstance.getClass().getSimpleName(),
field.getName());
}
}
}
}
Chapter 3: The Services - 准备我们的业务组件
现在,我们用刚刚定义的注解来标记我们的业务类。
UserService.java
: 这是一个将被注入的依赖。
@MiniComponent // 标记为组件
public class UserService {
public void findUser() {
System.out.println("成功找到了用户...");
}
}
OrderService.java
: 这个类需要 UserService
。
@MiniComponent // 也标记为组件
public class OrderService {
@MiniAutowired // 在此字段上请求注入
private UserService userService;
public void placeOrder() {
System.out.println("正在处理订单...");
userService.findUser(); // 调用依赖的方法
System.out.println("订单处理完成!");
}
public UserService getUserService() { return userService; }
}
Chapter 4: The Engine Room - 构建核心容器 MiniApplicationContext
这是最激动人心的部分,我们将构建驱动一切的核心引擎。注意看代码中的注释,它解释了每一步如何模仿 Spring。
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class MiniApplicationContext {
// 1. IoC 容器本身,用于存储单例 Bean
private final Map<Class<?>, Object> beanMap = new HashMap<>();
// 2. 注入元数据缓存,模仿 Spring 的性能优化
private final Map<Class<?>, MiniInjectionMetadata> metadataCache = new HashMap<>();
public MiniApplicationContext(Class<?>... componentClasses) {
try {
// 步骤一:实例化所有组件
instantiate(componentClasses);
// 步骤二:执行依赖注入
populateProperties();
} catch (Exception e) {
e.printStackTrace();
}
}
// 模拟 Spring 实例化 Bean 的过程
private void instantiate(Class<?>... componentClasses) throws Exception {
System.out.println("--- 步骤 1: 实例化 Beans ---");
for (Class<?> clazz : componentClasses) {
if (clazz.isAnnotationPresent(MiniComponent.class)) {
Object instance = clazz.getDeclaredConstructor().newInstance();
beanMap.put(clazz, instance);
System.out.println(" [Instance] 成功实例化 Bean: " + clazz.getSimpleName());
}
}
}
// 模拟 BeanPostProcessor 的工作,填充属性
private void populateProperties() throws IllegalAccessException {
System.out.println("\n--- 步骤 2: 填充属性 (执行注入) ---");
for (Object beanInstance : beanMap.values()) {
// 2.1 寻找并构建该 Bean 的注入元数据
MiniInjectionMetadata metadata = findAutowiringMetadata(beanInstance.getClass());
// 2.2 使用元数据进行注入
metadata.inject(beanInstance, beanMap);
}
}
// 模仿 Spring 的 findAutowiringMetadata 方法,包含缓存逻辑
private MiniInjectionMetadata findAutowiringMetadata(Class<?> beanClass) {
if (metadataCache.containsKey(beanClass)) {
System.out.printf(" [Cache] 命中缓存,直接使用 [%s] 的注入元数据\n", beanClass.getSimpleName());
return metadataCache.get(beanClass);
}
System.out.printf(" [Scan] 未命中缓存,开始扫描 [%s] 的注入点...\n", beanClass.getSimpleName());
List<MiniInjectedField> injectedFields = new LinkedList<>();
Field[] fields = beanClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(MiniAutowired.class)) {
injectedFields.add(new MiniInjectedField(field));
System.out.printf(" - 发现注入点: 字段 [%s]\n", field.getName());
}
}
MiniInjectionMetadata metadata = new MiniInjectionMetadata(beanClass, injectedFields);
metadataCache.put(beanClass, metadata);
return metadata;
}
// 对外提供获取 Bean 的方法
public <T> T getBean(Class<T> clazz) {
return (T) beanMap.get(clazz);
}
}
Chapter 5: Ignition! - 启动与验证
一切就绪,让我们点火启动我们的迷你框架。
public class MainApplication {
public static void main(String[] args) {
System.out.println("启动我们的迷你 IoC 容器...");
// 1. 创建应用上下文,告诉它要管理哪些组件
MiniApplicationContext context = new MiniApplicationContext(
OrderService.class,
UserService.class
);
System.out.println("\n--- 容器初始化完成 ---");
// 2. 从容器中获取 Bean
OrderService orderService = context.getBean(OrderService.class);
// 3. 验证依赖是否注入成功
System.out.println("\n--- 开始验证 ---");
if (orderService != null && orderService.getUserService() != null) {
System.out.println("验证成功:OrderService 中的 UserService 实例已成功注入!");
orderService.placeOrder();
} else {
System.out.println("验证失败:依赖注入未成功。");
}
}
}
运行 MainApplication
,你将看到清晰的执行流程:
启动我们的迷你 IoC 容器...
--- 步骤 1: 实例化 Beans ---
[Instance] 成功实例化 Bean: OrderService
[Instance] 成功实例化 Bean: UserService
--- 步骤 2: 填充属性 (执行注入) ---
[Scan] 未命中缓存,开始扫描 [OrderService] 的注入点...
- 发现注入点: 字段 [userService]
[Inject] 成功将 [UserService] 注入到 [OrderService] 的字段 [userService]
[Scan] 未命中缓存,开始扫描 [UserService] 的注入点...
--- 容器初始化完成 ---
--- 开始验证 ---
验证成功:OrderService 中的 UserService 实例已成功注入!
正在处理订单...
成功找到了用户...
订单处理完成!
Conclusion: What Have We Learned? - 总结与升华
通过这次亲手实践,我们不仅实现了一个功能,更重要的是,我们理解了其背后的设计哲学:
- IoC 是核心:
beanMap
就是我们的 IoC 容器,它将对象的创建和管理权从用户代码中解放出来。 - 注解是元数据:
@MiniAutowired
本身不执行任何操作,它只是一个等待被框架处理器读取的“路标”。 - 反射是实现手段:没有 Java 反射,动态地检查注解和修改私有字段就无从谈起,自动化也就成了空谈。
- 设计模式是灵魂:关注点分离(发现元数据 vs 执行注入)和缓存模式(使用
metadataCache
提升性能)是这个迷你框架乃至整个 Spring 框架高效、健壮的秘诀。
现在,当您再回过头去看 Spring 的源码,看到那些 BeanPostProcessor
、InjectionMetadata
时,它们将不再是遥远而抽象的概念,而是一位你已经亲手“复刻”过的老朋友。