模仿Spring的@Autowired行为。核心思路是:
- 手动管理一个“Bean仓库”(类似Spring的容器)。
- 按类型查找需要的对象。
- 使用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注入到UserService的userRepository字段里。即把其中的创建 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类型的对象。如果需要处理多个同类型对象,可以:
- 修改
Map为Map<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。