面试必问:Spring 依赖注入怎么实现?3 注解 + 2 处理器,原理很简单

在 Spring 框架中,依赖注入(Dependency Injection, DI)是核心特性之一,它通过注解化配置简化了组件间的依赖管理,避免了手动创建对象和组装依赖的繁琐操作。其中,@Autowired、@Value、@Resource 是日常开发中最常用的三个依赖注入注解,本文将从基本使用、核心实现步骤、底层原理三个维度,深入剖析这三个注解的工作机制。

一、基本使用:注解功能与场景区分

在了解底层实现前,先明确三个注解的核心用途和使用差异,这是理解实则现逻辑的基础:

注解

核心功能

注入依据

适用场景

关键特性

@Autowired

自动装配 Spring 容器中的 Bean

类型(Type)优先,支持按名称(需配合 @Qualifier)

注入自定义组件(如 Service、Dao)

支持构造器、字段、setter 注入;默认必须存在(required=true)

@Value

注入配置属性、字面量或 SpEL 表达式结果

配置键名 / 表达式

注入配置参数(如 application.yml 中的值)

支持 ${} 配置占位符、#{} SpEL 表达式;可指定默认值

@Resource

自动装配 Bean(JDK 原生注解,非 Spring 定义)

名称(Name)优先,其次类型

注入自定义组件或 JDK 原生组件

支持字段、setter 注入;可通过 name 属性指定 Bean 名称

典型使用示例

  1. @Autowired 注入
@Service
public class UserService {
    // 字段注入:按类型匹配 UserDao,若存在多个同类型 Bean 需配合 @Qualifier 指定名称
    @Autowired
    @Qualifier("userDaoImpl")
    private UserDao userDao;

    // 构造器注入(Spring 4.3+ 无需显式 @Autowired)
    private OrderService orderService;
    @Autowired
    public UserService(OrderService orderService) {
        this.orderService = orderService;
    }
}
  1. @Value 注入
@Component
public class AppConfig {
    // 注入配置文件中的属性,默认值为 "dev"
    @Value("${spring.profiles.active:dev}")
    private String activeProfile;

    // 注入 SpEL 表达式结果(计算 10+20)
    @Value("#{10 + 20}")
    private Integer calculateResult;

    // 注入字面量
    @Value("Hello Spring")
    private String greeting;
}
  1. @Resource 注入
@Component
public class PaymentService {
    // 按名称 "alipayClient" 注入,若不存在则按类型匹配
    @Resource(name = "alipayClient")
    private PaymentClient paymentClient;
}

二、核心实现步骤:依赖注入的通用逻辑

无论哪个注入注解,Spring 实现依赖注入的核心思路一致,本质是拦截 Bean 创建过程,识别注解标记的依赖,从容器中获取对应资源并注入,具体分为两步:

1. 拦截 Bean 的创建:生命周期扩展点介入

Spring 容器创建 Bean 时,会遵循固定的生命周期流程(如实例化、属性填充、初始化、销毁)。依赖注入的关键是在属性填充阶段(即 Bean 实例化后、初始化前)介入,而介入的核心是
InstantiationAwareBeanPostProcessor 接口 —— 这是 Spring 提供的 Bean 生命周期扩展接口,专门用于在 Bean 实例化后、属性设置前执行自定义逻辑,是依赖注入的 “入口”。


InstantiationAwareBeanPostProcessor 继承自 BeanPostProcessor,但新增了与 “实例化后、属性填充前” 相关的方法,其中 postProcessProperties() 是依赖注入的核心方法,负责处理字段或方法上的注入注解。

2. 识别注解与注入值:元数据解析 + 资源查找

Spring 会通过以下子步骤完成注入:

  1. 扫描注解:对目标 Bean 的类结构进行解析,查找标记了 @Autowired、@Value、@Resource 的字段或方法,收集这些需要注入的 “元数据”(如字段类型、注解属性、目标名称等)。
  2. 封装元数据:将收集到的注入信息封装为 InjectionMetadata 对象,每个具体的注入点(如一个字段)会被封装为对应的元素对象(如 AutowiredFieldElement、ResourceElement),便于统一处理。
  3. 查找资源:根据注入元数据的规则,从 Spring 容器(BeanFactory)中查找对应的资源 —— 可能是 Bean 实例(@Autowired、@Resource)、配置属性(@Value)或 SpEL 表达式结果。
  4. 执行注入:通过反射机制,将找到的资源赋值给目标 Bean 的字段或通过方法参数传入,完成依赖注入。

三、@Autowired 与 @Value 的实现原理

@Autowired 和 @Value 由同一个核心处理器
AutowiredAnnotationBeanPostProcessor 负责处理,二者共享 “拦截 Bean 创建” 的流程,但在 “资源查找” 阶段有不同逻辑。

1. 拦截 Bean 创建:AutowiredAnnotationBeanPostProcessor 的注册

@Autowired 和 @Value 能生效的前提是,Spring 容器中存在
AutowiredAnnotationBeanPostProcessor 实例 —— 它是
InstantiationAwareBeanPostProcessor 接口的实现类,专门处理 @Autowired、@Value 以及 JSR-330 规范的 @Inject 注解。

注册流程

Spring Boot 启动时,会自动完成
AutowiredAnnotationBeanPostProcessor 的注册,无需手动配置,具体流程如下:

面试必问:Spring 依赖注入怎么实现?3 注解 + 2 处理器,原理很简单

关键说明:

  • AnnotationConfigServletWebServerApplicationContext 是 Spring Boot Web 应用的默认上下文,专门支持注解驱动的配置。
  • AnnotatedBeanDefinitionReader 负责读取注解式的 Bean 定义(如 @Component、@Configuration),其构造时会调用 AnnotationConfigUtils 注册一系列 “注解处理处理器”,AutowiredAnnotationBeanPostProcessor 就是其中之一。

2. 注解识别与注入:核心方法与流程


AutowiredAnnotationBeanPostProcessor 通过两个核心方法完成注入:
postProcessMergedBeanDefinition()(解析注入元数据)和 postProcessProperties()(执行注入),具体流程如下:

面试必问:Spring 依赖注入怎么实现?3 注解 + 2 处理器,原理很简单

关键细节解析

  1. 元数据缓存:postProcessMergedBeanDefinition() 会在 Bean 实例化后立即解析注入注解,并将元数据缓存到 BeanDefinition 中。这样做是为了避免每次 Bean 初始化都重复解析类结构,提升性能。
  2. InjectionMetadata 与 AutowiredFieldElement
  • InjectionMetadata 是注入元数据的容器,包含一个 Bean 所有需要注入的 “注入点元素”(字段或方法)。
  • AutowiredFieldElement 是字段注入的具体实现类,封装了字段的反射信息(Field 对象)和注入规则。
  1. 3. @Value 的特殊处理
  • resolveEmbeddedValue() 是 Spring 提供的字符串解析方法,专门处理 ${} 配置占位符(如读取 application.yml 中的属性)和 #{} SpEL 表达式(如计算表达式、调用 Bean 方法)。
  • 解析后的数据会通过类型转换器(ConversionService)转换为目标字段的类型(如将字符串 “8080” 转换为 Integer)。
  1. 4. @Autowired 的自动装配规则
  • 优先按 “类型” 查找容器中的 Bean,若存在唯一匹配则直接注入。
  • 若存在多个同类型 Bean,会按 “字段名称” 或 @Qualifier 注解指定的名称筛选。
  • 若未找到匹配的 Bean,且 @Autowired(required=true)(默认),则抛出 NoSuchBeanDefinitionException;若 required=false,则注入 null。

四、@Resource 的实现原理

@Resource 是 JDK 原生注解(位于 javax.annotation 包),并非 Spring 定义,但 Spring 对其提供了完美支持。实则现逻辑与 @Autowired 类似,但核心处理器和注入规则不同。

1. 拦截 Bean 创建:CommonAnnotationBeanPostProcessor 的注册

@Resource 由
CommonAnnotationBeanPostProcessor 负责处理,该处理器同样实现了
InstantiationAwareBeanPostProcessor 接口,同时还处理 @PostConstruct(初始化回调)、@PreDestroy(销毁回调)等 JDK 原生注解。

注册流程(与 @Autowired 共享部分逻辑)

面试必问:Spring 依赖注入怎么实现?3 注解 + 2 处理器,原理很简单

关键说明:

  • CommonAnnotationBeanPostProcessor 与 AutowiredAnnotationBeanPostProcessor 由同一个 AnnotationConfigUtils 工具类注册,是 Spring 注解驱动的核心处理器组合。
  • 若项目中未引入 JDK 的 javax.annotation 相关依赖(如 JDK 9+ 需手动引入 javax.annotation-api),@Resource 注解将无法被识别。

2. 注解识别与注入:核心方法与流程


CommonAnnotationBeanPostProcessor 的注入流程与
AutowiredAnnotationBeanPostProcessor 类似,但核心差异在于 “注入点封装” 和 “资源查找规则”:

面试必问:Spring 依赖注入怎么实现?3 注解 + 2 处理器,原理很简单

关键细节解析

  1. ResourceElement 封装:@Resource 对应的注入点元素是 ResourceElement(继承自 InjectionElement),它封装了 @Resource 注解的属性(如 name、type)和字段 / 方法的反射信息。
  2. 注入规则:名称优先
  • @Resource 的核心注入规则是 “按名称匹配”:第一根据 @Resource(name=”xxx”) 指定的名称查找 Bean,若未指定 name,则默认使用 “字段名称” 或 “方法参数名称” 作为查找键。
  • 若按名称未找到 Bean,会降级为 “按类型匹配”,此时逻辑与 @Autowired 类似,但不支持 @Qualifier 注解。
  1. 3. 与 @Autowired 的核心差异
  • 注解来源:@Resource 是 JDK 原生注解,@Autowired 是 Spring 注解。
  • 匹配优先级:@Resource 名称优先,@Autowired 类型优先。
  • 支持的注入方式:@Resource 不支持构造器注入,@Autowired 支持。
  • 依赖查找范围:@Resource 可通过 lookup 属性指定查找范围,@Autowired 依赖 Spring 容器的 Bean 查找机制。

五、总结:三大注解实现逻辑对比

通过以上分析,可将三大注解的实现逻辑归纳为 “同一框架,不同处理器,不同规则”:

维度

@Autowired + @Value

@Resource

核心处理器

AutowiredAnnotationBeanPostProcessor

CommonAnnotationBeanPostProcessor

依赖查找规则

@Autowired:类型优先,支持 @Qualifier;@Value:配置 / SpEL

名称优先,降级类型匹配,不支持 @Qualifier

注入点封装元素

AutowiredFieldElement(字段)、AutowiredMethodElement(方法)

ResourceElement(字段 / 方法)

核心依赖查找方法

DefaultListableBeanFactory.resolveDependency()

DefaultListableBeanFactory.resolveBeanByName()

特殊功能

@Value 支持配置占位符、SpEL

支持 JDK 原生规范,兼容非 Spring 环境

核心底层共性

  1. 都依赖 InstantiationAwareBeanPostProcessor 接口拦截 Bean 生命周期,在属性填充阶段执行注入。
  2. 都通过 “元数据解析 + 缓存” 提升性能,避免重复解析类结构。
  3. 都通过反射机制完成最终的属性赋值,依赖 Spring 的 BeanFactory 查找资源。

理解这些底层原理,不仅能协助我们更灵活地使用三大注解(如解决注入冲突、自定义注入规则),还能深入理解 Spring 容器的工作机制,为排查依赖注入相关问题(如
NoSuchBeanDefinitionException、
NoUniqueBeanDefinitionException)提供理论支撑。

© 版权声明

相关文章

1 条评论

  • 杜光
    杜光 投稿者

    收藏了,感谢分享

    回复