大家在Spring项目里写依赖注入时,是不是总在@Resource和@Autowired之间犹豫?两个注解都能完成对象注入,却不知道核心差异在哪?线上环境突然爆出NoUniqueBeanDefinitionException,排查半天才发现是注解的注入逻辑搞反了?JDK版本升级后,@Resource突然报找不到类的错误,却不知道怎么解决?
注解的核心归属与出身差异
@Resource的出身:JSR-250 Java规范定义的注解,JDK8及之前全限定名为javax.annotation.Resource,JDK9模块化之后,javax.annotation包被移除,JDK11+及Jakarta EE 9+环境中,全限定名变为jakarta.annotation.Resource。它是Java官方的依赖注入注解,不绑定任何具体的容器,只要是实现了JSR-250规范的IoC容器,都能支持这个注解的解析。 @Autowired的出身:Spring Framework原生定义的注解,全限定名org.springframework.beans.factory.annotation.Autowired,从Spring 2.5版本开始引入,和Spring IoC容器深度绑定,只能在Spring环境中生效,脱离Spring容器,这个注解没有任何意义。 两者的出身差异,直接决定了它们的兼容性边界、注入逻辑设计、以及与Spring生态的配合度。如果你的组件需要在不同的IoC容器中运行,@Resource的兼容性优势会非常明显,而@Autowired只能在Spring体系内使用。
核心注入逻辑与查找顺序全拆解
注入逻辑是两个注解最核心的差异,也是绝大多数踩坑场景的根源。
@Resource完整注入规则
- 当注解显式指定了name属性时,Spring会仅按指定的name值作为Bean名称去IoC容器中查找,找到则注入,找不到直接抛出NoSuchBeanDefinitionException,不会再进行类型查找。
- 当注解没有指定name属性时,Spring会先提取被标注字段的名称(或setter方法对应的属性名)作为默认的Bean名称,去IoC容器中查找。
- 如果按默认名称找到了对应的Bean,直接注入;如果没找到,会退化为按字段的类型去IoC容器中查找。
- 如果按类型找到了唯一的Bean,直接注入;如果按类型找到了多个Bean,Spring会再次用之前的默认Bean名称去匹配这多个Bean的名称,匹配到唯一的则注入,匹配不到则抛出NoUniqueBeanDefinitionException。

@Autowired完整注入规则
- 注解默认按被标注字段/参数的类型去IoC容器中查找Bean,先进行类型匹配。
- 如果按类型找到0个Bean,会判断注解的required属性值:如果required=true(默认值),直接抛出NoSuchBeanDefinitionException;如果required=false,注入null,不会报错。
- 如果按类型找到了唯一的Bean,直接注入。
- 如果按类型找到了多个Bean,Spring会先检查这些Bean中是否有标注了@Primary注解的Bean,如果有且只有一个@Primary标注的Bean,直接注入这个Bean。
- 如果没有@Primary标注的Bean,Spring会提取被标注字段/参数的名称作为Bean名称,去匹配这多个Bean的名称,匹配到唯一的则注入。
- 如果名称匹配也没有找到唯一的Bean,会检查是否有@Qualifier注解指定了Bean名称,指定了则按指定名称匹配,匹配到则注入,匹配不到则抛出NoUniqueBeanDefinitionException。

全维度特性对比
| 对比维度 | @Resource | @Autowired |
|---|---|---|
| 规范归属 | JSR-250 Java/Jakarta EE官方规范 | Spring Framework原生规范 |
| 全限定名 | jakarta.annotation.Resource(JDK11+)/javax.annotation.Resource(JDK8-) | org.springframework.beans.factory.annotation.Autowired |
| 默认注入策略 | 优先按名称(byName)注入 | 优先按类型(byType)注入 |
| 核心属性 | name、type | required |
| 注入失败处理 | 无容错配置,找不到符合要求的Bean直接抛出异常 | 支持required=false配置,找不到Bean时注入null,不影响启动 |
| 支持的注入点 | 字段、setter方法 | 字段、setter方法、构造方法、普通多参数方法 |
| 构造器注入支持 | 不支持 | 支持,Spring 4.3+单构造器场景可省略注解 |
| @Qualifier配合 | 支持,但不推荐,自身已有name属性指定Bean名称 | 支持,多Bean场景下常用配合方式 |
| @Primary配合 | 支持,多Bean类型匹配时生效 | 原生支持,多Bean场景优先级最高 |
| 容器兼容性 | 兼容所有实现JSR-250规范的IoC容器 | 仅兼容Spring IoC容器 |
| Spring解析处理器 | CommonAnnotationBeanPostProcessor | AutowiredAnnotationBeanPostProcessor |
| 执行优先级 | 更高,CommonAnnotationBeanPostProcessor先于AutowiredAnnotationBeanPostProcessor执行 | 低于@Resource |
代码示例
项目依赖配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0</version>
<relativePath/>
</parent>
<groupId>com.jam.demo</groupId>
<artifactId>resource-autowired-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>resource-autowired-demo</name>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version>
<springdoc.version>2.6.0</springdoc.version>
<guava.version>33.2.1-jre</guava.version>
<fastjson2.version>2.0.53</fastjson2.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
项目配置文件
ruby
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/demo_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: resource-autowired-demo
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
type-aliases-package: com.jam.demo.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
springdoc:
swagger-ui:
path: /swagger-ui.html
enabled: true
api-docs:
enabled: true
path: /v3/api-docs
数据库表结构
sql
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_name` varchar(64) NOT NULL COMMENT '用户名',
`real_name` varchar(64) DEFAULT NULL COMMENT '真实姓名',
`age` int DEFAULT NULL COMMENT '年龄',
`email` varchar(128) DEFAULT NULL COMMENT '邮箱',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint DEFAULT '0' COMMENT '逻辑删除标识 0-未删除 1-已删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统用户表';
项目启动类
kotlin
package com.jam.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 项目启动类
* @author ken
*/
@SpringBootApplication
@MapperScan("com.jam.demo.mapper")
public class ResourceAutowiredDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceAutowiredDemoApplication.class, args);
}
}
实体类定义
kotlin
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 系统用户实体
* @author ken
*/
@Data
@TableName("sys_user")
@Schema(description = "系统用户实体")
public class SysUser {
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID", example = "1")
private Long id;
@Schema(description = "用户名", example = "zhangsan")
private String userName;
@Schema(description = "真实姓名", example = "张三")
private String realName;
@Schema(description = "年龄", example = "25")
private Integer age;
@Schema(description = "邮箱", example = "zhangsan@demo.com")
private String email;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@TableLogic
@Schema(description = "逻辑删除标识", example = "0")
private Integer deleted;
}
数据访问层定义
java
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统用户Mapper
* @author ken
*/
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
}
服务层接口定义
java
package com.jam.demo.service;
import com.jam.demo.entity.SysUser;
import java.util.List;
/**
* 用户服务接口
* @author ken
*/
public interface UserService {
/**
* 查询所有用户列表
* @return 用户列表
*/
List<SysUser> listAllUsers();
/**
* 根据ID查询用户
* @param id 用户ID
* @return 用户信息
*/
SysUser getUserById(Long id);
}
服务层实现类
kotlin
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.entity.SysUser;
import com.jam.demo.mapper.SysUserMapper;
import com.jam.demo.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 管理员用户服务实现
* @author ken
*/
@Service("adminUserService")
@RequiredArgsConstructor
public class AdminUserServiceImpl implements UserService {
private final SysUserMapper sysUserMapper;
@Override
public List<SysUser> listAllUsers() {
return sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getDeleted, 0));
}
@Override
public SysUser getUserById(Long id) {
return sysUserMapper.selectById(id);
}
}
java
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.entity.SysUser;
import com.jam.demo.mapper.SysUserMapper;
import com.jam.demo.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 普通用户服务实现
* @author ken
*/
@Service("normalUserService")
@RequiredArgsConstructor
public class NormalUserServiceImpl implements UserService {
private final SysUserMapper sysUserMapper;
@Override
public List<SysUser> listAllUsers() {
return sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getDeleted, 0)
.eq(SysUser::getAge, 18));
}
@Override
public SysUser getUserById(Long id) {
SysUser user = sysUserMapper.selectById(id);
if (user != null) {
user.setEmail(null);
}
return user;
}
}
kotlin
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.entity.SysUser;
import com.jam.demo.mapper.SysUserMapper;
import com.jam.demo.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 首选用户服务实现
* @author ken
*/
@Primary
@Service("primaryUserService")
@RequiredArgsConstructor
public class PrimaryUserServiceImpl implements UserService {
private final SysUserMapper sysUserMapper;
@Override
public List<SysUser> listAllUsers() {
return sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getDeleted, 0)
.orderByDesc(SysUser::getCreateTime));
}
@Override
public SysUser getUserById(Long id) {
return sysUserMapper.selectById(id);
}
}
基础注入场景演示
kotlin
package com.jam.demo.controller;
import com.jam.demo.entity.SysUser;
import com.jam.demo.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 基础依赖注入演示Controller
* @author ken
*/
@RestController
@RequestMapping("/base/inject")
@Tag(name = "基础注入演示", description = "演示@Resource与@Autowired的基础注入用法")
public class BaseInjectController {
@Resource
private UserService adminUserService;
@Autowired
private UserService normalUserService;
@GetMapping("/admin/list")
@Operation(summary = "查询管理员用户列表", description = "使用@Resource注入adminUserService")
public List<SysUser> listAdminUsers() {
return adminUserService.listAllUsers();
}
@GetMapping("/normal/list")
@Operation(summary = "查询普通用户列表", description = "使用@Autowired注入normalUserService")
public List<SysUser> listNormalUsers() {
return normalUserService.listAllUsers();
}
@GetMapping("/user/{id}")
@Operation(summary = "根据ID查询用户", description = "使用@Resource注入的服务查询用户")
public SysUser getUserById(
@Parameter(description = "用户ID", required = true, example = "1")
@PathVariable Long id) {
return adminUserService.getUserById(id);
}
}
指定名称注入场景演示
kotlin
package com.jam.demo.controller;
import com.jam.demo.entity.SysUser;
import com.jam.demo.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 指定名称注入演示Controller
* @author ken
*/
@RestController
@RequestMapping("/name/specify")
@Tag(name = "指定名称注入演示", description = "演示显式指定Bean名称的注入用法")
public class NameSpecifyController {
@Resource(name = "normalUserService")
private UserService userServiceByResource;
@Autowired
@Qualifier("adminUserService")
private UserService userServiceByAutowired;
@GetMapping("/resource/normal/list")
@Operation(summary = "@Resource指定名称注入", description = "显式指定name为normalUserService")
public List<SysUser> listNormalUsersByResource() {
return userServiceByResource.listAllUsers();
}
@GetMapping("/autowired/admin/list")
@Operation(summary = "@Autowired配合@Qualifier指定名称", description = "用@Qualifier指定adminUserService")
public List<SysUser> listAdminUsersByAutowired() {
return userServiceByAutowired.listAllUsers();
}
}
构造器注入场景演示
kotlin
package com.jam.demo.controller;
import com.jam.demo.entity.SysUser;
import com.jam.demo.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 构造器注入演示Controller
* @author ken
*/
@RestController
@RequestMapping("/constructor/inject")
@Tag(name = "构造器注入演示", description = "演示@Autowired的构造器注入用法")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class ConstructorInjectController {
private final UserService adminUserService;
private final UserService normalUserService;
@GetMapping("/admin/list")
@Operation(summary = "构造器注入管理员服务", description = "使用@Autowired构造器注入adminUserService")
public List<SysUser> listAdminUsers() {
return adminUserService.listAllUsers();
}
@GetMapping("/normal/list")
@Operation(summary = "构造器注入普通用户服务", description = "使用@Autowired构造器注入normalUserService")
public List<SysUser> listNormalUsers() {
return normalUserService.listAllUsers();
}
}
可选依赖注入场景演示
kotlin
package com.jam.demo.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 可选依赖注入演示Controller
* @author ken
*/
@RestController
@RequestMapping("/optional/inject")
@Tag(name = "可选依赖注入演示", description = "演示@Autowired的required=false用法")
public class OptionalInjectController {
@Autowired(required = false)
private String notExistBean;
@GetMapping("/check")
@Operation(summary = "检查可选依赖", description = "验证required=false的注入效果")
public String checkOptionalBean() {
return "notExistBean is " + (notExistBean == null ? "null" : notExistBean);
}
}
@Primary注解配合场景演示
kotlin
package com.jam.demo.controller;
import com.jam.demo.entity.SysUser;
import com.jam.demo.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Primary注解配合注入演示Controller
* @author ken
*/
@RestController
@RequestMapping("/primary/inject")
@Tag(name = "@Primary配合注入演示", description = "演示@Primary注解与两个注解的配合效果")
public class PrimaryInjectController {
@Autowired
private UserService userService;
@Resource(type = UserService.class)
private UserService userServiceByResource;
@GetMapping("/autowired/list")
@Operation(summary = "@Autowired配合@Primary", description = "按类型注入时优先选择@Primary标注的Bean")
public List<SysUser> listUsersByAutowired() {
return userService.listAllUsers();
}
@GetMapping("/resource/list")
@Operation(summary = "@Resource配合@Primary", description = "按类型注入时优先选择@Primary标注的Bean")
public List<SysUser> listUsersByResource() {
return userServiceByResource.listAllUsers();
}
}
Spring底层源码深度拆解
Spring对两个注解的处理,核心是通过BeanPostProcessor扩展机制实现的。在Spring Bean的生命周期中,属性填充阶段(populateBean)会调用InstantiationAwareBeanPostProcessor的postProcessProperties方法,两个注解的注入逻辑,都在这个方法中完成。
@Resource的源码处理逻辑
处理@Resource注解的核心类是CommonAnnotationBeanPostProcessor,这个类继承了InitDestroyAnnotationBeanPostProcessor,实现了InstantiationAwareBeanPostProcessor和PriorityOrdered接口,具备更高的执行优先级。 核心执行流程如下:
- 重写postProcessProperties方法,在Bean的属性填充阶段触发执行,这是依赖注入的入口。
- 调用findResourceMetadata方法,解析当前Bean的Class对象,遍历所有字段和方法,筛选出标注了@Resource注解的元素,生成注入元数据InjectionMetadata,并且会将元数据缓存起来,避免重复解析。
- 调用InjectionMetadata的inject方法,执行具体的依赖注入。inject方法会遍历所有解析到的注入元素,逐个完成依赖查找和反射赋值。
- 依赖查找阶段,会严格按照之前拆解的注入规则执行:先判断是否指定了name属性,指定了则仅按名称查找;未指定则先按字段名查找,找不到再按类型查找;多Bean场景下再进行名称匹配,匹配失败则抛出异常。
- 查找完成后,通过反射将找到的Bean实例赋值给对应的字段,或者调用对应的setter方法,完成注入。 这里有一个关键的细节:当@Resource注解同时指定了name和type属性时,Spring会同时按名称和类型进行匹配,只有名称和类型都完全匹配的Bean,才会被注入,任何一个条件不满足都会直接报错,不会有降级查找的逻辑。
@Autowired的源码处理逻辑
处理@Autowired注解的核心类是AutowiredAnnotationBeanPostProcessor,这个类同样实现了InstantiationAwareBeanPostProcessor和PriorityOrdered接口,但其order值比CommonAnnotationBeanPostProcessor低,所以执行顺序更靠后。 核心执行流程如下:
- 重写postProcessProperties方法,在Bean的属性填充阶段触发执行,和CommonAnnotationBeanPostProcessor的执行时机相同,但优先级更低。
- 调用findAutowiringMetadata方法,解析当前Bean的Class对象,遍历所有字段、构造方法、普通方法,筛选出标注了@Autowired、@Value注解的元素,生成注入元数据InjectionMetadata,同样会进行缓存优化。
- 调用InjectionMetadata的inject方法,执行具体的依赖注入。和@Resource不同的是,@Autowired的注入逻辑会区分字段注入、方法注入、构造器注入三种场景,分别处理。
- 依赖查找阶段,核心调用了BeanFactory的resolveDependency方法,这个方法是Spring依赖注入的核心实现,封装了按类型查找、@Primary注解处理、@Qualifier注解处理、名称匹配、泛型匹配等所有复杂逻辑。
- 查找完成后,会先判断required属性的值,如果required=true但没有找到符合要求的Bean,直接抛出异常;如果required=false则注入null。找到Bean实例后,通过反射完成赋值或方法调用,结束注入流程。
两个注解的执行优先级差异
CommonAnnotationBeanPostProcessor的getOrder()方法返回值为Ordered.LOWEST_PRECEDENCE - 3,而AutowiredAnnotationBeanPostProcessor的getOrder()方法返回值为Ordered.LOWEST_PRECEDENCE - 2。在Spring的优先级规则中,order值越小,优先级越高,所以CommonAnnotationBeanPostProcessor会先执行。 这就意味着,如果同一个字段上同时标注了@Resource和@Autowired两个注解,@Resource的注入会先执行,@Autowired的注入会后执行,最终字段的值会被@Autowired的注入结果覆盖,出现不可预期的执行结果,这也是开发中需要严格避免的用法。
高频踩坑场景全复盘
JDK17环境下@Resource注解找不到类
问题复现:JDK17项目中仅引入Spring Boot基础依赖,使用@Resource注解时,IDE报类找不到的错误,编译失败。 根本原因:JDK9引入模块化机制后,javax.annotation包被从JDK核心库中移除,JDK11+版本完全不再包含这个包。Spring Boot 3.x版本全面适配Jakarta EE 9+,注解的包名已经从javax.annotation变更为jakarta.annotation,必须手动引入对应的依赖才能使用。 解决方案:在pom.xml中引入jakarta.annotation-api依赖,也就是示例项目中已经添加的依赖配置,即可解决这个问题。
多Bean场景下注入逻辑搞反导致的启动异常
问题复现:一个接口有多个实现类,字段名没有对应任何Bean的名称,使用@Autowired注解注入,启动时抛出NoUniqueBeanDefinitionException异常。 根本原因:@Autowired默认按类型查找,找到多个同类型的Bean后,才会用字段名去匹配Bean名称,字段名没有对应的Bean时,就会抛出异常。很多开发者误以为@Autowired会先按名称查找,写错字段名后没有及时发现,导致启动失败。 解决方案:三种方式都可以解决这个问题,一是使用@Resource注解,显式指定name属性为目标Bean的名称;二是使用@Autowired配合@Qualifier注解,显式指定Bean名称;三是给其中一个实现类添加@Primary注解,标注为首选注入的Bean。
@Resource指定name属性后,Bean名称写错导致启动失败
问题复现:@Resource注解的name属性值写错,和@Service注解中指定的Bean名称不一致,启动时抛出NoSuchBeanDefinitionException异常。 根本原因:@Resource注解一旦显式指定了name属性,Spring就只会按指定的名称去查找Bean,找不到就会直接抛出异常,不会退化为按类型查找,没有任何降级逻辑。 解决方案:严格核对@Service注解中指定的Bean名称,和@Resource的name属性值保持完全一致。如果没有显式指定Bean名称,Spring默认的Bean名称是类名首字母小写,需要按照这个规则填写name属性。
构造器注入使用@Resource注解导致注入失败
问题复现:在类的构造方法上标注@Resource注解,启动时无法完成注入,甚至直接报编译错误。 根本原因:@Resource注解的@Target元注解中,只包含了TYPE、FIELD、METHOD三种类型,没有包含CONSTRUCTOR类型,所以Java编译器就不允许在构造方法上标注@Resource注解,Spring自然也无法处理这种场景的注入。 解决方案:构造器注入统一使用@Autowired注解,Spring 4.3+版本中,如果类只有一个构造方法,甚至可以省略@Autowired注解,Spring会自动完成构造器注入。
可选依赖场景使用@Resource导致启动失败
问题复现:依赖的Bean是可选的,可能不存在于容器中,使用@Resource注解注入,启动时直接抛出NoSuchBeanDefinitionException异常。 根本原因:@Resource注解没有提供required相关的配置项,只要找不到符合要求的Bean,就会直接抛出异常,没有任何容错机制,无法支持可选依赖的场景。 解决方案:两种方式可选,一是使用@Autowired(required=false)注解,找不到Bean时会注入null,不会影响项目启动;二是使用@Resource注解配合Optional类型包装依赖,Spring 5+版本支持Optional类型的注入,找不到Bean时会注入Optional.empty(),不会抛出异常。
静态字段标注注入注解导致注入失败
问题复现:在静态字段上标注@Resource或@Autowired注解,项目启动成功后,字段的值始终为null,注入失败。 根本原因:Spring的依赖注入是基于对象实例的,所有的注入操作都是在Bean实例化之后执行的,而静态字段属于类本身,不属于对象实例,Spring不会处理静态字段的注入,即使通过特殊方式实现,也完全违背了Spring的设计思想。 解决方案:不要在静态字段上使用任何注入注解。如果必须给静态字段赋值,可以在非静态的setter方法上标注注入注解,在setter方法内部给静态字段赋值,同时要注意线程安全问题。
循环依赖场景下构造器注入报错
问题复现:A类依赖B类,B类依赖A类,使用@Autowired构造器注入,启动时抛出循环依赖异常,而使用字段注入则能正常启动。 根本原因:Spring解决循环依赖的核心机制是三级缓存,构造器注入时,Bean还没有完成实例化,无法提前暴露到三级缓存中,Spring无法解决这种场景的循环依赖。而字段注入是在Bean实例化完成之后的属性填充阶段执行的,此时Bean已经被放入三级缓存中,Spring可以提前暴露Bean实例,解决循环依赖问题。 解决方案:最优方案是优化代码结构,拆分依赖关系,从根本上消除循环依赖。如果无法修改代码结构,可以使用字段注入的方式,或者在构造器的参数上添加@Lazy注解,实现依赖的延迟加载,绕过循环依赖的检查。
代理对象注入时的类型匹配失败
问题复现:Bean被AOP动态代理之后,使用@Resource或@Autowired注解注入时,抛出NoSuchBeanDefinitionException或NoUniqueBeanDefinitionException异常。 根本原因:Spring的AOP动态代理分为两种,JDK动态代理和CGLIB动态代理。JDK动态代理只能代理接口,生成的代理对象的类型是接口类型,不是实现类类型;CGLIB动态代理生成的代理对象是实现类的子类类型。如果注入时使用实现类作为字段类型,而Bean被JDK动态代理了,按类型查找时就会匹配不到,导致注入失败。 解决方案:始终面向接口编程,注入时统一使用接口类型作为字段类型,不要使用实现类类型。如果必须使用实现类类型,需要在Spring配置中开启CGLIB代理,确保代理对象的类型和实现类兼容。
选型建议
字段注入优先选择@Resource,并且显式指定name属性。显式指定name属性后,注入逻辑完全可控,不会因为多Bean场景导致意外报错,代码的可读性和可维护性更高,相比@Autowired+@Qualifier的组合更加简洁,同时作为Java官方规范,具备更好的兼容性。 强制依赖必须使用构造器注入,选择@Autowired。构造器注入能保证依赖的不可变性,强制依赖必须在Bean实例化时传入,从根源上避免空指针异常,同时也更利于单元测试的编写。Spring官方也明确推荐构造器注入用于强制依赖,而@Resource不支持构造器注入,这是@Autowired不可替代的场景。 可选依赖统一使用@Autowired(required=false)。@Resource没有容错机制,无法处理可选依赖的场景,而@Autowired的required属性可以灵活控制依赖是否必须,配合Optional类型使用,能写出更加优雅的可选依赖代码,避免项目启动因为非核心依赖的缺失而失败。 多实现类场景,必须显式指定注入的Bean名称,不要依赖默认的名称匹配。默认的字段名匹配非常脆弱,很容易因为字段名写错、Bean名称修改导致注入失败,显式指定@Resource的name属性,或者@Autowired+@Qualifier,能让注入逻辑更加清晰,排查问题也更加方便。 不要在同一个注入点上同时标注@Resource和@Autowired。两个注解的处理器执行顺序不同,会导致注入结果被覆盖,出现不可预期的执行结果,同时也会让代码的可读性变得极差,给后续的维护带来巨大的麻烦。 面向接口编程,注入时统一使用接口类型,不要使用实现类类型。这样不仅符合依赖倒置的设计原则,提升代码的扩展性,还能避免AOP动态代理带来的类型匹配问题,减少不必要的踩坑。 跨容器兼容的场景,必须使用@Resource。如果开发的是公共组件,需要在不同的IoC容器中运行,@Resource是唯一的选择,它是Java官方规范,所有实现了JSR-250规范的容器都能支持,而@Autowired只能在Spring环境中使用。
常见误区澄清
误区一:@Resource只能按名称注入,@Autowired只能按类型注入。 澄清:这个认知是完全错误的。@Resource默认优先按名称注入,找不到对应的Bean时,会自动退化为按类型注入;@Autowired默认优先按类型注入,找到多个同类型的Bean时,会自动退化为按字段名匹配注入。两个注解都同时支持按名称和按类型注入,只是默认的查找优先级不同。
误区二:@Resource不支持@Qualifier注解。 澄清:@Resource完全支持配合@Qualifier注解使用,只是这种用法完全没有必要。@Resource自身已经提供了name属性来显式指定Bean名称,功能和@Qualifier完全一致,使用@Qualifier属于多此一举。而且需要注意的是,如果@Resource已经显式指定了name属性,@Qualifier注解的配置会被直接忽略,不会生效。
误区三:两个注解可以随便互换,效果完全一样。 澄清:只有在单类型单Bean的场景下,两个注解的注入效果基本一致。在其他绝大多数场景下,互换两个注解都会导致注入失败或者项目启动报错,比如多Bean场景、构造器注入场景、可选依赖场景、代理对象注入场景,两个注解的表现完全不同,绝对不能随便互换。
误区四:@Autowired不能按名称注入。 澄清:@Autowired完全支持按名称注入,有两种标准的实现方式。一种是多Bean场景下,Spring会自动用字段名/参数名去匹配Bean名称,匹配到唯一的Bean就会完成注入;另一种是配合@Qualifier注解,显式指定Bean的名称,强制按名称注入。只是@Autowired的默认注入策略是按类型,按名称注入是降级逻辑。
误区五:@Resource的执行优先级比@Autowired低。 澄清:实际情况正好相反。处理@Resource的CommonAnnotationBeanPostProcessor的优先级,比处理@Autowired的AutowiredAnnotationBeanPostProcessor更高,在Bean的属性填充阶段,@Resource的注入逻辑会先执行,@Autowired的注入逻辑会后执行。