Spring 依赖注入方式及原理

787 阅读6分钟

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


一、依赖注入的三种方式

1. 构造器注入(Constructor Injection)

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

  • 使用场景

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

    @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)。
    • 需要动态重新配置依赖的场景。
  • 代码示例

    @Component
    public class PaymentService {
        private PaymentGateway paymentGateway;
    ​
        @Autowired
        public void setPaymentGateway(PaymentGateway paymentGateway) {
            this.paymentGateway = paymentGateway;
        }
    }
    
  • 优点

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

3. 字段注入(Field Injection)

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

  • 使用场景

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

    @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。

  • 示例代码(依赖解析)

    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: 延迟初始化依赖,直到首次访问。

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

    @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。

    @Configuration
    public class AppConfig {
        @Bean
        @Conditional(OnProductionEnvCondition.class)
        public DataSource productionDataSource() {
            return new ProductionDataSource();
        }
    }
    

3. 自定义依赖注入逻辑

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

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

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