文档版本:V1.0 | 适用版本:Spring Framework 5.x+/Spring Boot 2.x+/3.x | 面向人群:Java初学者、Spring开发工程师、需规范生产代码的研发人员
开篇:先搞懂「@Autowired 和 @Resource 到底是什么?」
一句话生动定义
@Autowired 和 @Resource 都是Spring框架里,帮你自动「找帮手、配员工」的依赖注入注解——不用你手动在代码里new对象,Spring会自动把你需要的工具/对象送过来,让代码更简洁、耦合度更低、更好维护。
贯穿全文的生活化类比(小学生都能懂)
我们用公司运营体系做统一类比,全文所有概念都会和这个类比一一对应,前后100%统一:
专业严谨定义
@Autowired:Spring框架原生提供的依赖注入注解,属于Spring自有规范,核心基于byType(按类型) 实现Bean的匹配与注入,可配合Spring原生注解扩展能力,是Spring生态中最基础的依赖注入实现方式。
@Resource:JSR-250 Java官方规范定义的标准注解,不属于Spring原生体系,Spring框架对其做了兼容支持,核心基于byName(按名称) 实现Bean的匹配与注入,是Java EE/ Jakarta EE的通用规范,兼容性更强。
两者的核心目标一致:实现IoC(控制反转) 设计思想,将对象的创建、生命周期管理、依赖关系维护,全部交给Spring容器处理,彻底解决代码硬编码带来的高耦合问题。
技术诞生背景/解决的核心痛点
传统开发的灾难:在Spring出现之前,Java开发中对象的依赖完全靠硬编码实现。比如
UserController需要UserService,就要在代码里写private UserService userService = new UserServiceImpl();,一旦UserService的实现类改了,所有用到它的代码都要修改,耦合度极高,维护成本巨大,根本无法支撑大型项目开发。Spring的解决方案:Spring推出了IoC容器,把所有对象(Bean)都交给容器统一管理,开发者只需要声明「我需要什么对象」,容器自动注入,这就是依赖注入。而
@Autowired就是Spring原生推出的、最核心的依赖注入注解,解决了硬编码耦合的核心痛点。@Resource的补充:
@Autowired默认只按类型匹配,在多实现类、跨框架兼容的场景有局限性。于是Java官方推出了JSR-250规范,定义了@Resource注解,优先按名称匹配,提供了更灵活、更通用的依赖注入方式,Spring也第一时间兼容了这个Java官方标准,两者成为Spring开发中最常用的两个依赖注入注解。
第一章:@Autowired 和 @Resource 核心特性:为什么要用它?
用表格形式清晰呈现两者的核心特性,每个特性都配套类比、专业解释和适用场景,避免纯概念堆砌。
第二章:@Autowired 和 @Resource 核心设计原理:底层是怎么工作的?
每个核心原理都先用「生活化类比」讲透逻辑,再讲专业的底层实现细节,拆解完整工作流程,细腻到每个执行环节,讲清「为什么这么设计」。
2.1 先搞懂前置核心:Spring IoC 容器的工作基础
生活化类比
公司开业前,HR会把所有招聘好的员工(Bean)都登记到花名册里,记录每个员工的工号(Bean名称)、岗位(Bean类型)、所属部门,所有员工都入职完成后,公司才正式开门营业。任何部门需要员工,HR都能立刻从花名册里找到匹配的人。
专业底层原理
Spring 容器启动时,会执行两个核心步骤:
Bean的扫描与注册:扫描项目中所有加了
@Controller、@Service、@Repository、@Component的类,将其实例化为Bean,解析每个Bean的名称、类型、属性,注册到BeanDefinitionMap(Bean花名册)中。依赖注入执行:所有Bean注册完成后,容器会遍历每个Bean,解析其中加了
@Autowired、@Resource的属性/方法,按照对应的匹配规则,从容器中找到匹配的Bean,注入到对应的属性中,完成依赖装配。
为什么这么设计?
先把所有Bean都注册到容器里,再统一执行依赖注入,确保注入时,所有需要的Bean都已经在容器中存在,避免出现「要找的员工还没入职」的情况。
2.2 @Autowired 底层工作原理与完整流程
生活化类比(岗位匹配申请全流程)
你是部门主管,填了公司内部的「岗位匹配申请单」(@Autowired),申请一个Java开发岗的员工,HR的处理流程是:
先看申请单上的必填岗位:Java开发岗,去花名册里找所有这个岗位的员工;
如果花名册里只有1个这个岗位的员工,直接把他派到你的部门;
如果花名册里有多个这个岗位的员工,就看你申请单上有没有写指定工号(@Qualifier),有就按工号找,没有就看有没有标「优先录用」(@Primary)的员工,都没有就直接报错;
如果花名册里完全没有这个岗位的员工,就看你申请单上有没有勾「允许岗位空缺」(required=false),没勾就直接拒绝申请,公司启动失败,勾了就先给你留个空位,后续再处理。
专业完整工作流程(一步一步拆解)
@Autowired 的注入逻辑由 Spring 原生的 AutowiredAnnotationBeanPostProcessor 处理器实现,完整执行流程如下:
注解解析:Spring 容器遍历Bean时,检测到属性/Setter方法上加了
@Autowired注解,解析注解的required属性(默认true);类型匹配:以注解标注的属性类型为依据,从
BeanDefinitionMap中查询该类型的所有Bean实例,得到匹配的Bean列表;唯一性判断:
匹配到0个Bean:判断
required属性,为true则抛出NoSuchBeanDefinitionException,容器启动失败;为false则注入null,不报错;匹配到1个Bean:直接将该Bean实例注入到属性中,注入完成;
匹配到多个Bean:进入名称匹配与优先级判断流程;
多Bean场景的二次匹配:
先检查是否加了
@Qualifier注解,有则按注解指定的Bean名称精准匹配,匹配成功则注入,失败则抛出异常;无
@Qualifier则检查是否有Bean加了@Primary注解,有则注入该优先级最高的Bean;无
@Primary则回退到byName匹配:以属性名作为Bean名称,在多Bean列表中匹配,匹配成功则注入;以上都匹配失败,抛出
NoUniqueBeanDefinitionException,容器启动失败。
为什么这么设计?
@Autowired 作为Spring原生注解,核心设计理念是**「按类型契约编程」**,开发者只需要关注依赖的接口类型,不需要关注具体的实现类名称,符合面向接口编程的思想,同时提供了丰富的扩展能力适配复杂场景。
2.3 @Resource 底层工作原理与完整流程
生活化类比(人员匹配申请全流程)
你是部门主管,填了国家人社部标准的「人员匹配申请单」(@Resource),HR的处理流程是:
先看申请单上有没有写指定工号(name),写了就直接去花名册里找这个工号的员工,找到了就直接派过来,找不到就直接报错;
如果没写工号,就默认把申请单上填的「员工姓名」(属性名) 作为工号,去花名册里找,找到了就直接派过来;
如果按工号/姓名找不到,就 fallback 到看岗位(type),去花名册里找这个岗位的员工;
如果岗位匹配到多个员工,直接报错,不会再做额外的优先级判断;
从头到尾都找不到匹配的员工,直接拒绝申请,公司启动失败。
专业完整工作流程(一步一步拆解)
@Resource 的注入逻辑由 Spring 的 CommonAnnotationBeanPostProcessor 处理器实现,完全遵循JSR-250规范,完整执行流程如下:
注解解析:Spring 容器遍历Bean时,检测到属性/Setter方法上加了
@Resource注解,优先解析注解的name属性(Bean名称),其次解析type属性(Bean类型);名称优先匹配:
如果手动指定了
name属性,直接以该名称为依据,从容器中查询对应Bean,找到则注入,找不到直接抛出异常,不会进入后续类型匹配;如果未手动指定
name属性,默认以注解标注的属性名作为Bean名称,去容器中查询,找到则注入,找不到则进入类型匹配流程;
类型 fallback 匹配:按名称匹配失败后,以注解标注的属性类型为依据,从容器中查询该类型的所有Bean;
唯一性判断:
匹配到0个Bean:直接抛出
NoSuchBeanDefinitionException,容器启动失败;匹配到1个Bean:直接将该Bean实例注入到属性中,注入完成;
匹配到多个Bean:直接抛出
NoUniqueBeanDefinitionException,不会做任何额外的优先级/名称二次匹配,容器启动失败。
为什么这么设计?
@Resource 作为Java官方标准注解,核心设计理念是**「按名称唯一标识契约编程」**,每个Bean有唯一的名称,通过名称就能精准锁定目标Bean,规范统一,不绑定Spring框架,所有兼容Java EE的框架都能通用,同时避免了类型匹配带来的多实现类歧义问题。
第三章:核心术语扫盲:从0到1搞懂所有概念
用表格形式呈现,所有术语和前文的类比完全统一,按逻辑顺序排序,避免术语冲突。
第四章:核心中的核心:匹配规则与注入逻辑深度全解
这是整个技术最核心、最容易用错、决定代码是否能正常运行的知识点,我们做超细腻的深度拆解。
4.1 一句话本质
@Autowired 本质:Spring原生的「类型优先」注入注解,先按类型找,找不到/找多个再按名称找,支持丰富的Spring原生扩展,强绑定Spring生态。
@Resource 本质:Java官方的「名称优先」注入注解,先按名称找,找不到再按类型找,标准通用,兼容性强,不绑定Spring,扩展能力弱。
4.2 核心硬性规则(避坑红线,违反必出问题)
@Autowired 硬性规则:
必须有匹配的类型,默认
required=true,无匹配类型直接启动失败;同一个类型有多个实现类时,必须通过
@Qualifier/@Primary/属性名匹配锁定唯一Bean,否则启动失败;仅在Spring生态中生效,迁移到非Spring框架会完全失效;
字段注入、Setter注入、构造器注入都支持。
@Resource 硬性规则:
手动指定了
name属性后,只会按这个名称匹配,匹配不到直接报错,不会 fallback 到类型匹配;未指定
name时,默认用属性名匹配,匹配不到才会按类型匹配;同一个类型有多个实现类时,按类型匹配到多个Bean会直接报错,不会做任何二次匹配;
不支持
@Primary、@Qualifier等Spring原生注解,无法设置非必填;仅支持字段注入、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 启动与测试
启动Spring Boot项目,看到
Started SpringInjectDemoApplication in X seconds说明启动成功,注入无异常;浏览器/Postman访问
http://localhost:8080/api/user/1,返回用户信息,说明注入成功,代码正常运行。
第六章:@Autowired 和 @Resource 使用黄金法则
6.1 核心设计理念差异
6.2 生产级标准使用流程(一步一步规范落地)
第一步:优先选择注入方式
所有核心业务依赖,优先使用构造器注入,Spring官方推荐,天然支持非空校验、单元测试,避免循环依赖问题;
构造器注入无需加
@Autowired或@Resource,Spring 4.3+自动支持。
第二步:选择注入注解
单实现类场景、Spring生态内部项目、需要扩展能力(非必填、延迟注入):优先使用
@Autowired;多实现类场景、需要明确指定Bean名称、跨框架兼容、不希望强绑定Spring:优先使用
@Resource;禁止在同一个项目中混用两种注解,统一规范,避免维护混乱。
第三步:多实现类场景处理
使用
@Autowired:配合@Qualifier明确指定Bean名称,禁止仅靠属性名匹配;使用
@Resource:明确指定name属性,禁止仅靠属性名匹配,提升代码可读性;有明确的默认实现时,给默认实现加
@Primary注解,避免歧义。
第四步:非空校验
核心业务依赖:默认
required=true,启动时校验依赖完整性,避免运行时空指针;非核心可选依赖:使用
@Autowired(required=false),配合空值判断,避免启动失败。
6.3 绝对禁止红线(违反必出生产问题)
❌ 绝对禁止:字段注入为主,大量使用
@Autowired/@Resource标注成员变量,不使用构造器注入;后果:无法做强制非空校验,单元测试需要Mock依赖,易引发空指针,不利于代码维护。
❌ 绝对禁止:多实现类场景下,不指定Bean名称,仅靠属性名匹配注入;
后果:后续修改属性名、修改Bean名称时,会导致注入失败,引发线上bug,代码可读性极差。
❌ 绝对禁止:同一个项目中随意混用
@Autowired和@Resource,没有统一规范;后果:团队开发维护混乱,不同开发者对注解的理解不一致,引发注入异常、启动失败等问题。
❌ 绝对禁止:给静态变量加
@Autowired/@Resource注解;后果:静态变量属于类,Spring注入是基于对象实例的,会导致注入失败,出现空指针异常。
❌ 绝对禁止:在循环依赖场景中使用
@Resource字段注入;后果:Spring未对
@Resource做循环依赖优化,会导致注入失败、启动报错、代理对象不生效等问题。
第七章:最佳实践 VS 反面案例 全对比
所有案例均为生产环境真实高频踩坑点,按模块分类,用表格清晰呈现。
7.1 注入注解选型规范
7.2 注入方式规范
7.3 多实现类注入规范
7.4 代码规范与可维护性
第八章:生产环境常见问题排查指南
针对生产环境最高频的5个问题,按「排查方法→根因分析→解决方案」的结构讲解,给出可直接执行的排查方法。
问题1:项目启动失败,报 NoSuchBeanDefinitionException
排查方法
查看异常堆栈,找到报错的注入属性和对应的类型;
执行
mvn compile,确认目标实现类是否被正常编译;检查实现类是否加了
@Service/@Repository/@Component注解,是否被Spring扫描到;检查启动类的
@SpringBootApplication注解,是否扫描到了实现类所在的包;检查
@Resource是否指定了错误的name属性,@Autowired是否required=true但Bean不存在。
根因分析
高频根因按优先级排序:
实现类没有加Spring组件注解,没有被注册为Bean,容器中不存在该类型的Bean;
实现类所在的包不在Spring Boot的扫描路径下,没有被扫描注册;
@Resource指定的name属性错误,容器中没有对应名称的Bean;多模块项目中,实现类所在的模块没有被引入,类不存在;
使用了
@Profile/@Conditional注解,Bean在当前环境下没有被注册。
解决方案
给实现类加上正确的Spring组件注解,确保被Spring扫描;
启动类增加
scanBasePackages属性,指定扫描的包路径,确保覆盖实现类所在的包:@SpringBootApplication(scanBasePackages = "com.example");修正
@Resource的name属性,确保和Bean的名称完全一致;在pom.xml中引入实现类所在的模块,确保类能被正常加载;
调整
@Profile/@Conditional注解,确保Bean在当前环境下正常注册。
问题2:项目启动失败,报 NoUniqueBeanDefinitionException
排查方法
查看异常堆栈,找到报错的类型,确认该类型有多少个实现类;
查看所有实现类的
@Service注解,确认Bean名称;检查注入点的代码,是否指定了唯一的Bean名称或优先级。
根因分析
同一个接口有多个实现类,都被注册为Bean,
@Autowired仅按类型匹配,无法确定唯一Bean;@Resource按名称匹配失败,fallback到类型匹配,匹配到多个Bean,无法确定唯一Bean;意外引入了第三方包中的同类型Bean,导致容器中出现多个同类型Bean。
解决方案
注入点明确指定Bean名称:
@Resource(name = "xxx")或@Autowired+@Qualifier("xxx");给默认的实现类加上
@Primary注解,指定该Bean为同类型的优先注入Bean;给不需要的实现类加上
@Lazy/@Profile,避免在当前环境下注册;排查第三方包中的同类型Bean,通过
exclude排除不需要的自动配置。
问题3:注入的对象为null,运行时报空指针异常
排查方法
查看报错的代码行,确认注入的属性是否为null;
检查该类是否被Spring管理,是否加了
@Controller/@Service等注解;检查是否手动new了该类的对象,而非从Spring容器中获取;
检查是否给静态变量加了注入注解;
检查是否在构造器中使用了注入的字段属性。
根因分析
该类没有被Spring管理,是手动new出来的对象,Spring无法给手动new的对象注入依赖,导致属性为null;
给静态变量加了注入注解,Spring无法注入静态变量,属性为null;
在构造器中使用了字段注入的属性,构造器执行时,字段注入还未完成,属性为null;
循环依赖场景下,代理对象创建失败,注入的是未完成初始化的对象,属性为null;
使用了
@Autowired(required = false),Bean不存在,注入了null,使用前未做空值判断。
解决方案
所有需要注入依赖的类,必须加Spring组件注解,交给Spring管理,禁止手动new对象;
绝对禁止给静态变量加注入注解,特殊场景通过
ApplicationContext手动获取Bean;字段注入的属性,禁止在构造器中使用,改为构造器注入;
循环依赖场景,重构代码解除循环,或使用
@Lazy延迟注入;required=false注入的属性,使用前必须做空值判断。
问题4:循环依赖导致项目启动失败
排查方法
查看异常堆栈,找到循环依赖的两个类,确认注入关系;
检查注入方式,是构造器注入还是字段注入;
检查注入注解是
@Autowired还是@Resource。
根因分析
两个类互相通过构造器注入对方,Spring无法解决构造器循环依赖,直接启动失败;
两个类互相通过
@Resource字段注入对方,Spring未对@Resource做循环依赖优化,导致注入失败;循环依赖的类加了
@Async/@Transactional注解,代理对象创建失败,循环依赖无法被解决。
解决方案
最优方案:重构代码,拆分业务逻辑,解除循环依赖,从根本上解决问题;
无法重构时,将构造器注入改为
@Autowired字段注入,配合@Lazy注解,Spring会自动解决循环依赖;禁止在循环依赖的场景中使用
@Resource字段注入,改为@Autowired;给循环依赖的注入点加上
@Lazy注解,延迟注入代理对象,解决代理导致的循环依赖问题。
问题5:@Resource 注解注入的对象,在AOP切面中不生效
排查方法
检查目标Bean是否被AOP代理,切面是否正常执行;
检查
@Resource注入的属性,是否是原始对象,而非代理对象;检查注入的Bean名称,是否和代理对象的名称一致。
根因分析
@Resource是按名称匹配注入,当Bean被AOP代理后,代理对象的名称和原始Bean名称不一致,导致注入的是原始对象,而非代理对象,切面逻辑不生效;@Resource注入的时机早于AOP代理对象的创建,导致注入的是未被代理的原始对象。
解决方案
改为使用
@Autowired按类型注入,Spring会自动注入代理后的对象,确保AOP切面正常生效;必须使用
@Resource时,明确指定代理对象的名称,确保注入的是代理后的对象;调整AOP的配置,确保代理对象在注入前完成创建。
第九章:总结:@Autowired 和 @Resource 核心心法
核心心法口诀(8条)
类型优先Autowired,名称优先Resource:@Autowired先看类型,@Resource先看名称,核心差异记清楚;
构造注入是首选,字段注入要少用:Spring官方推荐构造器注入,强制非空、易测试、无循环依赖问题;
多实现类要明确,禁止模糊匹配:多实现类场景必须指定Bean名称,禁止靠属性名模糊匹配,避免启动失败;
核心依赖必须有,可选依赖要判空:核心依赖默认required=true,启动校验;可选依赖用required=false,使用前必须判空;
项目规范要统一,禁止注解乱混用:同一个项目统一用一种注解,禁止随意混用,避免维护混乱;
静态变量不注入,手动new不生效:Spring只给容器管理的对象注入依赖,静态变量、手动new的对象无法注入;
循环依赖要重构,Autowired更兼容:优先解除循环依赖,无法重构时用@Autowired,Spring有专门优化;
Spring生态用Autowired,跨框架通用Resource:纯Spring项目用@Autowired,扩展能力强;跨框架、不绑定Spring用@Resource,标准通用。
适用场景与选型判断
不适用场景
绝对禁止在手动new的对象中使用两个注解,无法实现注入;
绝对禁止给静态变量使用两个注解,会导致注入失败;
不推荐在核心业务代码中大量使用字段注入,优先使用构造器注入;
不推荐在循环依赖场景中使用@Resource,会导致注入失败。