尧图网站设计 尧图网站设计YAOTU DESIGN
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控制反转
返回列表