高频面试题:Spring 依赖注入怎么实现的?3 个注解 + 2 个处理器,原理其实很简单

9 阅读7分钟

在 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 对象,每个具体的注入点(如一个字段)会被封装为对应的元素对象(如 AutowiredFieldElementResourceElement),便于统一处理。
  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 的注册,无需手动配置,具体流程如下:

graph TD
A("SpringApplication.run() 启动")-->|初始化应用上下文| B("createApplicationContext()")
B-->|"默认创建注解驱动的 Web 上下文"| C("AnnotationConfigServletWebServerApplicationContext.class")
C -->|"构造方法中初始化 Bean 定义读取器"| D("AnnotatedBeanDefinitionReader.class")
D -->|构造方法调用注册工具类| E("AnnotationConfigUtils.registerAnnotationConfigProcessors()")
E-->|"向容器中注册处理器 Bean"|F(AutowiredAnnotationBeanPostProcessor)
F-->|"容器启动完成后,处理器就绪"|G("等待拦截 Bean 创建流程")

关键说明:

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

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

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

graph TD
A("Spring 容器创建 Bean 流程")-->|"1. Bean 实例化完成(new 出对象)"| B("调用 AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition()")
B-->|"解析 Bean 的类结构,查找 @Autowired/@Value 注解"| C("findAutowiringMetadata(beanName, beanClass, null)")
C -->|"封装注入点信息(字段/方法 + 注解属性)"| D("InjectionMetadata.checkConfigMembers()")
D-->|"将注入元数据缓存到 BeanDefinition 中,避免重复解析"| E("元数据缓存完成")
A -->|"2. 进入属性填充阶段"| F("调用 AutowiredAnnotationBeanPostProcessor.postProcessProperties()")
F-->|从缓存中获取注入元数据| G("findAutowiringMetadata(beanName, beanClass, pvs)")
G-->|执行注入逻辑| H("InjectionMetadata.inject(bean, beanName, pvs)")
H-->|遍历所有注入点元素| I("AutowiredFieldElement.inject(bean, beanName, pvs)")
I-->|"委托 BeanFactory 解析依赖"| J("DefaultListableBeanFactory.resolveDependency()")
J-->|进一步处理依赖查找| K("DefaultListableBeanFactory.doResolveDependency()")
K-->|"获取注解指定的建议值(如 @Value 的表达式)"| L("getAutowireCandidateResolver().getSuggestedValue(descriptor)")
L-->|判断建议值类型| M{"是否为字符串(配置占位符/SpEL)"}
M-->|"是:处理 @Value 逻辑"| N("resolveEmbeddedValue() 解析 ${} 和 #{}")
N-->|"转换为目标类型(如 Integer)"| O("注入到字段/方法")
M-->|"否:处理 @Autowired 逻辑"| P("findAutowireCandidates() 按类型查找 Bean")
P-->|"存在多个时按名称匹配(配合 @Qualifier)"| Q("筛选出唯一匹配的 Bean")
Q-->|注入到字段/方法| O

关键细节解析

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

四、@Resource 的实现原理

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

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

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

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

graph TD
A("SpringApplication.run() 启动")-->|初始化应用上下文| B("createApplicationContext()")
B-->|创建注解驱动上下文| C("AnnotationConfigServletWebServerApplicationContext.class")
C -->|"构造方法初始化 Bean 定义读取器"| D("AnnotatedBeanDefinitionReader.class")
D -->|调用注册工具类| E("AnnotationConfigUtils.registerAnnotationConfigProcessors()")
E-->|"向容器中注册处理器 Bean"|F(CommonAnnotationBeanPostProcessor)
F-->|"容器启动完成后,处理器就绪"|G("等待拦截 Bean 创建流程")

关键说明:

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

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

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

graph TD
A("Spring 容器创建 Bean 流程")-->|"1. Bean 实例化完成"| B("调用 CommonAnnotationBeanPostProcessor.postProcessMergedBeanDefinition()")
B-->|"解析 Bean 的类结构,查找 @Resource 注解"| C("findAutowiringMetadata(beanName, beanClass, null)")
C -->|封装注入点信息| D("InjectionMetadata.checkConfigMembers()")
D-->|"缓存元数据到 BeanDefinition"| E("元数据缓存完成")
A -->|"2. 进入属性填充阶段"| F("调用 CommonAnnotationBeanPostProcessor.postProcessProperties()")
F-->|获取缓存的注入元数据| G("findAutowiringMetadata(beanName, beanClass, pvs)")
G-->|执行注入| H("InjectionMetadata.inject(bean, beanName, pvs)")
H-->|遍历注入点元素| I("ResourceElement.inject(bean, beanName, pvs)")
I-->|获取要注入的资源| J("ResourceElement.getResourceToInject(bean, beanName)")
J-->|委托处理器查找资源| K("CommonAnnotationBeanPostProcessor.getResource(descriptor, beanName)")
K-->|自动装配资源| L("CommonAnnotationBeanPostProcessor.autowireResource(beanFactory, descriptor, beanName)")
L-->|"核心:按名称查找 Bean"| M("DefaultListableBeanFactory.resolveBeanByName(descriptor.getName())")
M-->|判断是否找到匹配名称的 Bean| N{Bean 是否存在}
N-->|是| O("返回找到的 Bean 实例")
N-->|否| P("按类型查找 Bean(名称匹配失败后的降级策略)")
P-->|找到唯一匹配的 Bean| O
P-->|未找到或多个匹配| Q("抛出 NoSuchBeanDefinitionException 或 NoUniqueBeanDefinitionException")
O-->|通过反射注入到字段/方法| R("注入完成")

关键细节解析

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

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

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

维度@Autowired + @Value@Resource  
核心处理器    AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor              
依赖查找规则  @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)提供理论支撑。