Spring 依赖注入方式及原理

pring 的依赖注入(Dependency Injection, DI)是其核心特性之一,通过将对象依赖关系的创建和管理交给容器,实现松耦合和高扩展性。以下是依赖注入的 方式 、底层原理 及 核心实现细节 的全面解析。


一、依赖注入的三种方式

1. 构造器注入(Constructor Injection)

  • 定义:通过构造函数参数传递依赖。

  • 使用场景:

    • 强制依赖(必须的依赖项)。
    • 不可变字段(final 修饰的字段)。
    • 推荐用于解决循环依赖问题。
  • 代码示例:

    kotlin 复制代码
    @Component
    public class OrderService {
        private final UserService userService;
        
        @Autowired // Spring 4.3+ 可省略
        public OrderService(UserService userService) {
            this.userService = userService;
        }
    }
  • 优点:

    • 保证依赖不可变。
    • 避免 NullPointerException(依赖必须在对象创建时提供)。
    • 易于单元测试(通过构造器直接传入 Mock 对象)。

2. Setter 注入(Setter Injection)

  • 定义:通过 Setter 方法传递依赖。

  • 使用场景:

    • 可选依赖(依赖可为 null)。
    • 需要动态重新配置依赖的场景。
  • 代码示例:

    typescript 复制代码
    @Component
    public class PaymentService {
        private PaymentGateway paymentGateway;
    ​
        @Autowired
        public void setPaymentGateway(PaymentGateway paymentGateway) {
            this.paymentGateway = paymentGateway;
        }
    }
  • 优点:

    • 灵活性高,允许依赖变更。
    • 符合 JavaBean 规范。

3. 字段注入(Field Injection)

  • 定义:直接通过字段注入依赖(借助反射)。

  • 使用场景:

    • 快速开发(代码简洁)。
    • 非强制依赖(但需谨慎使用)。
  • 代码示例:

    kotlin 复制代码
    @Component
    public class NotificationService {
        @Autowired
        private EmailService emailService;
    }
  • 缺点:

    • 破坏封装性(字段通常应为 private)。
    • 难以测试(需通过反射或 Spring 容器注入依赖)。
    • 隐藏依赖关系(类签名中不直接体现依赖)。

4.对比

特性 构造器注入 Setter注入 字段注入
依赖可见性 显式(构造函数参数) 显式(Setter方法) 隐式(字段声明)
不可变性 支持(final字段) 不支持 不支持
循环依赖支持 不支持 支持(单例模式) 支持(单例模式)
测试友好性 高(无需容器即可注入依赖) 中(需调用Setter) 低(需反射或容器)
代码侵入性 低(天然支持) 中(需显式Setter) 无
推荐场景 必需依赖、线程安全需求 可选依赖、动态配置 快速原型开发,不推荐生产环境

二、依赖注入的底层原理

Spring 的依赖注入实现依赖于 Bean 生命周期管理 和 后置处理器(BeanPostProcessor) ,核心流程如下:

1. Bean 的生命周期与注入时机

  1. Bean 定义加载 : 通过 BeanDefinitionReader 解析配置(XML、注解、Java Config),生成 BeanDefinition。
  2. Bean 实例化 : 调用构造函数或工厂方法创建对象(AbstractAutowireCapableBeanFactory.createBeanInstance())。
  3. 属性填充(依赖注入) : 通过 populateBean() 方法注入依赖(字段、Setter 方法或构造器参数)。
  4. 初始化 : 调用 @PostConstruct 方法、InitializingBean.afterPropertiesSet() 或自定义初始化方法。
  5. 销毁 : 容器关闭时调用 @PreDestroy 方法或 DisposableBean.destroy()。

2. 注解驱动的依赖注入实现

Spring 通过 BeanPostProcessor 实现注解解析和依赖注入:

  • 关键类:

    • AutowiredAnnotationBeanPostProcessor:处理 @Autowired 和 @Value。
    • CommonAnnotationBeanPostProcessor:处理 @Resource、@PostConstruct 等 JSR-250 注解。
  • 注入流程:

    1. 后置处理器注册 : 在容器初始化时,将 BeanPostProcessor 注册到 BeanFactory。
    2. 元数据解析 : 在 postProcessMergedBeanDefinition() 阶段,扫描 Bean 的字段、方法,提取注入点(InjectionMetadata)。
    3. 依赖注入 : 在 postProcessProperties() 阶段,通过反射注入具体值。

3. 依赖解析机制

Spring 通过 DefaultListableBeanFactory 解析依赖:

  • 按类型注入 : 查找所有匹配类型的 Bean,若存在多个候选 Bean,需结合 @Primary、@Qualifier 解决歧义。

  • 按名称注入 : 若使用 @Resource(name = "beanName"),直接按名称查找 Bean。

  • 示例代码(依赖解析) :

    typescript 复制代码
    public class DefaultListableBeanFactory {
        public Object resolveDependency(DependencyDescriptor descriptor, String beanName) {
            // 1. 按类型查找候选 Bean
            Map<String, Object> candidates = findAutowireCandidates(beanName, descriptor.getDependencyType());
            // 2. 根据 @Primary、@Priority、名称等确定最终 Bean
            Object result = determineAutowireCandidate(candidates, descriptor);
            return result;
        }
    }

三、循环依赖的处理机制

Spring 通过 三级缓存 解决单例 Bean 的循环依赖问题:

1. 三级缓存结构

缓存名称 存储内容 作用
singletonObjects 完全初始化好的单例 Bean 直接获取最终可用的 Bean。
earlySingletonObjects 提前暴露的 Bean(未完成属性填充和初始化) 解决循环依赖中的早期引用。
singletonFactories 生成早期 Bean 的 ObjectFactory 延迟创建 Bean 的早期引用。

2. 循环依赖解决流程

以 Bean A → Bean B → Bean A 为例:

  1. 创建 Bean A:

    • 实例化 A(调用构造函数),生成一个原始对象。
    • 将 A 的 ObjectFactory 放入 singletonFactories。
  2. 填充 Bean A 的属性:

    • 发现 A 依赖 B,尝试获取 Bean B。
  3. 创建 Bean B:

    • 实例化 B,将 B 的 ObjectFactory 放入 singletonFactories。
    • 填充 B 的属性时,发现 B 依赖 A。
  4. 获取 Bean A 的早期引用:

    • 从 singletonFactories 获取 A 的 ObjectFactory,生成 A 的代理或原始对象,放入 earlySingletonObjects。
    • 将 A 的早期引用注入到 B 中。
  5. 完成 Bean B 的初始化:

    • B 完成属性填充和初始化,放入 singletonObjects。
  6. 完成 Bean A 的初始化:

    • 将 B 的实例注入 A,A 完成初始化,放入 singletonObjects。

3. 无法解决的循环依赖场景

  • 构造器循环依赖 : 若两个 Bean 均通过构造器注入对方,Spring 无法解决 ,抛出 BeanCurrentlyInCreationException。
  • 原型(Prototype)Bean 的循环依赖: Spring 不缓存原型 Bean,因此无法处理其循环依赖。

四、高级依赖注入技术

1. 延迟注入(Lazy Injection)

  • 使用 @Lazy: 延迟初始化依赖,直到首次访问。

    less 复制代码
    @Component
    public class OrderService {
        @Lazy
        @Autowired
        private InventoryService inventoryService;
    }
  • 使用 ObjectProvider: 按需获取 Bean,避免过早初始化。

    java 复制代码
    @Component
    public class OrderService {
        private final ObjectProvider<InventoryService> inventoryServiceProvider;
    ​
        public OrderService(ObjectProvider<InventoryService> inventoryServiceProvider) {
            this.inventoryServiceProvider = inventoryServiceProvider;
        }
    ​
        public void processOrder() {
            InventoryService inventoryService = inventoryServiceProvider.getIfAvailable();
            // ...
        }
    }

2. 条件化注入

  • @Conditional 注解: 根据条件动态决定是否注册 Bean。

    less 复制代码
    @Configuration
    public class AppConfig {
        @Bean
        @Conditional(OnProductionEnvCondition.class)
        public DataSource productionDataSource() {
            return new ProductionDataSource();
        }
    }

3. 自定义依赖注入逻辑

通过实现 BeanPostProcessor 或 BeanFactoryPostProcessor 干预依赖注入过程:

  • 示例:自定义注解处理器:

    typescript 复制代码
    public class CustomAnnotationProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) {
            // 解析自定义注解并注入依赖
            if (bean instanceof MyService) {
                ((MyService) bean).setCustomDependency(...);
            }
            return bean;
        }
    }

五、总结

Spring 的依赖注入通过以下核心机制实现:

  1. 多种注入方式:构造器注入(推荐)、Setter 注入、字段注入。
  2. 注解驱动 :@Autowired、@Resource 等结合后置处理器实现自动化注入。
  3. 依赖解析 :按类型或名称匹配 Bean,支持 @Primary 和 @Qualifier 解决冲突。
  4. 循环依赖处理:三级缓存机制解决单例 Bean 的循环依赖。
  5. 扩展性 :通过 BeanPostProcessor 和自定义条件实现灵活控制。

理解这些原理不仅能帮助开发者避免常见的依赖问题(如循环依赖),还能优化 Bean 设计(如选择最佳注入方式),并提升应用的可维护性和可测试性。

相关推荐
ZealSinger2 小时前
Boot4升级PostConstruct不跑了
java·spring boot·spring·升级迁移
嵌入式学习菌3 小时前
ESP32 ModbusTCP 分片缓存
java·后端·spring
专业程序开发源5 小时前
springboot高校学生社团管理系统74810-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计
萧瑟余晖6 小时前
Spring Test与综合实战详解
spring
萧瑟余晖6 小时前
Spring 任务调度与异步执行详解
spring
vx_Biye_Design7 小时前
springboot角色扮演服务平台65161-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·python·spring·课程设计
vx_Biye_Design7 小时前
springboot咖啡厅顾客点单管理系统12080-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·课程设计·express
Q26433650237 小时前
【有源码】基于springboot的知识产权管理系统-知识产权咨询问答管理系统-知识产权价值评估管理系统
java·spring boot·mysql·spring·毕业设计·javaweb·课程设计
Joy T1 天前
聊天系统中五大ID的演进与实战解析
java·前端·人工智能·spring·agent入门
萧瑟余晖1 天前
Spring 事务管理与数据访问详解
spring