侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

实现基于注解的简易依赖注入框架

2025-12-5 / 0 评论 / 4 阅读

题目

实现基于注解的简易依赖注入框架

信息

  • 类型:问答
  • 难度:⭐⭐

考点

自定义注解定义,反射实现依赖注入,单例模式应用

快速回答

实现要点:

  1. 定义@Component注解标记可注入类
  2. 定义@Autowired注解标记需要注入的字段
  3. 通过反射扫描包路径,收集所有@Component
  4. 创建并缓存单例实例
  5. 遍历字段完成依赖注入:
    • 查找字段类型对应的实现类
    • 从缓存获取或创建实例
    • 通过Field.set()注入依赖
## 解析

1. 问题背景

依赖注入(DI)是现代框架的核心功能。本题要求通过反射和注解实现简易DI容器,考察对Java元编程的理解。

2. 核心实现步骤

2.1 定义注解

// 标记可被容器管理的组件
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {}

// 标记需要自动注入的字段
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {}

2.2 DI容器实现

public class DIContainer {
    private final Map<Class<?>, Object> instances = new ConcurrentHashMap<>();

    public void init(Class<?>... entryClasses) {
        // 扫描所有@Component类
        Set<Class<?>> components = new HashSet<>();
        for (Class<?> entryClass : entryClasses) {
            components.addAll(scanClasses(entryClass.getPackageName()));
        }

        // 创建单例实例
        components.forEach(clazz -> {
            try {
                instances.put(clazz, clazz.getDeclaredConstructor().newInstance());
            } catch (Exception e) {
                throw new RuntimeException("创建实例失败: " + clazz.getName(), e);
            }
        });

        // 依赖注入
        instances.values().forEach(this::injectDependencies);
    }

    private void injectDependencies(Object instance) {
        for (Field field : instance.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                Class<?> fieldType = field.getType();
                Object dependency = instances.get(fieldType);

                if (dependency == null) {
                    throw new RuntimeException("未找到依赖: " + fieldType.getName());
                }

                try {
                    field.setAccessible(true);
                    field.set(instance, dependency);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("注入失败: " + field.getName(), e);
                }
            }
        }
    }

    // 包扫描实现(简化版)
    private Set<Class<?>> scanClasses(String packageName) { /* 使用ClassLoader扫描包 */ }
}

3. 使用示例

@Component
public class ServiceA {
    @Autowired
    private ServiceB serviceB;

    public void execute() {
        serviceB.process();
    }
}

@Component
public class ServiceB {
    public void process() {
        System.out.println("Processing...");
    }
}

// 启动容器
public class Main {
    public static void main(String[] args) {
        DIContainer container = new DIContainer();
        container.init(ServiceA.class);

        ServiceA serviceA = (ServiceA) container.getBean(ServiceA.class);
        serviceA.execute();  // 输出: Processing...
    }
}

4. 最佳实践

  • 循环依赖检测:添加依赖注入状态跟踪,避免StackOverflowError
  • 异常处理:封装自定义异常(如NoSuchBeanDefinitionException)
  • 作用域扩展:通过@Scope注解支持原型(Prototype)作用域
  • 延迟初始化:添加@Lazy注解支持按需创建

5. 常见错误

  • 未处理接口注入:示例仅支持具体类注入,实际需支持接口+实现类映射
  • 线程安全问题:并发环境下容器初始化需同步控制
  • 扫描性能问题:大型项目应缓存扫描结果
  • 遗漏父类字段getDeclaredFields()不返回父类字段,需递归处理

6. 扩展知识

  • 字节码增强:Spring使用CGLIB进行代理增强
  • JSR-330标准:javax.inject规范定义@Inject/@Singleton
  • 方法注入:支持setter方法和构造器注入
  • Qualifier注解:解决同一接口多个实现的歧义问题