@Autowired 详解
定义与作用
@Autowired 是 Spring 框架提供的**按类型自动装配(Type-based Autowiring)**注解,是 Spring 依赖注入体系中最核心、最常用的注解。它解决了传统 Java 开发中对象之间手动创建依赖的痛点:当一个 Bean 需要另一个 Bean 协作时,开发者不再需要写 new Dependency() 或繁琐的工厂查找代码,而是由 Spring 容器在运行时自动将匹配的 Bean 实例注入到目标位置。
在飞翔科技的学生管理系统中,后端开发小崔编写的 StudentService 需要 StudentDao 来查询数据库,也需要 CourseService 来校验选课冲突。如果没有 @Autowired,小崔必须在 StudentService 内部手动构造这些依赖,导致代码紧耦合、难以测试。@Autowired 让容器接管了这项繁琐的工作。
适用位置与常用属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
required | boolean | true | 是否要求依赖必须存在。若为 true 且容器找不到匹配 Bean,则抛出 NoSuchBeanDefinitionException |
适用位置:
- 构造器:Spring 官方推荐的方式,确保依赖不可变
- Setter 方法:适用于可选依赖
- 字段:代码最简洁,但不利于测试和不可变设计
核心原理
Spring 容器在创建 Bean 实例后,进入属性填充(Population)阶段。AutowiredAnnotationBeanPostProcessor 会扫描目标 Bean 中所有标注了 @Autowired 的位置,根据类型去 BeanFactory 中查找匹配的候选 Bean。如果找到唯一匹配,则通过反射注入;如果找到多个,则进入歧义解决流程(尝试 @Primary、@Qualifier 或字段名匹配)。
注入方式对比与完整示例
场景简述
飞翔科技的学生管理系统中,EnrollmentService(选课服务)依赖 StudentDao(查询学生信息)、CourseDao(查询课程余量)和 NotificationService(发送选课结果通知,可选依赖)。小崔分别用三种注入方式实现,供团队评审。
方式一:构造器注入(Constructor Injection)
Spring 官方推荐。依赖通过构造方法参数传入,对象创建后依赖不可变(可声明为 final),且能保证对象在构造完成时处于完整状态。
package com.feixiang.student.service;
import com.feixiang.student.dao.CourseDao;
import com.feixiang.student.dao.StudentDao;
import com.feixiang.student.entity.EnrollmentResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class EnrollmentService {
private final StudentDao studentDao;
private final CourseDao courseDao;
// Spring 4.3+ 单构造器可省略 @Autowired
// 但为了可读性和显式表达,团队规范建议保留
@Autowired
public EnrollmentService(StudentDao studentDao, CourseDao courseDao) {
this.studentDao = studentDao;
this.courseDao = courseDao;
}
public EnrollmentResult enroll(Long studentId, Long courseId) {
// 使用注入的依赖完成业务逻辑
var student = studentDao.findById(studentId);
var course = courseDao.findById(courseId);
// ... 选课逻辑
return new EnrollmentResult(student.getName(), course.getName(), true);
}
}
操作前(无依赖注入):
public class EnrollmentService {
private StudentDao studentDao = new StudentDao(); // 硬编码,无法替换为 Mock
private CourseDao courseDao = new CourseDao();
// 单元测试时无法注入测试替身,必须连真实数据库
}
操作后(构造器注入):
- 单元测试可以直接
new EnrollmentService(mockStudentDao, mockCourseDao),无需 Spring 容器 studentDao和courseDao声明为final,运行期不可被篡改,线程安全- 如果
StudentDao或CourseDao未在容器中定义,应用启动时立即报错,快速失败
方式二:Setter 注入(Setter Injection)
适用于可选依赖(Optional Dependencies):依赖可以在对象创建后再注入,也可以不注入。
package com.feixiang.student.service;
import com.feixiang.student.dao.CourseDao;
import com.feixiang.student.dao.StudentDao;
import com.feixiang.student.service.NotificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class EnrollmentService {
private final StudentDao studentDao;
private final CourseDao courseDao;
private NotificationService notificationService; // 可选,非 final
@Autowired
public EnrollmentService(StudentDao studentDao, CourseDao courseDao) {
this.studentDao = studentDao;
this.courseDao = courseDao;
}
// Setter 注入可选依赖
@Autowired(required = false)
public void setNotificationService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void notifyStudent(Long studentId, String message) {
if (notificationService != null) {
notificationService.send(studentId, message);
}
}
}
关键细节:@Autowired(required = false) 表示如果容器中没有 NotificationService,不会报错,notificationService 保持为 null。这在某些模块未启用时非常有用(例如开发环境不接入短信网关)。
方式三:字段注入(Field Injection)
直接在字段上标注 @Autowired,无需构造器或 Setter。
@Service
public class EnrollmentService {
@Autowired
private StudentDao studentDao;
@Autowired
private CourseDao courseDao;
@Autowired(required = false)
private NotificationService notificationService;
}
为什么不推荐字段注入?
| 维度 | 构造器注入 | 字段注入 |
|---|---|---|
| 不可变性 | 可声明 final | 无法声明 final |
| 单元测试 | 直接 new 传入 Mock | 必须依赖 Spring 上下文或反射工具 |
| 依赖可见性 | 构造器参数一目了然 | 依赖隐藏在字段中,类签名不体现 |
| NPE 风险 | 构造完成即可安全使用 | 若忘记 @Autowired,运行期才暴露 NullPointerException |
Spring 官方从 4.x 开始明确推荐构造器注入。Spring Boot 的
spring-boot-starter甚至会在检测到字段注入时发出警告日志。
Spring 4.3+ 单构造器省略规则
从 Spring Framework 4.3 开始,如果目标类只有一个构造方法(且不是默认无参构造器),可以省略 @Autowired 注解,Spring 仍会自动按类型注入。
@Service
public class EnrollmentService {
private final StudentDao studentDao;
private final CourseDao courseDao;
// Spring 4.3+:单构造器,@Autowired 可省略
public EnrollmentService(StudentDao studentDao, CourseDao courseDao) {
this.studentDao = studentDao;
this.courseDao = courseDao;
}
}
注意:如果类有多个构造器,则必须在希望 Spring 使用的那个构造器上显式标注 @Autowired,否则 Spring 会选择默认无参构造器,导致依赖未被注入。
@Autowired(required = false) 详解
当依赖是可选的,或者存在多个候选但可能某些环境下不存在时,使用 required = false。
@Service
public class ReportService {
private final StudentDao studentDao;
private CacheManager cacheManager; // 可能未引入缓存模块
@Autowired
public ReportService(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Autowired(required = false)
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public byte[] generateReport() {
byte[] data = studentDao.exportAll();
if (cacheManager != null) {
cacheManager.put("report:all", data);
}
return data;
}
}
运行结果分析:
- 当
CacheManagerBean 存在时,cacheManager被正常注入,报表生成后写入缓存 - 当
CacheManagerBean 不存在时(如精简版部署),cacheManager为null,generateReport()跳过缓存逻辑,服务依然可用
歧义注入与解决
当容器中存在多个同类型的 Bean 时,@Autowired 单独使用会抛出 NoUniqueBeanDefinitionException。
// 容器中有两个 StudentDao 的实现
@Repository
public class JdbcStudentDao implements StudentDao { }
@Repository
public class JpaStudentDao implements StudentDao { }
@Service
public class EnrollmentService {
@Autowired
private StudentDao studentDao; // 报错:找到 2 个候选 Bean
}
解决方式:
- 配合
@Qualifier("jdbcStudentDao")精确指定 Bean 名称(详见 Qualifier.md) - 在其中一个实现上标注
@Primary(详见 Primary.md) - 让字段名与某个 Bean 的名称一致(不推荐,属于隐式约定,易出错)
易错场景与面试考点
易错场景一:循环依赖与构造器注入
@Service
public class AService {
private final BService bService;
@Autowired
public AService(BService bService) { this.bService = bService; }
}
@Service
public class BService {
private final AService aService;
@Autowired
public BService(AService aService) { this.aService = aService; }
}
后果:应用启动时抛出 BeanCurrentlyInCreationException,因为构造器注入要求两个对象在构造完成前就互相持有对方,而对象尚未实例化。
解决方案:
- 重构代码,打破循环(引入中间层或事件机制)
- 将其中一个改为 Setter 注入或字段注入(Spring 通过三级缓存可解决非构造器注入的循环依赖)
- 使用
@Lazy延迟注入(详见 Lazy.md)
易错场景二:多个构造器未标注 @Autowired
@Service
public class StudentService {
private StudentDao studentDao;
public StudentService() {} // 默认无参构造器
public StudentService(StudentDao studentDao) {
this.studentDao = studentDao;
}
}
后果:Spring 默认选择无参构造器,studentDao 永远为 null,运行期调用时抛出 NullPointerException。
正确做法:在需要注入的构造器上显式标注 @Autowired:
@Autowired
public StudentService(StudentDao studentDao) {
this.studentDao = studentDao;
}
面试考点
Q:@Autowired 默认按类型还是按名称装配?
默认**按类型(byType)**装配。如果找到多个同类型候选,会尝试按字段名/参数名作为 Bean 名称进行二次匹配;若仍无法确定,则抛出
NoUniqueBeanDefinitionException,需要配合@Qualifier或@Primary。
Q:@Autowired 和 @Resource 有什么区别?
@Autowired是 Spring 专有注解,按类型装配;@Resource是 JSR-250 标准注解,默认按名称装配,找不到再按类型。@Resource不能用于构造器参数,@Autowired可以。
Q:Spring 为什么推荐构造器注入?
① 依赖可声明为
final,保证不可变性;② 对象构造完成即处于完整状态,不会出现"部分构造"的对象;③ 不依赖 Spring 容器即可进行单元测试,直接传入 Mock 对象;④ 必填依赖缺失时在启动期就失败,而非运行期才暴露 NPE。
Q:Spring 4.3+ 省略 @Autowired 的规则是什么?
当类只有一个构造方法时(且该构造方法不是默认无参构造器),Spring 会自动将其视为自动装配构造器,无需显式写
@Autowired。但如果存在多个构造器,则必须显式标注,否则 Spring 选择无参构造器。
Q:@Autowired(required = false) 在构造器上能用吗?
可以,但语义危险。如果构造器上标注
@Autowired(required = false)且某个参数在容器中不存在,Spring 会尝试用null填充该参数。如果参数是基本类型(如int),则会报错,因为基本类型不能为null。因此required = false更推荐用于 Setter 注入或字段注入。