题目
实现基于注解的简易依赖注入框架
信息
- 类型:问答
- 难度:⭐⭐
考点
自定义注解定义,反射实现依赖注入,单例模式应用
快速回答
实现要点:
- 定义
@Component注解标记可注入类 - 定义
@Autowired注解标记需要注入的字段 - 通过反射扫描包路径,收集所有
@Component类 - 创建并缓存单例实例
- 遍历字段完成依赖注入:
- 查找字段类型对应的实现类
- 从缓存获取或创建实例
- 通过
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注解:解决同一接口多个实现的歧义问题