文档版本:V1.0 | 适用版本:Spring Framework 5.x+/Spring Boot 2.x+/3.x | 面向人群:Java初学者、Spring开发工程师、需规范生产代码的研发人员


开篇:先搞懂「@Autowired 和 @Resource 到底是什么?」

一句话生动定义

@Autowired 和 @Resource 都是Spring框架里,帮你自动「找帮手、配员工」的依赖注入注解——不用你手动在代码里new对象,Spring会自动把你需要的工具/对象送过来,让代码更简洁、耦合度更低、更好维护。

贯穿全文的生活化类比(小学生都能懂)

我们用公司运营体系做统一类比,全文所有概念都会和这个类比一一对应,前后100%统一:

技术概念

生活化类比

核心作用

Spring IoC 容器

公司总部人力资源部(HR)

管理所有员工的档案、入职、调岗,手里有全公司所有员工的完整信息

Bean

公司里的正式员工

每个员工有唯一的工号(Bean名称)、固定岗位(Bean类型)、具体的工作能力(业务方法)

依赖注入

部门主管申请员工支援

你要干活需要帮手,不用自己去社会上招人(手动new对象),直接跟HR申请,HR自动把匹配的员工派到你的部门

@Autowired

公司内部的「岗位匹配申请单」

你跟HR说「我要一个Java开发岗的员工」,HR优先按**岗位(类型)**给你找人

@Resource

国家人社部标准的「人员匹配申请单」

你跟HR说「我要工号为javaDeveloper的员工」,HR优先按**工号/姓名(名称)**给你找人

专业严谨定义

  • @Autowired:Spring框架原生提供的依赖注入注解,属于Spring自有规范,核心基于byType(按类型) 实现Bean的匹配与注入,可配合Spring原生注解扩展能力,是Spring生态中最基础的依赖注入实现方式。

  • @Resource:JSR-250 Java官方规范定义的标准注解,不属于Spring原生体系,Spring框架对其做了兼容支持,核心基于byName(按名称) 实现Bean的匹配与注入,是Java EE/ Jakarta EE的通用规范,兼容性更强。

  • 两者的核心目标一致:实现IoC(控制反转) 设计思想,将对象的创建、生命周期管理、依赖关系维护,全部交给Spring容器处理,彻底解决代码硬编码带来的高耦合问题。

技术诞生背景/解决的核心痛点

  1. 传统开发的灾难:在Spring出现之前,Java开发中对象的依赖完全靠硬编码实现。比如UserController需要UserService,就要在代码里写private UserService userService = new UserServiceImpl();,一旦UserService的实现类改了,所有用到它的代码都要修改,耦合度极高,维护成本巨大,根本无法支撑大型项目开发。

  2. Spring的解决方案:Spring推出了IoC容器,把所有对象(Bean)都交给容器统一管理,开发者只需要声明「我需要什么对象」,容器自动注入,这就是依赖注入。而@Autowired就是Spring原生推出的、最核心的依赖注入注解,解决了硬编码耦合的核心痛点。

  3. @Resource的补充@Autowired默认只按类型匹配,在多实现类、跨框架兼容的场景有局限性。于是Java官方推出了JSR-250规范,定义了@Resource注解,优先按名称匹配,提供了更灵活、更通用的依赖注入方式,Spring也第一时间兼容了这个Java官方标准,两者成为Spring开发中最常用的两个依赖注入注解。


第一章:@Autowired 和 @Resource 核心特性:为什么要用它?

用表格形式清晰呈现两者的核心特性,每个特性都配套类比、专业解释和适用场景,避免纯概念堆砌。

核心特性名称

生活化类比

专业解释

适用场景

匹配规则优先级

@Autowired:先看「岗位」,找不到再看「工号」<br/>@Resource:先看「工号」,找不到再看「岗位」

@Autowired:默认byType(按类型) 匹配,类型匹配不到时,会 fallback 到 byName 匹配<br/>@Resource:默认byName(按名称) 匹配,名称匹配不到时,会 fallback 到 byType 匹配

@Autowired:Spring生态内部项目,绝大多数单实现类的常规注入场景<br/>@Resource:多实现类场景、跨框架兼容场景、需要明确指定Bean名称注入的场景

规范归属

@Autowired:公司内部规章制度,只在本公司生效<br/>@Resource:国家人社部统一标准,所有正规公司都要遵守

@Autowired:Spring 框架自有规范,仅在Spring/Spring Boot生态中生效<br/>@Resource:JSR-250 Java 官方标准规范,所有兼容Java EE规范的框架都支持,不绑定Spring

@Autowired:纯Spring生态项目,深度使用Spring原生能力<br/>@Resource:需要跨框架迁移的项目、兼容Java EE规范的企业级项目、不希望强绑定Spring的代码

空值容错能力

@Autowired:申请的岗位必须有人,没人就直接报错,不允许岗位空缺<br/>@Resource:申请的员工必须存在,不存在就直接报错,默认不允许空缺

@Autowired:默认required=true,要求注入的Bean必须存在,容器启动时找不到匹配的Bean就会直接抛出异常,启动失败<br/>@Resource:默认要求注入的Bean必须存在,找不到匹配的Bean直接抛出异常,无原生的required配置

@Autowired:核心业务依赖,不允许空值,需要启动时就校验依赖完整性<br/>@Resource:固定存在的基础依赖,明确Bean一定存在的常规场景

多实现类匹配能力

@Autowired:岗位有多个员工时,必须明确指定要哪个员工的工号,否则报错<br/>@Resource:直接通过工号就能精准找到对应员工,天然适配多员工场景

@Autowired:同一个类型有多个实现类时,仅靠类型无法唯一匹配,必须配合@Qualifier注解指定Bean名称,否则会抛出NoUniqueBeanDefinitionException异常<br/>@Resource:同一个类型有多个实现类时,直接通过name属性指定Bean名称即可精准匹配,无需额外注解,天然适配多实现类场景

@Autowired:单实现类为主,少量多实现类配合@Qualifier使用<br/>@Resource:大量多实现类的场景,需要频繁指定Bean名称注入

扩展能力

@Autowired:公司内部申请单,可以加很多附加条件(比如优先找正式工、临时工)<br/>@Resource:国家标准申请单,只有固定的几个填写项,不能自定义附加条件

@Autowired:可配合@Primary@Qualifier@Lazy等Spring原生注解,实现优先级指定、延迟注入、精准匹配等扩展能力,灵活性极强<br/>@Resource:仅支持nametypelookup等固定属性,无法配合Spring原生注解实现扩展能力,灵活性较弱

@Autowired:需要复杂注入逻辑、延迟加载、优先级配置的高级场景<br/>@Resource:简单、标准的注入场景,不需要复杂扩展能力

循环依赖兼容性

@Autowired:公司内部有成熟的「跨部门协作预案」,两个部门互相申请支援也能正常处理<br/>@Resource:国家标准没有针对公司内部循环协作做特殊优化,部分场景会出问题

@Autowired:Spring针对其做了循环依赖的特殊处理,在Setter注入、字段注入场景下,可完美解决循环依赖问题<br/>@Resource:Spring未对其做特殊的循环依赖优化,部分场景下(比如字段注入+代理对象)会出现循环依赖注入失败、空指针问题

@Autowired:存在循环依赖的业务场景,Spring Boot常规项目<br/>@Resource:无循环依赖的分层清晰的项目


第二章:@Autowired 和 @Resource 核心设计原理:底层是怎么工作的?

每个核心原理都先用「生活化类比」讲透逻辑,再讲专业的底层实现细节,拆解完整工作流程,细腻到每个执行环节,讲清「为什么这么设计」。

2.1 先搞懂前置核心:Spring IoC 容器的工作基础

生活化类比

公司开业前,HR会把所有招聘好的员工(Bean)都登记到花名册里,记录每个员工的工号(Bean名称)、岗位(Bean类型)、所属部门,所有员工都入职完成后,公司才正式开门营业。任何部门需要员工,HR都能立刻从花名册里找到匹配的人。

专业底层原理

Spring 容器启动时,会执行两个核心步骤:

  1. Bean的扫描与注册:扫描项目中所有加了@Controller@Service@Repository@Component的类,将其实例化为Bean,解析每个Bean的名称、类型、属性,注册到BeanDefinitionMap(Bean花名册)中。

  2. 依赖注入执行:所有Bean注册完成后,容器会遍历每个Bean,解析其中加了@Autowired@Resource的属性/方法,按照对应的匹配规则,从容器中找到匹配的Bean,注入到对应的属性中,完成依赖装配。

为什么这么设计?

先把所有Bean都注册到容器里,再统一执行依赖注入,确保注入时,所有需要的Bean都已经在容器中存在,避免出现「要找的员工还没入职」的情况。


2.2 @Autowired 底层工作原理与完整流程

生活化类比(岗位匹配申请全流程)

你是部门主管,填了公司内部的「岗位匹配申请单」(@Autowired),申请一个Java开发岗的员工,HR的处理流程是:

  1. 先看申请单上的必填岗位:Java开发岗,去花名册里找所有这个岗位的员工;

  2. 如果花名册里只有1个这个岗位的员工,直接把他派到你的部门;

  3. 如果花名册里有多个这个岗位的员工,就看你申请单上有没有写指定工号(@Qualifier),有就按工号找,没有就看有没有标「优先录用」(@Primary)的员工,都没有就直接报错;

  4. 如果花名册里完全没有这个岗位的员工,就看你申请单上有没有勾「允许岗位空缺」(required=false),没勾就直接拒绝申请,公司启动失败,勾了就先给你留个空位,后续再处理。

专业完整工作流程(一步一步拆解)

@Autowired 的注入逻辑由 Spring 原生的 AutowiredAnnotationBeanPostProcessor 处理器实现,完整执行流程如下:

  1. 注解解析:Spring 容器遍历Bean时,检测到属性/Setter方法上加了@Autowired注解,解析注解的required属性(默认true);

  2. 类型匹配:以注解标注的属性类型为依据,从BeanDefinitionMap中查询该类型的所有Bean实例,得到匹配的Bean列表;

  3. 唯一性判断

    1. 匹配到0个Bean:判断required属性,为true则抛出NoSuchBeanDefinitionException,容器启动失败;为false则注入null,不报错;

    2. 匹配到1个Bean:直接将该Bean实例注入到属性中,注入完成;

    3. 匹配到多个Bean:进入名称匹配与优先级判断流程;

  4. 多Bean场景的二次匹配

    1. 先检查是否加了@Qualifier注解,有则按注解指定的Bean名称精准匹配,匹配成功则注入,失败则抛出异常;

    2. @Qualifier则检查是否有Bean加了@Primary注解,有则注入该优先级最高的Bean;

    3. @Primary则回退到byName匹配:以属性名作为Bean名称,在多Bean列表中匹配,匹配成功则注入;

    4. 以上都匹配失败,抛出NoUniqueBeanDefinitionException,容器启动失败。

为什么这么设计?

@Autowired 作为Spring原生注解,核心设计理念是**「按类型契约编程」**,开发者只需要关注依赖的接口类型,不需要关注具体的实现类名称,符合面向接口编程的思想,同时提供了丰富的扩展能力适配复杂场景。


2.3 @Resource 底层工作原理与完整流程

生活化类比(人员匹配申请全流程)

你是部门主管,填了国家人社部标准的「人员匹配申请单」(@Resource),HR的处理流程是:

  1. 先看申请单上有没有写指定工号(name),写了就直接去花名册里找这个工号的员工,找到了就直接派过来,找不到就直接报错;

  2. 如果没写工号,就默认把申请单上填的「员工姓名」(属性名) 作为工号,去花名册里找,找到了就直接派过来;

  3. 如果按工号/姓名找不到,就 fallback 到看岗位(type),去花名册里找这个岗位的员工;

  4. 如果岗位匹配到多个员工,直接报错,不会再做额外的优先级判断;

  5. 从头到尾都找不到匹配的员工,直接拒绝申请,公司启动失败。

专业完整工作流程(一步一步拆解)

@Resource 的注入逻辑由 Spring 的 CommonAnnotationBeanPostProcessor 处理器实现,完全遵循JSR-250规范,完整执行流程如下:

  1. 注解解析:Spring 容器遍历Bean时,检测到属性/Setter方法上加了@Resource注解,优先解析注解的name属性(Bean名称),其次解析type属性(Bean类型);

  2. 名称优先匹配

    1. 如果手动指定了name属性,直接以该名称为依据,从容器中查询对应Bean,找到则注入,找不到直接抛出异常,不会进入后续类型匹配;

    2. 如果未手动指定name属性,默认以注解标注的属性名作为Bean名称,去容器中查询,找到则注入,找不到则进入类型匹配流程;

  3. 类型 fallback 匹配:按名称匹配失败后,以注解标注的属性类型为依据,从容器中查询该类型的所有Bean;

  4. 唯一性判断

    1. 匹配到0个Bean:直接抛出NoSuchBeanDefinitionException,容器启动失败;

    2. 匹配到1个Bean:直接将该Bean实例注入到属性中,注入完成;

    3. 匹配到多个Bean:直接抛出NoUniqueBeanDefinitionException,不会做任何额外的优先级/名称二次匹配,容器启动失败。

为什么这么设计?

@Resource 作为Java官方标准注解,核心设计理念是**「按名称唯一标识契约编程」**,每个Bean有唯一的名称,通过名称就能精准锁定目标Bean,规范统一,不绑定Spring框架,所有兼容Java EE的框架都能通用,同时避免了类型匹配带来的多实现类歧义问题。


第三章:核心术语扫盲:从0到1搞懂所有概念

用表格形式呈现,所有术语和前文的类比完全统一,按逻辑顺序排序,避免术语冲突。

术语名称

贯穿全文的生活化类比

专业定义

简单示例

IoC 容器

公司人力资源部(HR)

Spring的核心容器,负责Bean的创建、注册、生命周期管理、依赖注入,是所有Bean的管理者

Spring Boot项目启动时自动创建的ApplicationContext对象

Bean

公司的正式员工

由Spring IoC容器管理的Java对象,是Spring中最小的功能单元,有唯一的名称和固定的类型

加了@Service注解的UserServiceImpl类,会被Spring实例化为Bean

依赖注入(DI)

部门主管申请员工支援,HR自动派员工过来

IoC设计思想的核心实现方式,将对象之间的依赖关系交给容器管理,开发者无需手动new对象,只需要声明依赖

UserController中加@Autowired注解,自动注入UserService对象

byType(按类型匹配)

按岗位找员工

依赖注入的核心匹配规则之一,以属性的Java类型为依据,从容器中找相同类型的Bean注入

属性类型是UserService,就从容器中找所有UserService类型的Bean

byName(按名称匹配)

按工号/姓名找员工

依赖注入的核心匹配规则之一,以指定的Bean名称为依据,从容器中找唯一名称对应的Bean注入

属性名是userService,就从容器中找名称为userService的Bean

@Qualifier

申请单上指定的员工工号

Spring原生注解,配合@Autowired使用,在多实现类场景下,指定要注入的Bean名称,实现精准匹配

@Qualifier("userServiceImpl"),指定注入名称为userServiceImpl的Bean

@Primary

申请单上标注的「优先录用」员工

Spring原生注解,加在Bean上,当同一个类型有多个实现类时,标注了@Primary的Bean会被优先注入

UserServiceImpl上加@Primary,该类会作为UserService类型的优先注入Bean

Bean名称

员工的唯一工号

Bean在Spring容器中的唯一标识,默认是类名首字母小写,可通过注解的value属性手动指定

@Service("userService"),手动指定Bean名称为userService

Bean类型

员工的岗位

Bean的Java类型,通常是接口或类,是@Autowired默认的匹配依据

UserService接口,就是所有实现类的类型

实现类

岗位上的具体员工

实现了接口的具体Java类,会被Spring实例化为Bean,注入到对应的依赖中

UserServiceImpl实现了UserService接口,是该类型的一个实现类


第四章:核心中的核心:匹配规则与注入逻辑深度全解

这是整个技术最核心、最容易用错、决定代码是否能正常运行的知识点,我们做超细腻的深度拆解。

4.1 一句话本质

  • @Autowired 本质:Spring原生的「类型优先」注入注解,先按类型找,找不到/找多个再按名称找,支持丰富的Spring原生扩展,强绑定Spring生态。

  • @Resource 本质:Java官方的「名称优先」注入注解,先按名称找,找不到再按类型找,标准通用,兼容性强,不绑定Spring,扩展能力弱。

4.2 核心硬性规则(避坑红线,违反必出问题)

  1. @Autowired 硬性规则

    1. 必须有匹配的类型,默认required=true,无匹配类型直接启动失败;

    2. 同一个类型有多个实现类时,必须通过@Qualifier/@Primary/属性名匹配锁定唯一Bean,否则启动失败;

    3. 仅在Spring生态中生效,迁移到非Spring框架会完全失效;

    4. 字段注入、Setter注入、构造器注入都支持。

  2. @Resource 硬性规则

    1. 手动指定了name属性后,只会按这个名称匹配,匹配不到直接报错,不会 fallback 到类型匹配;

    2. 未指定name时,默认用属性名匹配,匹配不到才会按类型匹配;

    3. 同一个类型有多个实现类时,按类型匹配到多个Bean会直接报错,不会做任何二次匹配;

    4. 不支持@Primary@Qualifier等Spring原生注解,无法设置非必填;

    5. 仅支持字段注入、Setter注入,不支持构造器注入

4.3 分场景代码示例(可直接复制运行)

我们基于Spring Boot环境,用最常见的「用户服务」场景,做全场景代码演示,覆盖单实现类、多实现类两大核心场景。

前置准备:基础接口与实现类

先定义接口和两个实现类,用于后续注入演示:

// 1. 定义用户服务接口(岗位:用户服务岗)
public interface UserService {
    String getUserInfo(Long userId);
}

// 2. 第一个实现类:普通用户服务(员工1:工号 userNormalService)
@Service("userNormalService")
public class UserNormalServiceImpl implements UserService {
    @Override
    public String getUserInfo(Long userId) {
        return "普通用户信息:" + userId;
    }
}

// 3. 第二个实现类:管理员用户服务(员工2:工号 userAdminService)
@Service("userAdminService")
public class UserAdminServiceImpl implements UserService {
    @Override
    public String getUserInfo(Long userId) {
        return "管理员用户信息:" + userId;
    }
}

场景1:单实现类注入(最常用场景)

当接口只有一个实现类时,两者的使用几乎无区别,都能正常注入。

@RestController
@RequestMapping("/user")
public class UserController {

    // ✅ @Autowired 正确用法:单实现类,按类型匹配,直接注入
    @Autowired
    private UserService userService;

    // ✅ @Resource 正确用法:单实现类,默认按属性名userService匹配,
    // 匹配不到则按类型UserService匹配,正常注入
    @Resource
    private UserService userService;

    @GetMapping("/info")
    public String getUserInfo(Long userId) {
        return userService.getUserInfo(userId);
    }
}

⚠️ 注意:单实现类场景下,Spring默认的Bean名称是「类名首字母小写」,比如UserNormalServiceImpl的默认Bean名称是userNormalServiceImpl,如果属性名和Bean名称不一致,@Autowired依然能按类型匹配,@Resource也能fallback到类型匹配,都能正常注入。


场景2:多实现类注入(最容易踩坑的场景)

当接口有多个实现类时,两者的用法有本质区别,也是生产环境最高频的踩坑点。

① @Autowired 多实现类正确用法

必须配合@Qualifier指定Bean名称,或者用@Primary指定优先级,否则启动报错。

@RestController
@RequestMapping("/user")
public class UserController {

    // ✅ 正确用法1:配合@Qualifier指定Bean名称,精准匹配
    @Autowired
    @Qualifier("userNormalService") // 对应实现类@Service注解里指定的Bean名称
    private UserService userService;

    // ✅ 正确用法2:用属性名匹配Bean名称,和@Qualifier效果一致
    @Autowired
    private UserService userAdminService; // 属性名和Bean名称userAdminService完全一致

    // ✅ 正确用法3:给其中一个实现类加@Primary注解,优先注入该实现类
    // 给UserAdminServiceImpl加@Primary,这里会优先注入管理员实现类
    @Autowired
    private UserService userService;

    // ❌ 错误用法:多实现类场景,仅用@Autowired,无任何指定,启动报错
    // 报错:NoUniqueBeanDefinitionException: expected single matching bean but found 2
    @Autowired
    private UserService userService;
}
② @Resource 多实现类正确用法

优先通过name属性指定Bean名称,无需额外注解,天然适配多实现类场景。

@RestController
@RequestMapping("/user")
public class UserController {

    // ✅ 正确用法1:手动指定name属性,精准匹配Bean名称,推荐用法
    @Resource(name = "userNormalService") // 直接指定Bean名称,一步到位
    private UserService userService;

    // ✅ 正确用法2:用属性名匹配Bean名称,和指定name效果一致
    @Resource
    private UserService userAdminService; // 属性名和Bean名称完全一致

    // ❌ 错误用法1:多实现类场景,未指定name,属性名也不匹配Bean名称
    // 按属性名userService匹配不到Bean,按类型匹配到2个实现类,启动报错
    @Resource
    private UserService userService;

    // ❌ 错误用法2:指定的name不存在,直接启动报错,不会fallback到类型匹配
    // 报错:NoSuchBeanDefinitionException: No bean named 'userService' available
    @Resource(name = "userService")
    private UserService userService;
}

场景3:非必填注入(允许空值)

仅@Autowired支持非必填注入,@Resource无原生支持。

@RestController
@RequestMapping("/user")
public class UserController {

    // ✅ @Autowired 正确用法:required=false,允许Bean不存在,注入null,不会启动报错
    @Autowired(required = false)
    private UserService userService;

    // ❌ @Resource 错误用法:无required属性,不支持非必填注入,Bean不存在直接启动报错
    @Resource
    private UserService userService;

    // ✅ @Resource 非必填兼容方案:配合@Lazy延迟注入,仅运行时报错,不影响启动
    @Resource
    @Lazy
    private UserService userService;
}

第五章:实操全指南:从环境搭建到基础使用

保姆级实操步骤,从依赖引入开始,所有代码可直接复制运行,标注正确/错误用法,符合生产环境规范。

5.1 环境搭建与依赖引入

我们基于主流的Spring Boot 2.7.x/3.2.x版本,无需额外配置,开箱即用。

步骤1:创建Spring Boot项目

通过Spring Initializr创建项目,选择以下基础依赖:

  • Spring Web:提供Web接口能力

  • Lombok:简化代码,无需写getter/setter

步骤2:pom.xml 核心依赖

无需额外引入依赖,Spring Boot的spring-boot-starter-web已经内置了Spring核心包,原生支持@Autowired@Resource注解。

<?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>2.7.18</version> <!-- 稳定版,也可使用3.2.5 -->
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-inject-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-inject-demo</name>
    <description>@Autowired和@Resource注入演示项目</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- Spring Web核心依赖,内置Spring核心包,支持两个注解 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Lombok简化代码 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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>

步骤3:启动类

无需额外配置,Spring Boot启动类会自动扫描Bean和注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringInjectDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringInjectDemoApplication.class, args);
    }
}

5.2 生产级规范实操代码

按照MVC三层架构,编写符合生产规范的注入代码,标注正确用法和红线警告。

1. Dao层(数据访问层)

// 数据访问接口
public interface UserDao {
    User selectUserById(Long userId);
}

// 实现类,加@Repository注解,注册为Bean
@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public User selectUserById(Long userId) {
        // 模拟数据库查询
        User user = new User();
        user.setUserId(userId);
        user.setUsername("测试用户");
        user.setPhone("13800138000");
        return user;
    }
}

2. Service层(业务逻辑层)

// 业务接口
public interface UserService {
    User getUserById(Long userId);
}

// 实现类,加@Service注解,注册为Bean
@Service
public class UserServiceImpl implements UserService {

    // ✅ 生产级正确用法:构造器注入,Spring官方推荐
    // 优点:1. 强制依赖非空,启动时校验;2. 便于单元测试;3. 避免循环依赖;4. 符合不可变设计
    private final UserDao userDao;

    // 构造器注入,Spring 4.3+无需加@Autowired注解,自动注入
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public User getUserById(Long userId) {
        return userDao.selectUserById(userId);
    }
}

3. Controller层(接口层)

@RestController
@RequestMapping("/api/user")
public class UserController {

    // ✅ 生产级正确用法1:@Resource按名称注入,多实现类场景清晰明确
    @Resource(name = "userServiceImpl")
    private UserService userService;

    // ✅ 生产级正确用法2:@Autowired配合构造器注入,Spring官方推荐,单实现类首选
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    // 业务接口
    @GetMapping("/{userId}")
    public User getUserById(@PathVariable Long userId) {
        return userService.getUserById(userId);
    }

    // ❌ 生产环境禁止用法:字段注入+@Autowired,无强制非空校验,不利于单元测试,易引发空指针
    @Autowired
    private UserService userService;
}

5.3 启动与测试

  1. 启动Spring Boot项目,看到Started SpringInjectDemoApplication in X seconds说明启动成功,注入无异常;

  2. 浏览器/Postman访问 http://localhost:8080/api/user/1,返回用户信息,说明注入成功,代码正常运行。


第六章:@Autowired 和 @Resource 使用黄金法则

6.1 核心设计理念差异

维度

@Autowired 设计理念

@Resource 设计理念

核心导向

面向类型、面向Spring生态

面向名称、面向Java通用规范

设计目标

深度适配Spring生态,提供灵活的扩展能力,满足复杂业务场景

遵循Java官方标准,提供通用、统一的依赖注入规范,不绑定具体框架

核心优势

扩展能力强,支持构造器注入、非必填、优先级配置、循环依赖优化

标准统一,名称优先匹配,多实现类场景天然清晰,无框架绑定

6.2 生产级标准使用流程(一步一步规范落地)

  1. 第一步:优先选择注入方式

    1. 所有核心业务依赖,优先使用构造器注入,Spring官方推荐,天然支持非空校验、单元测试,避免循环依赖问题;

    2. 构造器注入无需加@Autowired@Resource,Spring 4.3+自动支持。

  2. 第二步:选择注入注解

    1. 单实现类场景、Spring生态内部项目、需要扩展能力(非必填、延迟注入):优先使用@Autowired

    2. 多实现类场景、需要明确指定Bean名称、跨框架兼容、不希望强绑定Spring:优先使用@Resource

    3. 禁止在同一个项目中混用两种注解,统一规范,避免维护混乱。

  3. 第三步:多实现类场景处理

    1. 使用@Autowired:配合@Qualifier明确指定Bean名称,禁止仅靠属性名匹配;

    2. 使用@Resource:明确指定name属性,禁止仅靠属性名匹配,提升代码可读性;

    3. 有明确的默认实现时,给默认实现加@Primary注解,避免歧义。

  4. 第四步:非空校验

    1. 核心业务依赖:默认required=true,启动时校验依赖完整性,避免运行时空指针;

    2. 非核心可选依赖:使用@Autowired(required=false),配合空值判断,避免启动失败。

6.3 绝对禁止红线(违反必出生产问题)

  1. 绝对禁止:字段注入为主,大量使用@Autowired/@Resource标注成员变量,不使用构造器注入;

    1. 后果:无法做强制非空校验,单元测试需要Mock依赖,易引发空指针,不利于代码维护。

  2. 绝对禁止:多实现类场景下,不指定Bean名称,仅靠属性名匹配注入;

    1. 后果:后续修改属性名、修改Bean名称时,会导致注入失败,引发线上bug,代码可读性极差。

  3. 绝对禁止:同一个项目中随意混用@Autowired@Resource,没有统一规范;

    1. 后果:团队开发维护混乱,不同开发者对注解的理解不一致,引发注入异常、启动失败等问题。

  4. 绝对禁止:给静态变量加@Autowired/@Resource注解;

    1. 后果:静态变量属于类,Spring注入是基于对象实例的,会导致注入失败,出现空指针异常。

  5. 绝对禁止:在循环依赖场景中使用@Resource字段注入;

    1. 后果:Spring未对@Resource做循环依赖优化,会导致注入失败、启动报错、代理对象不生效等问题。


第七章:最佳实践 VS 反面案例 全对比

所有案例均为生产环境真实高频踩坑点,按模块分类,用表格清晰呈现。

7.1 注入注解选型规范

对比项

反面案例(错误写法)

问题分析

最佳实践(正确写法)

单实现类注入

大量使用@Resource字段注入,无明确name属性

单实现类场景下,@Resource无任何优势,反而失去了@Autowired的扩展能力,且不支持构造器注入

单实现类优先使用构造器注入,无需加任何注解,Spring自动注入;必须用字段注入时,使用@Autowired

多实现类注入

@Autowired+属性名匹配,不指定@Qualifier

后续修改属性名或Bean名称时,会导致注入逻辑改变,甚至启动失败,代码可读性极差,其他开发者不知道注入的是哪个实现类

多实现类优先使用@Resource(name = "xxx"),明确指定Bean名称,代码清晰无歧义;用@Autowired时必须配合@Qualifier指定名称

可选依赖注入

@Resource注入可选依赖,Bean不存在时直接启动失败

@Resource无原生的非必填配置,可选依赖不存在时会直接导致项目启动失败,无法实现「可选依赖不影响核心功能」的需求

可选依赖必须使用@Autowired(required = false),配合代码中的空值判断,确保Bean不存在时项目也能正常启动运行

跨框架兼容场景

代码中大量使用@Autowired,强绑定Spring生态

后续项目需要迁移到其他兼容Java EE的框架时,所有注入代码都要修改,迁移成本极高

跨框架兼容、不希望强绑定Spring的项目,统一使用@Resource注解,遵循Java官方标准,迁移零成本

7.2 注入方式规范

对比项

反面案例(错误写法)

问题分析

最佳实践(正确写法)

注入方式选择

全项目90%以上都是字段注入,大量代码如下:<br/>@Autowired<br/>private UserService userService;

1. 无法强制非空校验,运行时易出现空指针;2. 单元测试需要手动Mock依赖,无法通过构造器传入;3. 易引发循环依赖问题;4. 不符合Java不可变设计规范

核心业务依赖100%使用构造器注入,代码如下:<br/>private final UserService userService;<br/>public UserController(UserService userService) {<br/> this.userService = userService;<br/>}<br/>优点:强制非空、便于测试、避免循环依赖、符合不可变设计

静态变量注入

给静态变量加注入注解:<br/>@Autowired<br/>private static UserService userService;

Spring注入是基于对象实例的,静态变量属于类级别,无法被注入,运行时会出现空指针异常,导致业务故障

绝对禁止给静态变量加注入注解,必须使用实例变量注入;特殊场景需要静态变量使用Bean,通过实现ApplicationContextAware接口手动获取

循环依赖场景

循环依赖场景使用@Resource字段注入:<br/>A类注入B类,B类注入A类,均用@Resource

Spring未对@Resource做循环依赖的特殊优化,会导致循环依赖注入失败,项目启动报错,或代理对象不生效,出现空指针

循环依赖场景优先重构代码,解除循环依赖;无法重构时,使用@Autowired字段注入,配合@Lazy延迟注入,Spring会自动处理循环依赖

延迟注入

直接给启动时不需要的依赖加@Autowired,启动时就实例化Bean

非核心、启动时不需要的依赖,启动时就实例化,会增加项目启动时间,且Bean不存在时会导致启动失败

非核心、懒加载的依赖,使用@Autowired+@Lazy注解,实现延迟注入,仅在使用时才实例化Bean,提升启动速度

7.3 多实现类注入规范

对比项

反面案例(错误写法)

问题分析

最佳实践(正确写法)

多Bean匹配

多实现类场景,仅用@Autowired,无任何指定:<br/>@Autowired<br/>private UserService userService;

同一个类型有多个实现类时,Spring无法确定唯一Bean,直接抛出NoUniqueBeanDefinitionException,项目启动失败

多实现类场景必须明确锁定唯一Bean:<br/>1. 用@Resource(name = "xxx")指定Bean名称;<br/>2. 用@Autowired+@Qualifier("xxx")指定名称;<br/>3. 给默认实现加@Primary注解

@Resource name属性

多实现类场景,不指定name属性,仅靠属性名匹配:<br/>@Resource<br/>private UserService userAdminService;

后续修改属性名时,会直接导致注入失败,启动报错;代码可读性差,其他开发者需要去对应实现类找Bean名称,维护成本高

多实现类使用@Resource时,必须手动指定name属性,代码如下:<br/>@Resource(name = "userAdminService")<br/>private UserService userService;<br/>代码清晰,修改属性名不影响注入逻辑,可读性极强

@Qualifier使用

@Qualifier加在类上,而非注入点:<br/>@Qualifier("userService")<br/>@Service<br/>public class UserServiceImpl {}

@Qualifier注解仅在注入点生效,加在Bean上不会生效,无法解决多Bean匹配问题,项目依然会启动失败

@Qualifier必须加在注入点,配合@Autowired使用,代码如下:<br/>@Autowired<br/>@Qualifier("userNormalService")<br/>private UserService userService;

7.4 代码规范与可维护性

对比项

反面案例(错误写法)

问题分析

最佳实践(正确写法)

注解混用

同一个项目、同一个类中,随意混用@Autowired@Resource,一会用这个,一会用那个

团队开发维护混乱,不同开发者对注解的理解不一致,容易出现注入逻辑错误;代码风格不统一,可读性极差,增加维护成本

同一个项目必须制定统一的注入规范,要么统一用@Autowired,要么统一用@Resource,禁止随意混用;仅在特殊场景下使用另一个注解,并加注释说明

冗余注解

构造器注入时,依然加@Autowired注解:<br/>@Autowired<br/>public UserController(UserService userService) {<br/> this.userService = userService;<br/>}

Spring 4.3+版本中,单构造器的情况下,无需加@Autowired注解,Spring会自动注入,加注解属于冗余代码,无任何意义

单构造器注入时,无需加任何注入注解,代码简洁规范;只有多构造器时,才需要给要注入的构造器加@Autowired注解

空值处理

@Autowired(required = false)注入的依赖,使用前不做空值判断

当Bean不存在时,注入的是null,使用时会直接抛出空指针异常,导致线上业务故障

使用required=false注入的依赖,使用前必须做空值判断,代码如下:<br/>if (userService != null) {<br/> userService.getUserInfo(userId);<br/>}


第八章:生产环境常见问题排查指南

针对生产环境最高频的5个问题,按「排查方法→根因分析→解决方案」的结构讲解,给出可直接执行的排查方法。

问题1:项目启动失败,报 NoSuchBeanDefinitionException

排查方法

  1. 查看异常堆栈,找到报错的注入属性和对应的类型;

  2. 执行mvn compile,确认目标实现类是否被正常编译;

  3. 检查实现类是否加了@Service/@Repository/@Component注解,是否被Spring扫描到;

  4. 检查启动类的@SpringBootApplication注解,是否扫描到了实现类所在的包;

  5. 检查@Resource是否指定了错误的name属性,@Autowired是否required=true但Bean不存在。

根因分析

高频根因按优先级排序:

  1. 实现类没有加Spring组件注解,没有被注册为Bean,容器中不存在该类型的Bean;

  2. 实现类所在的包不在Spring Boot的扫描路径下,没有被扫描注册;

  3. @Resource指定的name属性错误,容器中没有对应名称的Bean;

  4. 多模块项目中,实现类所在的模块没有被引入,类不存在;

  5. 使用了@Profile/@Conditional注解,Bean在当前环境下没有被注册。

解决方案

  1. 给实现类加上正确的Spring组件注解,确保被Spring扫描;

  2. 启动类增加scanBasePackages属性,指定扫描的包路径,确保覆盖实现类所在的包:@SpringBootApplication(scanBasePackages = "com.example")

  3. 修正@Resourcename属性,确保和Bean的名称完全一致;

  4. 在pom.xml中引入实现类所在的模块,确保类能被正常加载;

  5. 调整@Profile/@Conditional注解,确保Bean在当前环境下正常注册。

问题2:项目启动失败,报 NoUniqueBeanDefinitionException

排查方法

  1. 查看异常堆栈,找到报错的类型,确认该类型有多少个实现类;

  2. 查看所有实现类的@Service注解,确认Bean名称;

  3. 检查注入点的代码,是否指定了唯一的Bean名称或优先级。

根因分析

  1. 同一个接口有多个实现类,都被注册为Bean,@Autowired仅按类型匹配,无法确定唯一Bean;

  2. @Resource按名称匹配失败,fallback到类型匹配,匹配到多个Bean,无法确定唯一Bean;

  3. 意外引入了第三方包中的同类型Bean,导致容器中出现多个同类型Bean。

解决方案

  1. 注入点明确指定Bean名称:@Resource(name = "xxx")@Autowired+@Qualifier("xxx")

  2. 给默认的实现类加上@Primary注解,指定该Bean为同类型的优先注入Bean;

  3. 给不需要的实现类加上@Lazy/@Profile,避免在当前环境下注册;

  4. 排查第三方包中的同类型Bean,通过exclude排除不需要的自动配置。

问题3:注入的对象为null,运行时报空指针异常

排查方法

  1. 查看报错的代码行,确认注入的属性是否为null;

  2. 检查该类是否被Spring管理,是否加了@Controller/@Service等注解;

  3. 检查是否手动new了该类的对象,而非从Spring容器中获取;

  4. 检查是否给静态变量加了注入注解;

  5. 检查是否在构造器中使用了注入的字段属性。

根因分析

  1. 该类没有被Spring管理,是手动new出来的对象,Spring无法给手动new的对象注入依赖,导致属性为null;

  2. 给静态变量加了注入注解,Spring无法注入静态变量,属性为null;

  3. 在构造器中使用了字段注入的属性,构造器执行时,字段注入还未完成,属性为null;

  4. 循环依赖场景下,代理对象创建失败,注入的是未完成初始化的对象,属性为null;

  5. 使用了@Autowired(required = false),Bean不存在,注入了null,使用前未做空值判断。

解决方案

  1. 所有需要注入依赖的类,必须加Spring组件注解,交给Spring管理,禁止手动new对象;

  2. 绝对禁止给静态变量加注入注解,特殊场景通过ApplicationContext手动获取Bean;

  3. 字段注入的属性,禁止在构造器中使用,改为构造器注入;

  4. 循环依赖场景,重构代码解除循环,或使用@Lazy延迟注入;

  5. required=false注入的属性,使用前必须做空值判断。

问题4:循环依赖导致项目启动失败

排查方法

  1. 查看异常堆栈,找到循环依赖的两个类,确认注入关系;

  2. 检查注入方式,是构造器注入还是字段注入;

  3. 检查注入注解是@Autowired还是@Resource

根因分析

  1. 两个类互相通过构造器注入对方,Spring无法解决构造器循环依赖,直接启动失败;

  2. 两个类互相通过@Resource字段注入对方,Spring未对@Resource做循环依赖优化,导致注入失败;

  3. 循环依赖的类加了@Async/@Transactional注解,代理对象创建失败,循环依赖无法被解决。

解决方案

  1. 最优方案:重构代码,拆分业务逻辑,解除循环依赖,从根本上解决问题;

  2. 无法重构时,将构造器注入改为@Autowired字段注入,配合@Lazy注解,Spring会自动解决循环依赖;

  3. 禁止在循环依赖的场景中使用@Resource字段注入,改为@Autowired

  4. 给循环依赖的注入点加上@Lazy注解,延迟注入代理对象,解决代理导致的循环依赖问题。

问题5:@Resource 注解注入的对象,在AOP切面中不生效

排查方法

  1. 检查目标Bean是否被AOP代理,切面是否正常执行;

  2. 检查@Resource注入的属性,是否是原始对象,而非代理对象;

  3. 检查注入的Bean名称,是否和代理对象的名称一致。

根因分析

  1. @Resource是按名称匹配注入,当Bean被AOP代理后,代理对象的名称和原始Bean名称不一致,导致注入的是原始对象,而非代理对象,切面逻辑不生效;

  2. @Resource注入的时机早于AOP代理对象的创建,导致注入的是未被代理的原始对象。

解决方案

  1. 改为使用@Autowired按类型注入,Spring会自动注入代理后的对象,确保AOP切面正常生效;

  2. 必须使用@Resource时,明确指定代理对象的名称,确保注入的是代理后的对象;

  3. 调整AOP的配置,确保代理对象在注入前完成创建。


第九章:总结:@Autowired 和 @Resource 核心心法

核心心法口诀(8条)

  1. 类型优先Autowired,名称优先Resource:@Autowired先看类型,@Resource先看名称,核心差异记清楚;

  2. 构造注入是首选,字段注入要少用:Spring官方推荐构造器注入,强制非空、易测试、无循环依赖问题;

  3. 多实现类要明确,禁止模糊匹配:多实现类场景必须指定Bean名称,禁止靠属性名模糊匹配,避免启动失败;

  4. 核心依赖必须有,可选依赖要判空:核心依赖默认required=true,启动校验;可选依赖用required=false,使用前必须判空;

  5. 项目规范要统一,禁止注解乱混用:同一个项目统一用一种注解,禁止随意混用,避免维护混乱;

  6. 静态变量不注入,手动new不生效:Spring只给容器管理的对象注入依赖,静态变量、手动new的对象无法注入;

  7. 循环依赖要重构,Autowired更兼容:优先解除循环依赖,无法重构时用@Autowired,Spring有专门优化;

  8. Spring生态用Autowired,跨框架通用Resource:纯Spring项目用@Autowired,扩展能力强;跨框架、不绑定Spring用@Resource,标准通用。

适用场景与选型判断

优先选 @Autowired 的场景

优先选 @Resource 的场景

纯Spring/Spring Boot生态项目

需要跨框架迁移、兼容Java EE规范的项目

单实现类为主的常规注入场景

大量多实现类、需要频繁指定Bean名称的场景

需要非必填注入、延迟注入、优先级配置等扩展能力

不希望代码强绑定Spring框架的场景

存在循环依赖的场景

希望代码符合Java官方标准,通用性更强的场景

使用构造器注入的场景

字段注入为主,希望注入逻辑清晰明确的场景

不适用场景

  • 绝对禁止在手动new的对象中使用两个注解,无法实现注入;

  • 绝对禁止给静态变量使用两个注解,会导致注入失败;

  • 不推荐在核心业务代码中大量使用字段注入,优先使用构造器注入;

  • 不推荐在循环依赖场景中使用@Resource,会导致注入失败。


附录

附录1:@Autowired 和 @Resource 核心差异对照表

对比维度

@Autowired

@Resource

规范归属

Spring 框架自有规范

JSR-250 Java 官方标准规范

默认匹配规则

优先 byType(按类型)匹配

优先 byName(按名称)匹配

多实现类处理

配合@Qualifier/@Primary实现精准匹配

直接通过name属性指定Bean名称,无需额外注解

非必填支持

支持required=false,允许空值

无原生支持,不允许空值

注入方式支持

字段注入、Setter注入、构造器注入

字段注入、Setter注入,不支持构造器注入

扩展能力

支持@Primary、@Qualifier、@Lazy等Spring原生注解,扩展能力强

仅支持name、type等固定属性,扩展能力弱

循环依赖兼容性

Spring做了专门优化,兼容循环依赖场景

无特殊优化,循环依赖场景易注入失败

框架绑定

强绑定Spring框架,仅在Spring生态生效

无框架绑定,所有兼容Java EE的框架都支持

附录2:常用配套注解使用说明

注解名称

核心作用

使用场景

@Qualifier

配合@Autowired指定Bean名称,实现精准匹配

多实现类场景,锁定唯一要注入的Bean

@Primary

指定同类型Bean的优先注入优先级

多实现类场景,给默认实现加该注解,避免匹配歧义

@Lazy

实现延迟注入,使用时才实例化Bean

非核心依赖、启动时不需要的Bean,提升项目启动速度

@Service

将业务实现类注册为Spring Bean,Bean名称默认类名首字母小写

Service层实现类

@Repository

将数据访问类注册为Spring Bean

Dao层实现类

@Controller/@RestController

将接口类注册为Spring Bean

Controller层接口类

@Component

将通用类注册为Spring Bean

不属于三层架构的通用组件类

附录3:版本选型建议

Spring/Spring Boot 版本

兼容情况

推荐用法

Spring Boot 3.x / Spring Framework 6.x

完全兼容两个注解,JDK版本要求17+

优先使用构造器注入,单实现类无需加注解,多实现类优先用@Resource指定name

Spring Boot 2.7.x / Spring Framework 5.3.x

完全兼容两个注解,JDK版本要求8+

生产环境稳定版,推荐使用,两个注解均可正常使用

Spring Boot 2.0.x-2.6.x / Spring Framework 5.0.x-5.2.x

兼容两个注解,部分低版本存在循环依赖兼容问题

建议升级到2.7.x稳定版,避免已知bug

Spring Boot 1.x / Spring Framework 4.x

兼容两个注解,已停止维护

强烈建议升级,存在安全漏洞和兼容性问题

非Spring框架(如Jakarta EE)

不支持@Autowired,完全支持@Resource

统一使用@Resource注解,遵循Java官方标准

两块二每分钟