Spring 依赖注入方式及原理

一、依赖注入的三种方式

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 方法或构造器参数)。
    初始化: 调用 @PostConstruct 方法、InitializingBean.afterPropertiesSet() 或自定义初始化方法。

  4. 销毁: 容器关闭时调用 @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
  1. 填充 Bean A 的属性:
  • 发现 A 依赖 B,尝试获取 Bean B。
  1. 创建 Bean B:
  • 实例化 B,将 B 的 ObjectFactory 放入 singletonFactories
  • 填充 B 的属性时,发现 B 依赖 A。
  1. 获取 Bean A 的早期引用:
  • singletonFactories 获取 A 的 ObjectFactory,生成 A 的代理或原始对象,放入 earlySingletonObjects
  • 将 A 的早期引用注入到 B 中。
  1. 完成 Bean B 的初始化:
  • B 完成属性填充和初始化,放入 singletonObjects
  1. 完成 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;
    }
}

五、Autowired 与 Resource区别

@Autowired注解是Spring提供,只按照byType进行注入。@Autowired如果想要按照byName方式需要加@QualifierQualifier意思是合格者,一般跟Autowired配合使用,需要指定一个bean的名称,通过bean名称就能找到需要装配的bean

//定义一个服务,添加@Service交给Spring Boot管理
@Service
public class Service1 {
}
//在另一个Service中需要依赖,使用方法如下。
@Service
public class Service2 {
    @Autowired
    private Service1 service1;
}

Autowired 注解的加载流程图如下:

flowchart LR
  A["按类型查找bean"] --> B{"是否找到?"}
  B --> |否|C["抛异常"]
  B --> |是|D{"只找到一个?"}
  D --> |只一个|F["自动装配"]
  D --> |不只一个|G{"是否配置了Qualifier?"}
  G --> |是|H["按照Qualifier参数查找bean"]
  H --> I{"是否找到?"}
  I --> |否|C
  I --> |是|F
  G --> |否|K["按照名称查找bean"]
  K --> |否|C
  K --> |是|F

@Resource注解是Java标准库提供。默认采用byName方式进行注入,如果找不到则使用byType。可通过注解参数进行改变。比起Autowired好处在于跟Spring的耦合度没有那么高。

flowchart LR
  A["按名称查找bean"] --> B{"是否找到?"}
  B --> |否|C["按类型查找bean"]
  B --> |是|D["自动装配"]
  C --> E{"是否找到?"}
  E --> |只找到一个|D
  E --> |没有找到或找到多个|F["抛异常"]

具体例子如下:

//定义一个服务,添加@Service交给Spring Boot管理
@Service
public class Service1 {
}
//在另一个Service中需要依赖,使用方法如下。
@Service
public class Service2 {
    @Resource
    private Service1 service1;
}

构造参数注入方式! 比起以上两种方式,Spring更推荐以下方式进行注入,因为以上两种通过反射将对象直接注入私有属性,会破环封装性。

@Service
public class Service2 {
    private final Service1 service1;
	//Spring Boot在进行解析时,会自动将Service1的单例对象进行注入
    public Service2(Service1 service1) {
        this.service1 = service1;
    }
}

六、总结

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

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

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

关于我
loading