乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@Autowired 详解

定义与作用

@Autowired 是 Spring 框架提供的**按类型自动装配(Type-based Autowiring)**注解,是 Spring 依赖注入体系中最核心、最常用的注解。它解决了传统 Java 开发中对象之间手动创建依赖的痛点:当一个 Bean 需要另一个 Bean 协作时,开发者不再需要写 new Dependency() 或繁琐的工厂查找代码,而是由 Spring 容器在运行时自动将匹配的 Bean 实例注入到目标位置。

在飞翔科技的学生管理系统中,后端开发小崔编写的 StudentService 需要 StudentDao 来查询数据库,也需要 CourseService 来校验选课冲突。如果没有 @Autowired,小崔必须在 StudentService 内部手动构造这些依赖,导致代码紧耦合、难以测试。@Autowired 让容器接管了这项繁琐的工作。

适用位置与常用属性

属性类型默认值说明
requiredbooleantrue是否要求依赖必须存在。若为 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;
    }
}

运行结果分析:

  • 当 CacheManager Bean 存在时,cacheManager 被正常注入,报表生成后写入缓存
  • 当 CacheManager Bean 不存在时(如精简版部署),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 注入或字段注入。

上一页
@Bean 详解
下一页
@Qualifier 详解