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. 自定义依赖注入逻辑

通过实现 BeanPostProcessorBeanFactoryPostProcessor 干预依赖注入过程:

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

    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 设计(如选择最佳注入方式),并提升应用的可维护性和可测试性。

相关推荐
在京奋斗者几秒前
spring boot自动装配原理
java·spring boot·spring
半部论语7 小时前
SpringMVC 中的DispatcherServlet生命周期是否受Spring IOC 容器管理
java·后端·spring
ktkiko118 小时前
用户模块——整合 Spring 缓存(Cacheable)
java·spring·缓存
shuair10 小时前
01 - spring security自定义登录页面
java·后端·spring
逆风局?13 小时前
Spring-AOP-面相切面编程
java·后端·spring
〆、风神15 小时前
策略模式与元数据映射模式融合 JSR 380 验证规范实现枚举范围校验
windows·spring·策略模式
多多*15 小时前
JVM Java类加载 isInstance instanceof 的区别
开发语言·python·spring·ai作画·eclipse·maven
卿着飞翔16 小时前
ssm框架之Spring
android·java·spring
天上掉下来个程小白16 小时前
Redis-13.在Java中操作Redis-Spring Data Redis使用方式-操作哈希类型的数据
java·spring boot·redis·spring·苍穹外卖
八股文领域大手子16 小时前
Spring多数据源环境下的事务与数据源切换
java·数据库·后端·mysql·spring·wpf