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

相关推荐
04Koi.9 小时前
八股训练--Spring
java·后端·spring
jllllyuz11 小时前
Spring中的事务是如何实现的
数据库·sql·spring
wangmengxxw11 小时前
Spring-常用注解
java·数据库·spring·注解
一只鹿鹿鹿12 小时前
【网络安全】信息网络安全建设方案(WORD)
人工智能·安全·spring·web安全·低代码
丶小鱼丶17 小时前
Spring之【循环引用】
java·spring
我爱996!18 小时前
Spring IoC&DI
java·后端·spring
我命由我1234520 小时前
Spring Boot 项目问题:Web server failed to start. Port 5566 was already in use.
java·前端·jvm·spring boot·后端·spring·java-ee
_码农1213821 小时前
使用mybatis生成器生成实体类mapper和查询参数文件,简单spring mvc 项目。使用log4j输出日志到控制台和文件中。使用配置文件注册Bean
spring·mvc·mybatis
励志成为糕手1 天前
深入剖析Spring IOC容器——原理、源码与实践全解析
java·开发语言·spring