90% Java 开发都踩过坑的 @Resource 与 @Autowired

大家在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完整注入规则

  1. 当注解显式指定了name属性时,Spring会仅按指定的name值作为Bean名称去IoC容器中查找,找到则注入,找不到直接抛出NoSuchBeanDefinitionException,不会再进行类型查找。
  2. 当注解没有指定name属性时,Spring会先提取被标注字段的名称(或setter方法对应的属性名)作为默认的Bean名称,去IoC容器中查找。
  3. 如果按默认名称找到了对应的Bean,直接注入;如果没找到,会退化为按字段的类型去IoC容器中查找。
  4. 如果按类型找到了唯一的Bean,直接注入;如果按类型找到了多个Bean,Spring会再次用之前的默认Bean名称去匹配这多个Bean的名称,匹配到唯一的则注入,匹配不到则抛出NoUniqueBeanDefinitionException。

@Autowired完整注入规则

  1. 注解默认按被标注字段/参数的类型去IoC容器中查找Bean,先进行类型匹配。
  2. 如果按类型找到0个Bean,会判断注解的required属性值:如果required=true(默认值),直接抛出NoSuchBeanDefinitionException;如果required=false,注入null,不会报错。
  3. 如果按类型找到了唯一的Bean,直接注入。
  4. 如果按类型找到了多个Bean,Spring会先检查这些Bean中是否有标注了@Primary注解的Bean,如果有且只有一个@Primary标注的Bean,直接注入这个Bean。
  5. 如果没有@Primary标注的Bean,Spring会提取被标注字段/参数的名称作为Bean名称,去匹配这多个Bean的名称,匹配到唯一的则注入。
  6. 如果名称匹配也没有找到唯一的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接口,具备更高的执行优先级。 核心执行流程如下:

  1. 重写postProcessProperties方法,在Bean的属性填充阶段触发执行,这是依赖注入的入口。
  2. 调用findResourceMetadata方法,解析当前Bean的Class对象,遍历所有字段和方法,筛选出标注了@Resource注解的元素,生成注入元数据InjectionMetadata,并且会将元数据缓存起来,避免重复解析。
  3. 调用InjectionMetadata的inject方法,执行具体的依赖注入。inject方法会遍历所有解析到的注入元素,逐个完成依赖查找和反射赋值。
  4. 依赖查找阶段,会严格按照之前拆解的注入规则执行:先判断是否指定了name属性,指定了则仅按名称查找;未指定则先按字段名查找,找不到再按类型查找;多Bean场景下再进行名称匹配,匹配失败则抛出异常。
  5. 查找完成后,通过反射将找到的Bean实例赋值给对应的字段,或者调用对应的setter方法,完成注入。 这里有一个关键的细节:当@Resource注解同时指定了name和type属性时,Spring会同时按名称和类型进行匹配,只有名称和类型都完全匹配的Bean,才会被注入,任何一个条件不满足都会直接报错,不会有降级查找的逻辑。

@Autowired的源码处理逻辑

处理@Autowired注解的核心类是AutowiredAnnotationBeanPostProcessor,这个类同样实现了InstantiationAwareBeanPostProcessor和PriorityOrdered接口,但其order值比CommonAnnotationBeanPostProcessor低,所以执行顺序更靠后。 核心执行流程如下:

  1. 重写postProcessProperties方法,在Bean的属性填充阶段触发执行,和CommonAnnotationBeanPostProcessor的执行时机相同,但优先级更低。
  2. 调用findAutowiringMetadata方法,解析当前Bean的Class对象,遍历所有字段、构造方法、普通方法,筛选出标注了@Autowired、@Value注解的元素,生成注入元数据InjectionMetadata,同样会进行缓存优化。
  3. 调用InjectionMetadata的inject方法,执行具体的依赖注入。和@Resource不同的是,@Autowired的注入逻辑会区分字段注入、方法注入、构造器注入三种场景,分别处理。
  4. 依赖查找阶段,核心调用了BeanFactory的resolveDependency方法,这个方法是Spring依赖注入的核心实现,封装了按类型查找、@Primary注解处理、@Qualifier注解处理、名称匹配、泛型匹配等所有复杂逻辑。
  5. 查找完成后,会先判断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的注入逻辑会后执行。

相关推荐
kybs19911 小时前
springboot租车系统--附源码68701
java·hadoop·spring boot·python·django·asp.net·php
过期动态2 小时前
MySQL中的约束
android·java·数据库·spring boot·mysql
wxin_VXbishe2 小时前
springboot新能源车充电站管理系统小程序-计算机毕业设计源码29213
java·c++·spring boot·python·spring·django·php
程序员陆通2 小时前
月烧 400 刀到不到 20 刀:我是怎么把 OpenClaw 的 Token 账单砍掉 95% 的
java·前端·数据库
代码漫谈3 小时前
一文学习 SpringBoot 的 application.yml 配置,基于 Spring Boot 3.2.x
java·spring boot·spring·配置文件
SamDeepThinking3 小时前
程序员如何接受工作内容毫无意义?
java·后端·程序员
三翼鸟数字化技术团队3 小时前
基于Redis ZSet实现分布式优先级队列的技术实践
java·redis
无所事事O_o3 小时前
加密过程及原理浅析
java·加密
2301_771717213 小时前
最近在刷牛客:使用Spring AOP实现性能监控时
java·后端·spring