初学者用map配合自定义注解模拟依赖注入,核心是“三步落地”:定义@component和@autowired注解标记角色、用concurrenthashmap存bean实例、靠两轮反射完成扫描注册与字段注入。

初学者用 Map 配合自定义注解模拟依赖注入,核心是“三步落地”:定义注解标记角色、用 ConcurrentHashMap 存 Bean 实例、靠反射完成扫描与注入。不依赖 Spring,却能清晰看到 IoC 容器怎么把对象串起来。
定义两个关键注解
注解就是给类和字段贴的“标签”,让容器知道“谁该被创建”“谁需要被填值”:
-
@Component:加在类上,比如
@Component public class UserDao { },表示这个类要被容器管理 -
@Autowired:加在字段上,比如
@Autowired private UserDao userDao;,表示这个字段需要自动塞进一个匹配的实例 - 两个注解都要加上
@Retention(RetentionPolicy.RUNTIME),否则运行时读不到;@Target(ElementType.TYPE)和@Target(ElementType.FIELD)分别限定它们只能贴在类或字段上
用 ConcurrentHashMap 当 Bean 池
不用 synchronized 或手动加锁,直接用 ConcurrentHashMap<string object></string> 做单例容器:
- key 是 bean 名,推荐统一规则:类名首字母小写,如
UserService→userService;也支持@Component("userSvc")自定义名称 - value 是通过
clazz.getDeclaredConstructor().newInstance()反射创建出来的实例 - 选 ConcurrentHashMap 是因为它线程安全,初学者扫包+创建 Bean 时不怕并发冲突
两轮反射完成装配
整个初始化过程分清楚两步,逻辑不乱:
- 第一轮:扫描注册——遍历指定包路径下所有 .class 文件,找出带 @Component 的类,反射创建实例,存进 Map
-
第二轮:字段注入——再遍历 Map 里已有的每个 Bean 实例,检查它所有字段是否标了 @Autowired;如果字段类型(如 UserDao)在 Map 中有对应实例,就用
field.setAccessible(true)强制设值 - 字段类型是接口?那就找 Map 中实现了该接口的类实例(比如 Dao 接口有 UserDaoImpl 和 MockUserDao,只注入已注册的那个)
- 遇到循环依赖(A 依赖 B,B 又依赖 A),可先用
Set<class>> creating</class>记录正在构建中的类,重复出现就抛异常提示,避免栈溢出
验证效果很简单
写几个测试类,比如:
@Component public class OrderDao { }@Component public class OrderService { @Autowired private OrderDao dao; }
启动时调用 MyContainer.init("com.example.dao", "com.example.service"),然后 OrderService service = (OrderService) MyContainer.getBean("orderService"); service.process(); —— 能正常执行,说明注入成功。
不复杂但容易忽略细节:记得所有反射操作前加 field.setAccessible(true),private 字段才能写进去;容器本身建议用静态内部类或枚举实现单例,避免多次 init。











