题目
如何通过反射和注解实现一个简单的依赖注入框架?
信息
- 类型:问答
- 难度:⭐⭐
考点
自定义注解定义,反射动态实例化,依赖注入实现,注解处理器设计
快速回答
实现步骤:
- 定义
@Autowired注解标记需要注入的字段 - 定义
@Component注解标记可被管理的组件 - 创建容器类扫描类路径,识别带
@Component的类并实例化 - 通过反射遍历字段,为带
@Autowired的字段注入对应实例
关键点:
- 使用
Class.getDeclaredFields()获取字段 - 通过
Field.setAccessible(true)突破私有字段限制 - 维护实例缓存避免重复创建
1. 核心原理说明
依赖注入(DI)通过反射动态识别类之间的依赖关系并自动装配。自定义注解作为元数据标记需要处理的类/字段,反射机制在运行时解析这些注解并完成对象实例化和依赖注入。
2. 完整代码实现
步骤1:定义注解
// 标记可被容器管理的组件
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {}
// 标记需要自动注入的字段
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {}
步骤2:实现容器
public class DIContainer {
private final Map<Class<?>, Object> instances = new HashMap<>();
public void init(String basePackage) {
// 扫描包路径(简化版,实际需用ClassLoader)
Class<?>[] classes = {UserService.class, UserRepository.class};
// 实例化组件
for (Class<?> clazz : classes) {
if (clazz.isAnnotationPresent(Component.class)) {
try {
Object instance = clazz.getDeclaredConstructor().newInstance();
instances.put(clazz, instance);
} catch (Exception e) {
throw new RuntimeException("创建实例失败: " + clazz.getName(), e);
}
}
}
// 依赖注入
for (Object bean : instances.values()) {
injectDependencies(bean);
}
}
private void injectDependencies(Object bean) {
for (Field field : bean.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
Class<?> fieldType = field.getType();
Object dependency = instances.get(fieldType);
if (dependency == null) {
throw new IllegalStateException("未找到依赖: " + fieldType.getName());
}
try {
field.setAccessible(true); // 突破私有限制
field.set(bean, dependency);
} catch (IllegalAccessException e) {
throw new RuntimeException("注入失败", e);
}
}
}
}
public <T> T getBean(Class<T> clazz) {
return clazz.cast(instances.get(clazz));
}
}
步骤3:使用示例
@Component
public class UserService {
@Autowired
private UserRepository userRepo; // 自动注入
public String getUserName(Long id) {
return userRepo.findNameById(id);
}
}
@Component
public class UserRepository {
public String findNameById(Long id) {
return "User" + id;
}
}
// 测试代码
public class Main {
public static void main(String[] args) {
DIContainer container = new DIContainer();
container.init("com.example");
UserService service = container.getBean(UserService.class);
System.out.println(service.getUserName(1L)); // 输出: User1
}
}
3. 最佳实践
- 循环依赖处理:使用三级缓存或构造器注入避免
- 性能优化:缓存反射得到的Field/Method对象
- 作用域扩展:增加@Scope注解实现单例/原型模式
- 异常处理:明确抛出BeanCreationException而非RuntimeException
4. 常见错误
- 未处理私有字段:忘记调用field.setAccessible(true)
- 遗漏注解保留策略:未设置@Retention(RetentionPolicy.RUNTIME)导致运行时无法获取注解
- 循环依赖:A依赖B的同时B也依赖A导致栈溢出
- 作用域混淆:未区分单例和原型导致状态污染
5. 扩展知识
- Spring框架实现:实际Spring使用BeanFactory+BeanPostProcessor扩展点
- 方法注入:通过@Autowired注解方法实现更灵活的注入
- Qualifier注解:解决同一接口多个实现类的歧义问题
- ASM字节码操作:生产环境通常避免直接反射,改用字节码增强提升性能