Skip to content

@Autowired@Resource 依赖注入全过程

@Autowired 按类型,@Resource 按名称”只是第一层结论。真正执行注入的是 Spring BeanPostProcessor:前者主要由 AutowiredAnnotationBeanPostProcessor 解析,后者由 CommonAnnotationBeanPostProcessor 解析。它们发现注入点、构造 DependencyDescriptor、查找候选、处理名称和限定符,最后通过构造器调用或反射/方法调用写入依赖。

学习目标

  • 说清两种注解的来源、处理器、支持位置和执行时机;
  • 理解构造器注入为何发生在实例化阶段,字段/Setter为何发生在属性填充阶段;
  • 掌握 @Autowired 按类型、Qualifier、Primary、Priority、名称的候选选择;
  • 掌握 @Resource 显式名称、默认属性名和类型回退;
  • 理解 required=false、Optional、Nullable、ObjectProvider 的不同语义;
  • 掌握 List、Set、Map、数组和泛型限定注入;
  • 理解代理对象、父子容器、Profile/Condition 与注入类型的关系;
  • 能排查 NoSuch、NoUnique、UnsatisfiedDependency、BeanNotOfRequiredType 和注入为 null;
  • 理解 JDK 8 javax.annotation 与 Spring 6 jakarta.annotation 迁移边界。

一、注解来源和处理器

维度@Autowired@Resource
来源Spring FrameworkJSR-250 / Jakarta Annotations
JDK8常见包org.springframework.beans.factory.annotation.Autowiredjavax.annotation.Resource
Spring6常见包同左jakarta.annotation.Resource
主要处理器AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor
默认思路类型候选解析名称优先,特定条件下类型回退
构造器支持注解本身通常不用于构造器参数
字段/Setter方法支持支持

@Resource 是标准注解,但在普通 Spring 应用中将其解析为 Spring Bean 的仍是 Spring 处理器。它不意味着每次都直接访问远程 JNDI;CommonAnnotationBeanPostProcessor 可根据配置和名称从 BeanFactory 或 JNDI 解析。

二、注入发生在 Bean 生命周期哪里

mermaid
flowchart TD
    A["解析BeanDefinition"] --> B["选择构造器"]
    B --> C["解析构造器参数依赖"]
    C --> D["调用构造器完成实例化"]
    D --> E["BPP发现字段和Setter注入点"]
    E --> F["解析依赖候选"]
    F --> G["反射写字段或调用方法"]
    G --> H["Aware与初始化回调"]
    H --> I["后置处理和AOP代理"]

2.1 构造器注入

对象产生前就必须解析依赖,依赖作为构造参数传入。创建后对象立即完整,字段可 final。构造器循环依赖因为双方都没有实例,通常无法解决。

2.2 字段与 Setter 注入

对象先通过构造器实例化,再由后置处理器扫描缓存的注入元数据,通过反射设置字段或调用 Setter/任意注入方法。对象在属性填充完成前处于不完整状态,这也是部分字段循环依赖能提前暴露引用的前提。

2.3 元数据缓存

处理器不会每次调用方法都重新扫描注解,而会按 Bean 类/名称缓存 InjectionMetadata;BeanDefinition 合并、类重载或上下文刷新时缓存可能重建。注入只发生在 Bean 创建阶段,不是每次业务方法调用都查容器。

三、@Autowired 完整候选解析

假设注入:

java
@Autowired
private PaymentClient paymentClient;

主流程:

mermaid
flowchart TD
    A["构造DependencyDescriptor"] --> B["按ResolvableType找候选"]
    B --> C["过滤autowireCandidate"]
    C --> D["应用Qualifier和泛型限定"]
    D --> E{"候选数量"}
    E -- "0" --> F["处理required或可选语义"]
    E -- "1" --> G["选择唯一候选"]
    E -- "多个" --> H["检查Primary"]
    H --> I["检查Priority"]
    I --> J["检查依赖名称与Bean名"]
    J --> K{"得到唯一候选吗"}
    K -- "是" --> G
    K -- "否" --> L["抛NoUniqueBeanDefinitionException"]

准确顺序和细节随 Spring 版本演进,但核心不是“类型相同就随机选一个”。

3.1 @Primary

java
@Component
@Primary
class DefaultPaymentClient implements PaymentClient { }

多个类型候选时作为默认优先候选。若多个候选都标 Primary,仍可能冲突。Primary 不会覆盖显式 Qualifier。

3.2 @Qualifier

java
public OrderService(
        @Qualifier("bankPaymentClient") PaymentClient client) {
    this.client = client;
}

Qualifier 不是简单“强制按 Bean 名”,它是候选限定元数据,字符串常与 Bean 名/Qualifier 值匹配。可定义元注解表达业务语义:

java
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface OfflinePayment { }

这比把类名字符串散落在业务代码更稳定。

3.3 Priority 与 Order

单值注入的优先候选可参考 @Priority/Ordered 相关规则;集合注入的排序常使用 @Order/Ordered。@Order 通常不等于单值候选的 Primary,不能混为一谈。

3.4 注入点名称回退

多个类型候选且无 Primary/Qualifier 时,Spring 可能用字段名或参数名匹配 Bean 名。参数名是否可用与编译 -parameters、调试信息和 Spring 版本有关。依赖“字段名刚好等于 Bean 名”很隐蔽,生产推荐显式 Qualifier。

四、@Resource 名称优先全过程

java
@Resource
private PaymentClient bankPaymentClient;

未显式设置 name 时,默认名称通常由字段名或 Setter 属性名推导。常见解析:

mermaid
flowchart TD
    A["发现Resource注入点"] --> B{"显式指定name吗"}
    B -- "是" --> C["按该名称查找资源"]
    B -- "否" --> D["推导字段或属性默认名称"]
    D --> E{"存在同名Bean吗"}
    E -- "是" --> F["按名称取得并校验类型"]
    E -- "否" --> G{"允许fallbackToDefaultTypeMatch吗"}
    G -- "是" --> H["按注入点类型解析候选"]
    G -- "否" --> I["抛找不到资源异常"]
    C --> F

4.1 显式 name

java
@Resource(name = "smsMessageService")
private MessageService messageService;

显式 name 表达明确资源名称,不应再期待“名称找不到后随便按类型”。名称存在但类型不兼容会抛 BeanNotOfRequiredTypeException。

4.2 type 属性

@Resource(type=...) 可显式声明类型,但实际候选、名称、JNDI 和 BeanFactory 行为仍由处理器配置决定。通常直接使用字段/Setter类型更清晰,不应同时给出互相冲突的 name/type。

4.3 默认名称回退不是 JSR-250 的万能结论

“先按名称,失败再按类型”是 Spring CommonAnnotationBeanPostProcessor 常见配置行为,不应推广成所有 JSR-250 容器都完全相同;阅读源码和排查必须标注 Spring 版本及处理器配置。

五、两者支持的注入位置

5.1 @Autowired 构造器

java
public OrderService(PaymentClient client) {
    this.client = client;
}

Spring 4.3+ 单构造器通常不需要写注解。多构造器时可用 @Autowired(required=true/false) 指示候选,但复杂重载容易产生歧义。

5.2 @Autowired 方法

java
@Autowired
public void configure(PaymentClient client, AuditService auditService) {
    this.client = client;
    this.auditService = auditService;
}

不限于 JavaBean Setter;整个方法参数一起解析。required=false 方法在依赖不满足时可能整体跳过,而不是给每个参数随意传 null,具体按版本语义验证。

5.3 @Resource Setter

java
@Resource(name = "bankPaymentClient")
public void setPaymentClient(PaymentClient client) {
    this.client = client;
}

标准 Resource 的目标通常是字段和方法,不用于构造器参数;需要构造器限定时使用 @Autowired + @Qualifier 或 JSR-330 @Inject + @Named(项目具备依赖时)。

六、为什么优先构造器注入

方式优点风险
构造器强制依赖、final、直接测试、对象完整参数过多暴露职责过重
Setter真正可选/可替换依赖清晰对象可能短暂不完整
字段代码短隐藏依赖、难直接new、不能final

“Setter 性能一定比构造器好”没有普遍意义。容器最终都要解析依赖;设计应优先保证对象不变量和可测试性。构造器参数很多通常说明类承担职责过多,应拆分,而不是改字段注入隐藏问题。

七、可选依赖的四种语义

7.1 required=false

java
@Autowired(required = false)
private OptionalExporter exporter;

没有候选时字段保持默认 null。业务每次使用都要判空;关键能力不应设置 false 后静默缺失。

7.2 Optional<T>

java
public ExportService(Optional<OptionalExporter> exporter) {
    this.exporter = exporter;
}

显式表达可能不存在,但实体字段到处使用 Optional 会增加复杂度。多个候选仍需消歧。

7.3 @Nullable

注入点标注可空,容器可在无候选时传 null。要确认使用的是 Spring 支持的 Nullable 注解和版本;null 仍有运行时误用风险。

7.4 ObjectProvider<T>

java
public ExportService(ObjectProvider<OptionalExporter> provider) {
    this.provider = provider;
}

支持按需、可选、迭代和有序流。getObject() 无候选会抛异常,getIfAvailable() 才返回 null/默认。它适合插件、Lazy和 singleton 每次取 prototype;不要用它把所有必需依赖推迟到运行时。

八、集合、数组和 Map 注入

java
public RuleEngine(List<ValidationRule> rules,
                  Map<String, ValidationRule> ruleMap) {
    this.rules = rules;
    this.ruleMap = ruleMap;
}
  • List/数组:所有类型匹配候选,通常按 Order/Ordered 排序;
  • Set:所有候选,迭代顺序不要无条件假定;
  • Map<String,T>:key 常为 Bean 名,value 是候选;
  • 空集合语义与 required/版本有关,应测试;
  • Qualifier 可过滤集合候选。

业务不能直接把外部输入当 ruleMap Bean 名调用任意 Bean,应维护安全业务码映射。

九、泛型作为候选限定

java
interface Handler<T> { void handle(T value); }
class OrderHandler implements Handler<Order> { }
class UserHandler implements Handler<User> { }

@Autowired
private Handler<Order> orderHandler;

Spring 使用 ResolvableType 读取字段、参数、接口和父类泛型签名,能把泛型作为隐式限定符。Java 类型擦除不等于 class 文件中所有泛型签名消失。

风险:原始类型 Handler 会丢失限定;JDK/CGLIB代理的类型暴露、桥接方法和 FactoryBean getObjectType 可能影响匹配。复杂泛型注入要写上下文测试。

十、代理对象会怎样影响注入类型

JDK 动态代理实现接口但不是目标实现类子类:

java
@Autowired
private PaymentService paymentService; // 接口,稳定

@Autowired
private PaymentServiceImpl impl; // JDK代理时可能无法满足

CGLIB 代理可按类赋值,但 final/可见性仍有限制。业务依赖应面向接口;排查 BeanNotOfRequiredType 时查看 bean.getClass()、AopUtils 和 FactoryBean#getObjectType()

十一、父子容器、Profile 和 Condition

找不到 Bean 不一定是没写 @Component

  • Bean 可能在 DispatcherServlet 子上下文,父上下文看不到;
  • Profile 未激活;
  • @Conditional 未满足;
  • 配置类未被 Import;
  • ComponentScan 不覆盖;
  • 测试 Slice 只加载部分 Bean;
  • 同名定义被覆盖/禁用;
  • Spring Boot 条件回退注册了另一实现。

用 Condition Evaluation Report、BeanDefinition 列表和上下文层次查证,不能只扩大扫描包。

十二、@Resource 与 JNDI 的关系

Resource 最初用于容器资源注入,name 可指逻辑资源。Spring CommonAnnotationBeanPostProcessor 支持配置 jndiFactoryresourceFactoryalwaysUseJndiLookup 等策略;普通 Spring Bean 名解析和应用服务器 JNDI 不能混为一谈。

部署到应用服务器时,java:comp/env/...、resource-ref 和实际 JNDI 名称映射由容器配置决定。Spring Boot 独立应用通常直接注入 DataSource Bean,不需要为每个 Resource 走 JNDI。

十三、完整可运行 Demo

java
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

public class InjectionDemo {
    interface MessageService { String send(); }
    static class EmailService implements MessageService {
        public String send() { return "EMAIL"; }
    }
    static class SmsService implements MessageService {
        public String send() { return "SMS"; }
    }

    static class NoticeService {
        private final MessageService constructorService;

        @Resource(name = "smsMessageService")
        private MessageService resourceService;

        @Autowired
        NoticeService(
                @Qualifier("emailMessageService") MessageService service) {
            this.constructorService = service;
        }

        String result() {
            return constructorService.send() + "+" + resourceService.send();
        }
    }

    @Configuration
    static class Config {
        @Bean @Primary
        MessageService defaultMessageService() { return new SmsService(); }
        @Bean
        MessageService emailMessageService() { return new EmailService(); }
        @Bean
        MessageService smsMessageService() { return new SmsService(); }
        @Bean
        NoticeService noticeService(
                @Qualifier("emailMessageService") MessageService service) {
            return new NoticeService(service);
        }
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);
        try {
            System.out.println(context.getBean(NoticeService.class).result());
        } finally {
            context.close();
        }
    }
}

期望输出 EMAIL+SMS:构造器 Qualifier 选 Email,字段 Resource 显式名称选 SMS。@Primary 的 default 不会覆盖显式限定。

十四、商业策略链 Demo

java
@Service
public class NotificationRouter {
    private final Map<String, NotificationChannel> channels;

    public NotificationRouter(List<NotificationChannel> channelList) {
        Map<String, NotificationChannel> mapping = new HashMap<String, NotificationChannel>();
        for (NotificationChannel channel : channelList) {
            if (mapping.put(channel.code(), channel) != null) {
                throw new IllegalStateException("duplicate channel code: " + channel.code());
            }
        }
        this.channels = Collections.unmodifiableMap(mapping);
    }
}

这里按业务 code() 建映射,而不是把 Bean 名暴露给客户端。启动时检测重复,运行时无匹配返回明确业务错误。所有 Channel singleton 必须线程安全。

十五、常见异常诊断

15.1 NoSuchBeanDefinitionException

读取注入点完整 ResolvableType、Qualifier、上下文层次、Profile/Condition、扫描和配置导入。接口有实现类但实现未注册为 Bean,仍然找不到。

15.2 NoUniqueBeanDefinitionException

异常会列候选名称。判断业务是默认实现、指定实现还是全部策略;使用 Primary、Qualifier、List/Map。不要靠字段改名作为唯一修复,也不要删除合法实现。

15.3 UnsatisfiedDependencyException

是依赖链包装异常,沿最深 cause 查真正 Bean、构造器和配置。例如 A→B→DataSource,最外层报 A 注入失败,根因可能是数据库 URL 缺失。

15.4 BeanNotOfRequiredTypeException

名称找到了 Bean,但实际类型不能赋值。检查同名 Bean、FactoryBean 产品、JDK 代理和 @Resource(name=...) 是否指向错误类型。

15.5 字段仍为 null

常见不是 Spring “没注入”,而是对象手工 new、测试没有启动 Spring、字段 static、注入发生前在构造器使用字段、处理器未注册或该类不是 Bean。字段注入要到实例化后的属性填充才完成,构造器内读取必然尚未注入。

十六、循环依赖与注入方式

构造器 A→B→A 无法实例化任一方,启动立即失败,迫使暴露设计问题。字段/Setter singleton 可能通过早期引用解决,但代理一致性、Spring Boot 默认禁止循环依赖等会限制。能被容器解决不等于设计合理;优先拆职责、提取协调者或事件解耦。

十七、生产排查 Runbook

17.1 注入错实现

记录注入点、实际 Bean class、Bean 名、Qualifier、Primary、Profile、Condition 和代理类型。检查测试 Mock/备用配置是否进入生产。不要记录对象敏感配置。

17.2 本地成功、部署失败

比较构建参数 -parameters、JDK/Spring版本、ClassPath、Profile、环境变量、条件报告、组件扫描和父子上下文。字段名回退依赖编译元数据时尤其容易环境差异,改显式 Qualifier。

17.3 升级 Spring 6 后 Resource 不工作

检查是否仍使用 javax.annotation.Resource,依赖是否迁到 jakarta.annotation-api,第三方库是否支持 Jakarta。Spring 6/Boot 3 基线 Java 17;不能在 JDK 8 中通过仅改 import 升级。

17.4 启动变慢

检查候选数量、超宽扫描、复杂泛型、FactoryBean 类型预测、构造器中远程 I/O 和注入后初始化。依赖解析通常不是主要瓶颈,真正慢常在 Bean 构造/init。

17.5 运行时才报缺 Bean

通常使用 ObjectProvider、Lazy 或条件插件把错误推迟。确认依赖是否真正可选;核心能力应在启动时验证并失败,而不是首个请求才报错。

十八、版本迁移边界

环境Resource 包
JDK 8 + Spring 5常见 javax.annotation.Resource,API可能来自JDK/依赖
JDK 9/10java.xml.ws.annotation模块已标记演进/弃用
JDK 11+Java EE模块移除,需显式annotation-api依赖
Spring 6 / Boot 3jakarta.annotation.Resource,Java 17基线

Spring @Autowired 包名没有随 Jakarta 改名。迁移时同时检查 @PostConstruct@PreDestroy、Servlet/JPA/Validation 包,不要只替换 Resource。

十九、常见错误与后果

错误后果正确方向
多实现依赖字段名碰巧匹配重命名后启动失败/注入变化显式Qualifier
多个Primary仍然NoUnique只定义一个默认
required=false用于核心依赖运行时NPE/静默降级启动失败
字段注入并在构造器读取构造时为null构造器注入
手工new带注解对象注入和AOP均失效由容器创建
Resource name指错类型BeanNotOfRequiredType校验名称与接口
Map Bean名直接接受用户输入可调用非预期策略业务码白名单
注入具体实现遇JDK代理类型不匹配面向接口
Lazy掩盖缺Bean首次流量失败核心依赖启动校验
javax/jakarta混用注解不处理/类找不到按版本整体迁移

二十、面试标准回答

Autowired 和 Resource 区别

Autowired来自Spring,由AutowiredAnnotationBeanPostProcessor处理,默认按ResolvableType筛选并结合Qualifier、Primary、Priority和名称;支持构造器、字段和方法。Resource来自JSR-250/Jakarta,由CommonAnnotationBeanPostProcessor处理,字段/Setter常按显式或推导名称查找,再按配置决定类型回退。

为什么推荐构造器注入

强制依赖创建时必须提供,对象创建后完整,字段可final,测试可直接new,并会立即暴露构造器循环依赖。字段注入依赖反射,在对象实例化后才填充,隐藏依赖且构造器中尚为null。

多个实现怎么注入

单值使用语义明确的Qualifier或唯一Primary;全部策略使用List/Map并用Order排序,再建立业务码映射。字段名匹配可作为容器回退,但不应作为核心设计契约。

Resource 按名称找不到一定按类型吗

不能无条件这么说。Spring 常见处理器在未显式name且默认名称不存在时,可按fallbackToDefaultTypeMatch进行类型解析;显式name通常要求该资源。具体行为取决于Spring版本和CommonAnnotationBeanPostProcessor配置。

字段有Autowired为什么仍是null

先确认对象是否由Spring创建;字段注入在实例化后属性填充阶段执行,因此构造器中读取仍为null。还要检查static字段、测试上下文、处理器、候选和是否手工new。

二十一、关联知识与验收

掌握验收:

  • 能说出两种注解对应的 BeanPostProcessor;
  • 能画出构造器注入与字段注入在生命周期中的不同位置;
  • 能按类型、Qualifier、Primary、Priority、名称分析多个候选;
  • 能解释 Resource 显式名称和默认名称回退差异;
  • 能选择 required=false、Optional、Nullable、ObjectProvider;
  • 能解释 List/Map 和泛型限定注入;
  • 能定位手工new、JDK代理、父子上下文、Profile和Condition导致的注入问题;
  • 能说明 javax.annotation 到 jakarta.annotation 迁移为什么不能只改一个import。