2025年11月23日

[手写 MiniSpring 框架教程] 第 05 讲:依赖注入 – 构造函数注入

📖 本讲目标

  • 理解构造函数注入的原理
  • 实现构造函数注入功能
  • 支持 @Qualifier 在构造函数参数上的使用
  • 理解构造函数注入 vs 字段注入的区别

🎯 构造函数注入概述

什么是构造函数注入?

构造函数注入是通过构造函数参数来注入依赖的方式。Spring 框架推荐使用构造函数注入,因为它具有以下优势:

  1. 不可变性:依赖可以声明为 final,确保不可变
  2. 强制依赖:构造函数注入确保所有必需依赖都被提供
  3. 易于测试:可以轻松创建测试对象
  4. 避免空指针:依赖在对象创建时就已经注入

使用示例

@Component
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;  // 通过构造函数注入
    }
}

🔧 构造函数注入实现

实现思路

构造函数注入的核心流程:

  1. 获取类的所有构造函数
  2. 查找带 @Autowired 注解的构造函数
  3. 解析构造函数参数
  4. 检查参数是否有 @Qualifier 注解
  5. 从容器中获取依赖 Bean
  6. 使用依赖创建对象实例

实现代码

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

🔍 构造函数选择策略

策略说明

  1. 优先使用 @Autowired 构造函数:如果存在带 @Autowired 注解的构造函数,优先使用
  2. 多个 @Autowired 构造函数:抛出异常(不允许)
  3. 无 @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 官方推荐:优先使用构造函数注入

原因

  1. 依赖不可变(final)
  2. 强制提供所有依赖
  3. 易于单元测试
  4. 避免空指针异常

示例

// ✅ 推荐:构造函数注入
@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());
    }
}

📝 本讲总结

本讲我们完成了:

  1. ✅ 理解了构造函数注入的原理和优势
  2. ✅ 实现了构造函数注入功能
  3. ✅ 支持 @Qualifier 在构造函数参数上的使用
  4. ✅ 实现了构造函数选择策略
  5. ✅ 理解了构造函数注入 vs 字段注入的区别

🎯 关键点回顾

  1. 构造函数注入:通过构造函数参数注入依赖
  2. @Autowired 构造函数:优先使用带注解的构造函数
  3. 参数解析:使用 Parameter 获取参数信息
  4. 依赖获取:支持按类型和按名称注入

⚠️ 注意事项

  1. 多个 @Autowired 构造函数:不允许,会抛出异常
  2. 无参构造函数:如果没有 @Autowired 构造函数,使用无参构造函数
  3. 参数名称:Java 8+ 可以通过反射获取参数名称(需要编译时保留)

🚀 下一讲预告

在下一讲中,我们将实现 Bean 作用域,包括:

  • Singleton 单例模式
  • Prototype 原型模式
  • @Scope 注解实现
  • 作用域缓存策略

准备好了吗?让我们继续 第 06 讲:Bean 作用域


思考题

  1. 为什么 Spring 推荐使用构造函数注入?
  2. 构造函数注入和字段注入可以同时使用吗?
  3. 如何解决构造函数注入的循环依赖问题?

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

You may also like...

发表回复

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


*