2025年11月23日

[手写 MiniSpring 框架教程] 第 04 讲:依赖注入 – 字段注入

📖 本讲目标

  • 理解依赖注入的原理
  • 实现 @Autowired 注解
  • 使用反射实现字段注入
  • 实现按类型自动匹配
  • 理解循环依赖检测机制

🎯 什么是依赖注入?

依赖注入的概念

依赖注入(Dependency Injection,DI) 是 IoC 的一种实现方式,它通过外部容器将依赖对象注入到目标对象中,而不是由目标对象自己创建依赖。

依赖注入的方式

Spring 框架支持三种依赖注入方式:

  1. 字段注入:通过反射直接设置字段值
  2. 构造函数注入:通过构造函数参数注入
  3. 方法注入:通过 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;
}

🔧 字段注入实现

实现思路

字段注入的核心流程:

  1. 扫描目标对象的所有字段
  2. 检查字段是否有 @Autowired 注解
  3. 获取字段类型
  4. 检查是否有 @Qualifier 注解(按名称注入)
  5. 从容器中获取依赖 Bean
  6. 通过反射设置字段值

实现代码

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);
    }
}

检测流程

  1. 创建 ServiceA 时,将其加入 singletonsCurrentlyInCreation
  2. 注入 ServiceB 时,发现需要创建 ServiceB
  3. 创建 ServiceB 时,将其加入 singletonsCurrentlyInCreation
  4. 注入 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());
    }
}

📝 本讲总结

本讲我们完成了:

  1. ✅ 实现了 @Autowired 注解
  2. ✅ 实现了 @Qualifier 注解
  3. ✅ 使用反射实现了字段注入
  4. ✅ 实现了按类型和按名称的自动匹配
  5. ✅ 实现了循环依赖检测机制
  6. ✅ 支持可选依赖(required=false)

🎯 关键点回顾

  1. 反射机制:使用 Field.setAccessible(true) 访问私有字段
  2. 注解扫描:通过 field.isAnnotationPresent() 检查注解
  3. 类型匹配:使用 getBean(Class<T>) 按类型查找
  4. 循环依赖:通过创建中集合检测循环引用

⚠️ 注意事项

  1. 字段访问权限:必须调用 setAccessible(true) 才能访问私有字段
  2. 循环依赖:当前实现会抛出异常,Spring 框架使用三级缓存解决
  3. 性能考虑:反射操作有一定性能开销,但可以接受

🚀 下一讲预告

在下一讲中,我们将实现 依赖注入 – 构造函数注入,包括:

  • 构造函数注入实现
  • @Qualifier 在构造函数参数上的使用
  • 可选依赖处理
  • 构造函数注入 vs 字段注入的对比

准备好了吗?让我们继续 第 05 讲:依赖注入 – 构造函数注入


思考题

  1. 字段注入和构造函数注入各有什么优缺点?
  2. 为什么需要循环依赖检测?
  3. 如何优化字段注入的性能?

“以书为舟,遨游尘世”,
最好的免费 kindle 电子书分享站:

You may also like...

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


*