手写实现简化的DI机制,模仿Spring的@Autowired行为。

63 阅读3分钟

模仿Spring的@Autowired行为。核心思路是:

  1. 手动管理一个“Bean仓库”(类似Spring的容器)。
  2. 按类型查找需要的对象。
  3. 使用Java的反射机制把对象注入到目标字段。

目标场景

还是以经典的服务层和仓储层之间的调用为例,假设我们有以下类:

public interface UserRepository {
    void save();
}

public class UserRepositoryImpl implements UserRepository {
    public void save() {
        System.out.println("Saving user...");
    }
}

public class UserService {
    private UserRepository userRepository; // 需要注入的字段

    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

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

我们希望手动实现一个机制,把UserRepositoryImpl注入到UserServiceuserRepository字段里。即把其中的创建 UserRepositoryImpl 实例,和实例注入到 UserService 中的代码 自动完成。

public class Demo {
    public static void main(String[] args) {
        // 创建 UserService 实例
        UserService userService = new UserService();

        // 创建 UserRepositoryImpl 实例
        UserRepository userRepository = new UserRepositoryImpl();

        // 将 UserRepositoryImpl 实例注入到 UserService 中
        userService.setUserRepository(userRepository);

        // 调用 doSomething() 方法
        userService.doSomething();
    }
}

手写实现代码

以下是完整代码,包含一个简单的容器和注入逻辑:

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

// 简化的容器类,类似Spring的Bean容器
class MyContainer {
    // “bean仓库”,存储对象
    private Map<Class<?>, Object> beans = new HashMap<>();

    // 注册对象到容器 即向hashmap添加k-v的过程
    public void registerBean(Class<?> type, Object instance) {
        beans.put(type, instance);
    }

    // 获取对象,按类型查找
    public Object getBean(Class<?> type) {
        return beans.get(type);
    }

    // 手动注入方法
    public void autowire(Object target) {
        // 获取目标类的所有字段
        Field[] fields = target.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 假设我们用一个自定义注解标记需要注入的字段
            if (field.isAnnotationPresent(MyAutowired.class)) {
                // 获取字段的类型
                Class<?> fieldType = field.getType();
                // 从容器中找匹配的对象
                Object dependency = getBean(fieldType);
                if (null == dependency) {
                    throw new RuntimeException("No bean found for type: " + fieldType.getName());
                }
                try {
                    // 用反射注入
                    field.setAccessible(true); // 打开私有字段权限
                    field.set(target, dependency); // 把对象塞进去
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Failed to inject dependency", e);
                }
            }
        }
    }
}
// 自定义注解,模仿@Autowired
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface MyAutowired {
}
// 测试代码
public class Main {
    public static void main(String[] args) {
        // 创建简化的容器
        MyContainer container = new MyContainer();

        // 注册零件到仓库
        UserRepository userRepo = new UserRepositoryImpl();
        container.registerBean(UserRepository.class, userRepo);

        // 创建需要注入的对象
        UserService userService = new UserService();

        // 手动触发注入
        container.autowire(userService);

        // 测试注入结果
        userService.doSomething(); // 输出: Saving user...
    }
}
// UserService类,添加自定义注解
class UserService {
    @MyAutowired
    private UserRepository userRepository; // 用自定义注解标记

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

// UserRepository接口和实现类
interface UserRepository {
    void save();
}

class UserRepositoryImpl implements UserRepository {
    public void save() {
        System.out.println("Saving user...");
    }
}

如果有多个同类型对象怎么办?

就像Spring会“懵掉”一样,我们的代码也会失败,因为Map里只能存一个UserRepository类型的对象。如果需要处理多个同类型对象,可以:

  • 修改MapMap<Class<?>, List<Object>>,存一个类型对应多个实例。
  • 加一个“名字”机制(类似@Qualifier),在注解里指定具体名字。

例如,扩展后的注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface MyAutowired {
    String name() default ""; // 指定名字
}

然后调整容器逻辑:

class MyContainer {
    private Map<String, Object> namedBeans = new HashMap<>();

    public void registerBean(String name, Object instance) {
        namedBeans.put(name, instance);
    }

    public void autowire(Object target) {
        Field[] fields = target.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(MyAutowired.class)) {
                MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                String beanName = autowired.name();
                Object dependency = namedBeans.get(beanName);
                if (dependency == null) {
                    throw new RuntimeException("No bean found for name: " + beanName);
                }
                try {
                    field.setAccessible(true);
                    field.set(target, dependency);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Failed to inject", e);
                }
            }
        }
    }
}

总结

不用Spring,我们可以用一个简单的容器(Map)存对象,用反射找到字段并注入。这种方式模仿了@Autowired按类型查找和注入的过程。如果需要多个同类型对象,可以加名字机制,类似@Qualifier