ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

spring DI (依赖注入)

spring DI (依赖注入) 实现原理Spring DI依赖注入的核心实现原理基于 ‌IoC 容器‌、‌BeanDefinition 元数据‌、‌反射机制‌ 以及 ‌BeanPostProcessor 后置处理器‌。其本质是在 Bean 实例化前后由容器自动解析依赖关系并将目标对象赋值给当前 Bean 的属性或构造参数。1. 核心流程‌元数据解析‌容器启动时扫描 XML、注解Component等或 Java Config将 Bean 的定义信息封装为BeanDefinition对象注册到BeanFactory中。‌实例化Instantiation‌调用createBeanInstance通过反射调用构造函数创建 Bean 的原始对象此时属性尚未填充。‌属性填充Populate Bean‌调用populateBean这是 DI 发生的关键阶段。‌构造器注入‌在实例化阶段通过determineConstructorsFromBeanPostProcessors解析带Autowired的构造器递归获取依赖 Bean 并传入构造函数。‌Setter/字段注入‌在实例化后通过AutowiredAnnotationBeanPostProcessor处理Autowired、Value等注解利用反射将依赖 Bean 注入到字段或 Setter 方法中。‌初始化与代理‌执行初始化方法若涉及 AOP则生成代理对象。2. 关键组件‌DefaultListableBeanFactory‌默认的 IoC 容器实现负责存储BeanDefinition和单例缓存执行getBean逻辑。‌AutowiredAnnotationBeanPostProcessor‌核心的后置处理器专门负责处理Autowired、Inject、Value注解执行依赖查找和注入。‌BeanWrapper‌封装 Bean 实例提供统一的属性访问和类型转换能力。3. 循环依赖解决三级缓存仅针对 ‌单例Singleton‌ 且 ‌非构造器注入‌ 的场景有效。‌一级缓存‌singletonObjects存放完全初始化好的 Bean。‌二级缓存‌earlySingletonObjects存放原始的 Bean 对象已实例化但未填充属性用于解决循环引用。‌三级缓存‌singletonFactories存放ObjectFactory用于在需要时提前暴露 Bean 引用特别是涉及 AOP 代理时。流程简述‌A 创建 - 放入三级缓存 - 填充属性发现依赖 B - 创建 B - 填充属性发现依赖 A - 从三级缓存获取 A 的工厂 - 生成 A 的早期引用若有代理则生成代理- 放入二级缓存 - B 完成初始化 - A 获取 B - A 完成初始化 - 移入一级缓存。代码示例手动模拟 DI 核心逻辑以下代码展示了 Spring DI 最简化的底层实现逻辑反射 缓存import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class SimpleDiContainer { // 模拟一级缓存存放完整 Bean private final Map singletonObjects new HashMap(); // 模拟 Bean 定义类名到 Class 的映射 private final Map beanDefinitions new HashMap(); public void registerBean(String name, Class clazz) { beanDefinitions.put(name, clazz); } public Object getBean(String name) throws Exception { // public T getBean(String name) throws Exception { // 1. 检查一级缓存 if (singletonObjects.containsKey(name)) { return singletonObjects.get(name); } Class clazz (Class) beanDefinitions.get(name); if (clazz null) { throw new IllegalArgumentException(Bean not found: name); } // 2. 实例化 (无参构造) Object bean clazz.getDeclaredConstructor().newInstance(); // 注意真实 Spring 中此处会先放入三级缓存以解决循环依赖简化版省略 // 3. 依赖注入 (DI 核心反射赋值) populateBean(bean); // 4. 放入一级缓存 singletonObjects.put(name, bean); return bean; } private void populateBean(Object bean) throws Exception { Field[] fields bean.getClass().getDeclaredFields(); for (Field field : fields) { // 模拟 Autowired 行为假设所有非基本类型字段都需要注入 if (!field.getType().isPrimitive() !field.getType().getName().startsWith(java.)) { field.setAccessible(true); String depName field.getName(); // 简单按字段名查找 // 递归获取依赖 Object depBean getBean(depName); field.set(bean, depBean); } } } // 测试入口 public static void main(String[] args) throws Exception { SimpleDiContainer container new SimpleDiContainer(); // 注册 Bean container.registerBean(serviceA, ServiceA.class); container.registerBean(serviceB, ServiceB.class); // 获取 Bean自动完成注入 ServiceA serviceA (ServiceA) container.getBean(serviceA); System.out.println(serviceA.getServiceB() ! null); // true } }class ServiceA {private ServiceB serviceB;public ServiceB getServiceB() {return serviceB;}public void setServiceB(ServiceB serviceB) {this.serviceB serviceB;}}// 测试类 class ServiceB { // 假设 B 不依赖 A避免简化版循环依赖死栈 }4. 常见坑点‌构造器循环依赖‌Spring 无法通过三级缓存解决构造器注入的循环依赖会直接抛出BeanCurrentlyInCreationException。解决方案是改为 Setter 注入或使用Lazy。‌多实现类歧义‌当接口有多个实现类时仅靠Autowired按类型匹配会报错。需配合Qualifier(beanName)指定名称或在其中一个实现类上加Primary。‌静态字段注入‌Autowired不能直接注入静态字段因为 DI 发生在实例化阶段而静态字段属于类级别。需在 Setter 方法上标注Autowired并在方法内赋值给静态变量支付场景1. 定义支付接口与多种实现 首先定义一个通用的支付接口然后提供两种不同的实现。注意这里使用了 Service(name) 来给 Bean 起别名方便后续区分。public interface PaymentService { void pay(double amount); } Service(wechat) // 指定 Bean 的名称为 wechat public class WechatPayServiceImpl implements PaymentService { Override public void pay(double amount) { System.out.println([微信支付] 正在处理支付金额: amount 元); } }Service(alipay) // 指定 Bean 的名称为 alipay public class AlipayServiceImpl implements PaymentService { Override public void pay(double amount) { System.out.println([支付宝] 正在处理支付金额: amount 元); } }2. 订单服务核心 DI 场景 在 OrderServiceImpl 中我们需要注入 PaymentService。由于容器中有两个 PaymentService 的实现支付宝和微信 如果只写 AutowiredSpring 会报错NoUniqueBeanDefinitionException。 这时我们需要配合 Qualifier 注解明确告诉 Spring“我要注入名为 alipay 的那个 Bean”。Service public class OrderServiceImpl implements OrderService { private final PaymentService paymentService; // ‌小贴士‌除了构造器注入你也可以在字段上使用 Autowired 和 Qualifier但构造器注入是 Spring 官方推荐的更稳健的方式保证对象不可变且非空。 // 【核心点】构造器注入 Qualifier // 明确指定注入名为 alipay 的 PaymentService 实现类 Autowired public OrderServiceImpl(Qualifier(alipay) PaymentService paymentService) { this.paymentService paymentService; } Override public void createOrder(double amount) { System.out.println(订单服务创建订单成功...); // 调用注入进来的支付服务 paymentService.pay(amount); } }public static void main(String[] args) { // 1. 启动容器 AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(AppConfig.class); AnnotationConfigApplicationContext context1 new AnnotationConfigApplicationContext(AppConfig.class); // 2. 获取 OrderService OrderService orderService context.getBean(OrderService.class); Order1Service orderService1 context1.getBean(Order1Service.class); // 3. 执行下单 System.out.println(--- 开始测试 ---); orderService.createOrder(99.9); orderService1.createOrder(88.8); context.close(); }上一篇 SpringIOC控制反转
返回列表