从零手写 Spring 风格 IoC 容器:支持自动扫描与依赖注入

Scroll Down

手写一个 Spring 风格的 IoC 容器(支持自动扫描和依赖注入)

一、前言

控制反转(IoC, Inversion of Control) 是 Spring 框架的核心机制之一,它将对象的创建和依赖管理交给 IoC 容器,避免了对象之间的强耦合。本文将从零实现一个支持自动扫描和依赖注入的 IoC 容器,以深入理解 IoC 的工作原理。


二、IoC 核心机制

一个完整的 IoC 容器通常包含以下功能:

  1. Bean 定义与管理:存储 Bean 的元信息(类、作用域、依赖等)。
  2. 依赖注入(DI, Dependency Injection):通过构造函数或 @Autowired 自动注入依赖。
  3. 自动扫描组件:基于 @Component 注解,自动发现并注册 Bean。
  4. 生命周期管理:支持初始化、销毁等回调。

三、手写 IoC 容器

1. 定义 BeanDefinition

BeanDefinition 主要用于存储 Bean 的元信息,如类对象、是否为单例等。

public class BeanDefinition {
    private Class<?> beanClass;
    private boolean singleton = true;

    public BeanDefinition(Class<?> beanClass) {
        this.beanClass = beanClass;
    }

    public Class<?> getBeanClass() {
        return beanClass;
    }

    public boolean isSingleton() {
        return singleton;
    }

    public void setSingleton(boolean singleton) {
        this.singleton = singleton;
    }
}

2. 自定义 @Component@Autowired 注解

@Component 注解

用于标注类,使 IoC 容器能自动扫描该类并注册。

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default ""; // Bean 名称,默认使用类名(首字母小写)
}

@Autowired 注解

用于标注成员变量,IoC 容器会自动注入该类型的 Bean。

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

3. 设计 ApplicationContext 容器

IoC 容器负责:

  • 通过类路径扫描找到 @Component 标注的类并自动注册。
  • 支持**@Autowired 依赖注入**,自动注入所需的 Bean。
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class ApplicationContext {
    private Map<String, Object> singletonBeans = new HashMap<>();

    public ApplicationContext(String basePackage) {
        scanComponents(basePackage);
        injectDependencies();
    }

    // 组件扫描
    private void scanComponents(String basePackage) {
        String path = basePackage.replace(".", "/");
        URL url = Thread.currentThread().getContextClassLoader().getResource(path);
        if (url == null) {
            throw new RuntimeException("No resources found for package: " + basePackage);
        }

        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            if (file.getName().endsWith(".class")) {
                String className = basePackage + "." + file.getName().replace(".class", "");
                try {
                    Class<?> clazz = Class.forName(className);
                    if (clazz.isAnnotationPresent(Component.class)) {
                        registerBean(clazz);
                    }
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException("Failed to load class: " + className, e);
                }
            }
        }
    }

    // 注册 Bean
    private void registerBean(Class<?> clazz) {
        Component component = clazz.getAnnotation(Component.class);
        String beanName = component.value().isEmpty() ? lowerFirst(clazz.getSimpleName()) : component.value();

        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();
            singletonBeans.put(beanName, instance);
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException("Failed to create bean: " + clazz.getName(), e);
        }
    }

    // 依赖注入
    private void injectDependencies() {
        for (Object bean : singletonBeans.values()) {
            Field[] fields = bean.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    try {
                        Object dependency = getBean(field.getType());
                        field.set(bean, dependency);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException("Failed to inject dependency: " + field.getName(), e);
                    }
                }
            }
        }
    }

    // 获取 Bean
    public Object getBean(String name) {
        return singletonBeans.get(name);
    }

    // 根据类型获取 Bean
    public Object getBean(Class<?> clazz) {
        return singletonBeans.values().stream()
                .filter(clazz::isInstance)
                .findFirst()
                .orElseThrow(() -> new RuntimeException("No bean found for type: " + clazz.getName()));
    }

    // 首字母小写
    private String lowerFirst(String str) {
        return Character.toLowerCase(str.charAt(0)) + str.substring(1);
    }
}

4. 业务类示例

(1) UserRepository 组件

@Component
public class UserRepository {
    public void save() {
        System.out.println("User saved!");
    }
}

(2) UserService 组件

@Component
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public void registerUser() {
        userRepository.save();
    }
}

5. 测试 IoC 容器

public class IoCTest {
    public static void main(String[] args) {
        ApplicationContext context = new ApplicationContext("com.example");

        UserService userService = (UserService) context.getBean(UserService.class);
        userService.registerUser(); // 输出:User saved!
    }
}

四、总结

1. IoC 容器的实现核心

自动扫描:使用 @Component 进行类扫描,自动注册 Bean。
依赖注入:支持 @Autowired 注入,无需手动管理对象。
单例模式:默认所有 Bean 都是单例模式。

2. 进一步优化方向

🔹 支持 @Scope("prototype") —— 让 Bean 可选单例或多例。
🔹 支持 @PostConstruct 生命周期回调 —— 允许 Bean 初始化后执行自定义逻辑。
🔹 支持 @Qualifier —— 解决多个相同类型 Bean 的注入问题。


五、结语

通过本次手写 IoC 容器,我们深入理解了 Spring IoC 的核心机制,并实现了:

  • Bean 注册
  • 单例管理
  • 自动扫描
  • 依赖注入

你可以基于本示例进一步扩展,如支持 XML 配置、Bean 生命周期管理等,打造更完整的 IoC 容器。🚀