SpringIoC&DI
- [Spring IoC & DI 概述](#Spring IoC & DI 概述)
-
- [什么是 IoC(控制反转)](#什么是 IoC(控制反转))
- [什么是 DI(依赖注入)](#什么是 DI(依赖注入))
- [IoC 与 DI 的关系](#IoC 与 DI 的关系)
- [Spring IoC 容器的工作流程](#Spring IoC 容器的工作流程)
- [什么是 Bean?](#什么是 Bean?)
- [创建 Bean 的方式](#创建 Bean 的方式)
-
- 类注解
- 方法注解
-
- 方法注解要配合类注解使用
- 定义多个对象
- [重命名 Bean](#重命名 Bean)
- 导入配置类:@Import
- [FactoryBean 接口](#FactoryBean 接口)
- [Bean 对象的获取](#Bean 对象的获取)
-
- [通过 ApplicationContext 获取](#通过 ApplicationContext 获取)
-
- [Bean 的名称规则](#Bean 的名称规则)
- [获取不同方式创建的 Bean](#获取不同方式创建的 Bean)
- 通过依赖注入获取⭐
-
- [@Autowired 详解](#@Autowired 详解)
- [@Resource 详解](#@Resource 详解)
- [@Autowired 与 @Resource 对比⭐](#@Autowired 与 @Resource 对比⭐)
- [获取不同方式创建的 Bean](#获取不同方式创建的 Bean)
- [1. 属性注入(Field Injection)](#1. 属性注入(Field Injection))
- [2. 构造方法注入(Constructor Injection)](#2. 构造方法注入(Constructor Injection))
- [3. Setter方法注入(Setter Injection)](#3. Setter方法注入(Setter Injection))
- 三种注入方式对比
- [通过 ApplicationContextAware 获取](#通过 ApplicationContextAware 获取)
- 扫描路径
-
-
- 默认扫描范围
- 不在默认扫描范围的情况
- 自定义扫描路径
-
- 方式一:@ComponentScan
- [方式二:@SpringBootApplication 的 scanBasePackages 属性](#方式二:@SpringBootApplication 的 scanBasePackages 属性)
- [方式三:使用 AnnotationConfigApplicationContext 手动指定](#方式三:使用 AnnotationConfigApplicationContext 手动指定)
- 扫描路径的常见问题
-
- [问题 1:Bean 找不到(NoSuchBeanDefinitionException)](#问题 1:Bean 找不到(NoSuchBeanDefinitionException))
- [问题 2:@SpringBootApplication 与 @ComponentScan 同时使用](#问题 2:@SpringBootApplication 与 @ComponentScan 同时使用)
-
Spring IoC & DI 概述
什么是 IoC(控制反转)
IoC(Inversion of Control,控制反转) 是 Spring 框架的核心思想。传统开发中,对象由开发者自己通过 new 关键字创建和管理,对象的创建权在开发者手中。而 IoC 将这一控制权反转给 Spring 容器,由容器负责对象的创建、组装和生命周期管理。
一句话理解 :原来你亲自
new对象,现在把钥匙交给 Spring 容器,让它帮你new并管理。
IoC 带来的好处:
- 降低耦合:对象之间不再硬编码依赖关系,而是通过容器统一管理。
- 提高可测试性:可以轻松替换依赖的实现,方便单元测试。
- 统一生命周期管理:对象的创建、初始化、销毁都由容器统一控制。
什么是 DI(依赖注入)
DI(Dependency Injection,依赖注入) 是 IoC 的具体实现方式。当容器创建好一个对象后,会自动将它所依赖的其他对象注入进来,无需开发者手动组装。
java
// 没有 DI 的传统方式
public class UserController {
private UserService userService;
public UserController() {
// 开发者手动创建依赖
this.userService = new UserService();
}
}
// 使用 DI 的方式
@Component
public class UserController {
@Autowired // 容器自动注入依赖
private UserService userService;
}
IoC 与 DI 的关系
| 概念 | 角色 | 类比 |
|---|---|---|
| IoC(控制反转) | 设计思想 | 把"做饭"这件事交给餐厅,而不是自己下厨 |
| DI(依赖注入) | 实现方式 | 餐厅把做好的菜直接送到你桌上 |
IoC 是目标,DI 是手段。Spring 通过 DI 来实现 IoC,让容器管理对象的创建和依赖关系。
Spring IoC 容器的工作流程
① 配置元数据
(注解/XML/Java Config)
② Spring IoC 容器
读取配置
③ 实例化 Bean
(创建对象)
④ 依赖注入
(组装对象)
⑤ Bean 就绪
可供使用
流程说明:
- 配置元数据 :通过注解(
@Component、@Bean等)、XML 或 Java Config 告诉容器需要管理哪些类。 - 容器读取:Spring IoC 容器解析配置,构建 Bean 定义信息。
- 实例化:容器根据 Bean 定义创建对象实例。
- 依赖注入:容器自动将依赖的对象注入到需要的地方。
- 就绪:Bean 完全初始化,可以被应用程序使用。
什么是 Bean?
在 Spring 框架中,Bean 是由 Spring IoC 容器实例化、组装和管理的 Java 对象。简单来说,传统 Java 应用中对象由开发者自己 new 出来,而在 Spring 中,你把对象的创建权交给容器,容器创建出来的对象就叫做 Bean。
Bean 的核心特征:
- 由容器管理:对象的创建、初始化、销毁都由 Spring 容器负责。
- 默认单例:默认情况下,每个 Bean 在容器中只有一个实例,多次获取返回同一个对象。
- 可依赖注入 :一个 Bean 可以自动引用另一个 Bean,无需手动
new。
在 Spring 框架中,将对象存储到 IoC 容器是依赖注入的基础。
创建 Bean 的方式
Spring 提供了多种注解来实现这一目标,主要分为类注解 和方法注解两大类。
类注解
类注解用于将当前类的实例注册为 Spring 管理的 Bean。Spring 提供了以下 5 个核心类注解,它们都能将类注册为 Bean,但语义和功能上各有侧重:
| 注解 | 说明 | 典型应用层 | 特殊功能 |
|---|---|---|---|
@Controller |
控制器存储 | 前端控制器(MVC 中的 Controller) | 支持 @ResponseBody 语义、异常处理等 MVC 特性 |
@Service |
服务存储 | 业务逻辑层(Service) | 无特殊功能,纯语义标记 |
@Repository |
仓库存储 | 数据访问层(DAO / Repository) | 支持持久层异常转换 |
@Component |
组件存储 | 通用组件,不属于以上三层时使用 | 无特殊功能,纯语义标记 |
@Configuration |
配置存储 | 声明配置类,通常配合 @Bean 使用 |
支持 CGLIB 代理,确保 @Bean 方法单例 |
重要说明 :虽然这 5 个注解都能将类注册为 Bean,但
@Controller不能随意替换为其他注解 。@Controller是 Spring MVC 中处理 Web 请求的核心注解,它除了注册 Bean 外,还支持:
- 配合
@RequestMapping处理请求映射- 配合
@ResponseBody返回 JSON 数据- 全局异常处理(
@ExceptionHandler)- 视图解析等 MVC 特性
如果控制层使用
@Component或@Service代替@Controller,虽然 Bean 能被注册,但 Spring MVC 不会将其识别为控制器,请求映射等功能将失效。因此,控制层必须使用@Controller,其他层根据语义选择对应的注解。
方法注解
方法注解要配合类注解使用
@Bean 必须放在一个被类注解(如 @Configuration、@Component)修饰的类中才能生效。Spring 容器会自动创建 UserService 的实例,并交给 AppConfig 中的 @Bean 方法返回。
java
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
UserService 类的完整定义:
java
public class UserService {
public void sayHello() {
System.out.println("Hello from UserService Bean!");
}
}
定义多个对象
一个配置类中可以定义多个 @Bean 方法,每个方法对应一个 Bean。Spring 容器会自动创建 UserService、OrderService 的实例,并交给 AppConfig 中的 @Bean 方法返回。
java
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
@Bean
public OrderService orderService() {
return new OrderService();
}
}
OrderService 类的完整定义:
java
public class OrderService {
public void createOrder() {
System.out.println("Order created by OrderService Bean!");
}
}
重命名 Bean
默认情况下,Bean 的名称就是方法名。可以通过 @Bean("customName") 或 @Bean(name = "customName") 来指定自定义名称。
java
@Configuration
public class AppConfig {
@Bean("myUserService")
public UserService userService() {
return new UserService();
}
}
重命名后,再通过原方法名(如
userService)去获取 Bean 是找不到的,必须使用新名称myUserService。
导入配置类:@Import
@Import 用于在一个配置类中导入另一个配置类(或普通类),被导入的类也会被 Spring 容器管理。
适用场景:当你把配置分散在多个类中,需要在一个主配置类中统一引入时。
java
@Configuration
@Import({DbConfig.class, CacheConfig.class})
public class AppConfig {
// 这里可以继续定义 @Bean
}
这样 DbConfig 和 CacheConfig 中定义的 Bean 也会被注册到容器中。
FactoryBean 接口
FactoryBean 是一个特殊的接口,实现它可以让 Spring 通过一个工厂方法来创建 Bean。当你需要复杂的创建逻辑(比如根据配置动态创建不同的实现类)时,可以用它。
java
@Component
public class MyFactoryBean implements FactoryBean<UserService> {
@Override
public UserService getObject() {
// 这里可以写复杂的创建逻辑
return new UserServiceImpl();
}
@Override
public Class<?> getObjectType() {
return UserService.class;
}
}
注意:
FactoryBean本身也是一个 Bean,但通过getObject()返回的对象才是真正被注入使用的 Bean。
| 方式 | 适用场景 | 常用程度 |
|---|---|---|
@Bean 方法注解 |
创建第三方类、需要自定义初始化逻辑 | ⭐⭐⭐⭐⭐ |
| XML 配置 | 维护老项目 | ⭐⭐ |
@Import |
模块化配置、导入其他配置类 | ⭐⭐⭐⭐ |
FactoryBean |
复杂创建逻辑、动态选择实现类 | ⭐⭐⭐ |
Bean 对象的获取
将 Bean 存储到 Spring 容器后,需要从容器中获取这些 Bean 来使用。Spring 提供了多种获取 Bean 的方式,下面逐一介绍。
通过 ApplicationContext 获取
Bean 的名称规则
通过名称获取 Bean 时,需要知道 Bean 的名称是如何确定的。Spring 中 Bean 的名称规则如下:
| 创建方式 | 默认名称规则 | 示例 |
|---|---|---|
| 类注解 | 1.类名首字母小写(驼峰命名) 2.如果类名前两个字母都是大写,则保持原样 | UserService → userService URLParser →URLParser |
@Bean 方法注解 |
方法名即为 Bean 名称 | @Bean public UserService userService() → userService |
@Bean 指定名称 |
@Bean("customName") 或 @Bean(name = "customName") |
名称变为 customName |
获取不同方式创建的 Bean
ApplicationContext 是 Spring 容器的核心接口,可以通过它直接获取 Bean。下面演示如何通过 ApplicationContext 获取类注解 、方法注解 和指定名称三种方式创建的 Bean。
首先,在com.example.demo.service路径下准备三个 Bean 类:
java
// 1. 类注解 ------ @Service 创建 Bean
@Service
public class UserService {
public void sayHello() {
System.out.println("Hello from @Service Bean!");
}
}
// 2. 方法注解 ------ @Bean 创建 Bean
public class OrderService {
public void createOrder() {
System.out.println("Order from @Bean method!");
}
}
// 3. 指定名称 ------ @Bean("customName") 创建 Bean
public class ReportService {
public void generateReport() {
System.out.println("Report from named Bean!");
}
}
然后在在com.example.demo.configuration路径下,注册后两种 Bean:
java
@Configuration
public class AppConfig {
@Bean
public OrderService orderService() {
return new OrderService();
}
@Bean("myReportService")
public ReportService reportService() {
return new ReportService();
}
}
最后在启动类中分别获取:
java
package com.example.demo;
import com.example.demo.service.OrderService;
import com.example.demo.service.ReportService;
import com.example.demo.service.UserService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
// 1. 获取类注解创建的 Bean(按类型)
UserService userService = context.getBean(UserService.class);
userService.sayHello();
// 2. 获取 @Bean 方法注解创建的 Bean(按方法名)
OrderService orderService = (OrderService) context.getBean("orderService");
orderService.createOrder();
// 3. 获取指定名称的 Bean(按自定义名称)
ReportService reportService = (ReportService) context.getBean("myReportService");
reportService.generateReport();
}
}

下面用 Mermaid 图梳理三个 Bean 类、两种创建方式与启动类之间的整体关系:
Bean 类定义
方法注解创建
类注解创建
按类型获取
按方法名获取
按自定义名称获取
UserService
@Service
启动类 DemoApplication
context.getBean
AppConfig @Configuration
orderService @Bean
reportService @Bean
指定名称 myReportService
OrderService
ReportService
图例说明:
- 🟦 蓝色:类注解(
@Service)创建的 Bean - 🟧 橙色:方法注解(
@Bean)及配置类 - 🟩 绿色:启动类,负责获取 Bean
- 🟥 粉色:普通的 Java 类(被
@Bean方法返## 通过依赖注入获取
在 Spring 管理的 Bean 中,最常用的方式是通过 @Autowired 或 @Resource 注解自动注入,无需手动调用 `context.getBean()
通过依赖注入获取⭐
在 Spring 管理的 Bean 中,最常用的方式是通过依赖注入(Dependency Injection)自动装配依赖对象,无需手动调用 context.getBean()。Spring 支持三种主要的依赖注入方式:属性注入 、构造方法注入 和Setter方法注入。
@Autowired 详解
@Autowired 是 Spring 提供的原生注解,默认按类型(byType) 注入,可用于三种注入方式。
java
@Component
public class UserController {
// 属性注入
@Autowired
private UserService userService;
// 构造方法注入
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
// Setter方法注入
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
@Autowired 的注入规则:
| 步骤 | 行为 | 说明 |
|---|---|---|
| 1 | 按类型查找 | 容器中查找 UserService 类型的 Bean |
| 2 | 找到唯一 Bean | 直接注入,成功 |
| 3 | 找到多个同类型 Bean | 尝试按字段名/参数名/方法名匹配 Bean 名称 |
| 4 | 名称匹配成功 | 注入该名称对应的 Bean |
| 5 | 名称匹配失败 | 抛出 NoUniqueBeanDefinitionException |
解决多个同类型 Bean 的冲突:
java
// 方案一:@Primary 指定首选 Bean
@Primary
@Service
public class PrimaryUserService implements UserService { ... }
// 方案二:@Qualifier 指定名称
@Component
public class UserController {
@Autowired
@Qualifier("vipUserService") // 指定要注入的 Bean 名称
private UserService userService;
}
// 方案三:@Resource 按名称注入(见下文)
注意 :
@Autowired默认要求 Bean 必须存在 ,如果允许为空可以加required = false:
java@Autowired(required = false) private UserService userService; // 容器中没有 UserService 时,注入 null
@Resource 详解
@Resource 是 JSR-250 规范定义的注解,默认按名称(byName) 注入。
java
@Component
public class UserController {
@Resource(name = "userService")
private UserService userService; // 按名称注入
}
@Resource 的注入规则:
| 步骤 | 行为 | 说明 |
|---|---|---|
| 1 | 指定了 name 属性 | 按指定名称查找 Bean |
| 2 | 未指定 name | 按字段名/方法名查找 Bean |
| 3 | 按名称找到唯一 Bean | 直接注入 |
| 4 | 按名称未找到 | 回退为按类型查找 |
| 5 | 按类型找到唯一 Bean | 注入成功 |
| 6 | 按类型找到多个 | 抛出异常 |
@Resource 的常见用法:
java
// 1. 按字段名注入(默认)
@Resource
private UserService userService; // 查找名称为 "userService" 的 Bean
// 2. 按指定名称注入
@Resource(name = "vipUserService")
private UserService userService; // 查找名称为 "vipUserService" 的 Bean
// 3. 按类型注入(不推荐,语义不清晰)
@Resource(type = UserService.class)
private UserService userService;
@Autowired 与 @Resource 对比⭐
| 对比维度 | @Autowired |
@Resource |
|---|---|---|
| 来源 | Spring 原生注解 | JSR-250 规范(Java 标准) |
| 注入方式 | 先按类型,再按名称 | 先按名称,再按类型 |
| 指定名称 | 需配合 @Qualifier |
直接使用 name 属性 |
| required 属性 | 支持 required = false |
不支持(默认必须存在) |
| 适用场景 | 大多数情况,按类型注入更简洁 | 需要按名称注入时更直观 |
| 可移植性 | 仅限 Spring 项目 | 可在其他 Java EE 容器中使用 |
选择建议:
- 如果项目中只有一个同类型的 Bean → 推荐
@Autowired,代码更简洁 - 如果同一类型有多个 Bean → 推荐
@Resource(name = "..."),按名称注入更直观 - 如果希望代码脱离 Spring 也能运行 → 推荐
@Resource(JSR 标准)
获取不同方式创建的 Bean
下面演示如何通过依赖注入获取类注解 、方法注解 和指定名称三种方式创建的 Bean。
首先,准备三个 Bean 类(与上一节相同):
java
// 1. 类注解 ------ @Service 创建 Bean
@Service
public class UserService {
public void sayHello() {
System.out.println("Hello from @Service Bean!");
}
}
// 2. 方法注解 ------ @Bean 创建 Bean
public class OrderService {
public void createOrder() {
System.out.println("Order from @Bean method!");
}
}
// 3. 指定名称 ------ @Bean("customName") 创建 Bean
public class ReportService {
public void generateReport() {
System.out.println("Report from named Bean!");
}
}
然后在配置类中注册后两种 Bean:
java
@Configuration
public class AppConfig {
@Bean
public OrderService orderService() {
return new OrderService();
}
@Bean("myReportService")
public ReportService reportService() {
return new ReportService();
}
}
最后,通过依赖注入在一个组件中获取所有 Bean:
java
@Component
public class BeanDemoComponent {
// 1. 获取类注解创建的 Bean(按类型注入)
@Autowired
private UserService userService;
// 2. 获取 @Bean 方法注解创建的 Bean(按名称注入)
@Resource(name = "orderService")
private OrderService orderService;
// 3. 获取指定名称的 Bean(按自定义名称注入)
@Resource(name = "myReportService")
private ReportService reportService;
public void executeAll() {
userService.sayHello();
orderService.createOrder();
reportService.generateReport();
}
}
java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
// 从容器中获取 BeanDemoComponent
BeanDemoComponent demo = context.getBean(BeanDemoComponent.class);
demo.executeAll();
}
}
说明:
- 类注解(
@Service)创建的 Bean,推荐使用@Autowired按类型注入,Spring 会自动匹配。@Bean方法注解创建的 Bean,默认名称是方法名,可以使用@Resource(name = "orderService")按名称注入。- 通过
@Bean("myReportService")指定名称后,必须使用自定义名称myReportService注入,原方法名reportService不再有效。
下面用 Mermaid 图梳理三个 Bean 类、两种创建方式与 BeanDemoComponent 之间的依赖注入关系:
Bean 类定义
方法注解创建
类注解创建
@Autowired 按类型注入
@Resource(name='orderService') 按名称注入
@Resource(name='myReportService') 按名称注入
UserService
@Service
BeanDemoComponent
@Component
AppConfig @Configuration
orderService @Bean
reportService @Bean
指定名称 myReportService
OrderService
ReportService
图例说明:
- 🟦 蓝色:类注解(
@Service)创建的 Bean - 🟧 橙色:方法注解(
@Bean)及配置类 - 🟩 绿色:
BeanDemoComponent,通过依赖注入获取所有 Bean - 🟥 粉色:普通的 Java 类(被
@Bean方法返回)
1. 属性注入(Field Injection)
属性注入是最简单、最常用的注入方式,直接在字段上使用 @Autowired 或 @Resource 注解。
java
@Component
public class UserController {
// 使用 @Autowired 进行属性注入
@Autowired
private UserService userService;
// 使用 @Resource 进行属性注入
@Resource
private OrderService orderService;
public void execute() {
userService.sayHello();
orderService.createOrder();
}
}
优点:
- 代码简洁,无需编写额外的构造方法或Setter方法
- 易于理解和维护
缺点:
- 不利于单元测试(需要反射或Spring容器)
- 隐藏了依赖关系,不易发现循环依赖
- 字段被声明为final时无法使用
2. 构造方法注入(Constructor Injection)
构造方法注入通过构造方法参数来注入依赖,是Spring官方推荐的注入方式。
java
@Component
public class UserController {
private final UserService userService;
private final OrderService orderService;
// 构造方法注入(Spring 4.3+ 可以省略 @Autowired)
@Autowired
public UserController(UserService userService, OrderService orderService) {
this.userService = userService;
this.orderService = orderService;
}
// Spring 4.3+ 可以完全省略 @Autowired
public UserController(UserService userService, OrderService orderService) {
this.userService = userService;
this.orderService = orderService;
}
public void execute() {
userService.sayHello();
orderService.createOrder();
}
}
优点:
- 不可变性 :依赖可以声明为
final,确保线程安全 - 明确依赖:通过构造方法参数明确声明所有必需依赖
- 易于测试:可以直接通过构造方法传入mock对象进行单元测试
- 避免循环依赖:Spring在创建Bean时就能发现循环依赖问题
- Spring官方推荐 :从Spring 4.3开始,单构造方法可以省略
@Autowired
缺点:
- 当依赖较多时,构造方法参数列表会很长
3. Setter方法注入(Setter Injection)
Setter方法注入通过Setter方法来注入依赖,适用于可选依赖或需要重新配置的依赖。
java
@Component
public class UserController {
private UserService userService;
private OrderService orderService;
// Setter方法注入
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@Resource
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
// 可选依赖的Setter方法(required = false)
@Autowired(required = false)
public void setOptionalService(OptionalService optionalService) {
this.optionalService = optionalService;
}
public void execute() {
if (userService != null) {
userService.sayHello();
}
if (orderService != null) {
orderService.createOrder();
}
}
}
优点:
- 灵活性高,可以在Bean创建后重新配置依赖
- 适用于可选依赖(通过
required = false) - 符合JavaBean规范
缺点:
- 依赖可能为null,需要做空值检查
- 不能保证依赖在对象创建时就完全初始化
三种注入方式对比
| 特性 | 属性注入 | 构造方法注入 | Setter方法注入 |
|---|---|---|---|
| 代码简洁性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 不可变性 | ❌ | ✅ | ❌ |
| 明确依赖 | ❌ | ✅ | ⭐⭐⭐ |
| 易于测试 | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 循环依赖检测 | 运行时检测 | 启动时检测 | 运行时检测 |
| 可选依赖支持 | ✅(required=false) | ❌ | ✅ |
| Spring官方推荐 | ❌ | ✅ | ⭐⭐ |
| 适用场景 | 快速开发、简单场景 | 必需依赖、不可变对象 | 可选依赖、需要重新配置 |
通过 ApplicationContextAware 获取
如果需要在非 Spring 管理的普通类中获取容器,可以实现 ApplicationContextAware 接口。
java
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextUtil.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
}
使用示例:
java
// 在任意地方(包括非 Spring 管理的类中)获取 Bean
UserService userService = SpringContextUtil.getBean(UserService.class);
userService.sayHello();
扫描路径
Spring 容器需要知道去哪里查找带有 @Component、@Service、@Controller、@Repository 等注解的类,这个过程叫做组件扫描(Component Scan)。
默认扫描范围
在 Spring Boot 项目中,默认的扫描范围是启动类所在包及其所有子包。
java
package com.example.demo; // 启动类所在包
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
假设项目结构如下:
com.example.demo
├── DemoApplication.java ← 启动类
├── controller
│ └── UserController.java ← 会被扫描到
├── service
│ └── UserService.java ← 会被扫描到
├── config
│ └── AppConfig.java ← 会被扫描到
└── utils
└── StringUtil.java ← 会被扫描到
所有在 com.example.demo 及其子包(如 controller、service、config、utils)中的组件都会被自动扫描并注册到容器中。
不在默认扫描范围的情况
如果某个类放在启动类所在包之外,则不会被自动扫描:
com.example.demo
└── DemoApplication.java ← 启动类
com.other.module
└── OtherService.java ← ❌ 不会被扫描到
此时 OtherService 上的 @Service 注解不会生效,Spring 容器中不会有这个 Bean。
自定义扫描路径
如果需要扫描启动类所在包之外的组件,可以通过以下方式指定扫描路径。
方式一:@ComponentScan
在启动类或配置类上添加 @ComponentScan 注解,指定要扫描的包路径:
java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan("com.other.module") // 额外扫描 com.other.module 包
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
也可以指定多个包路径:
java
@ComponentScan({"com.other.module", "com.another.module"})
方式二:@SpringBootApplication 的 scanBasePackages 属性
@SpringBootApplication 本身包含了 @ComponentScan,可以直接通过 scanBasePackages 属性指定扫描路径,更加简洁:
java
@SpringBootApplication(scanBasePackages = {"com.example.demo", "com.other.module"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
注意 :使用
scanBasePackages后,默认的扫描范围(启动类所在包)不会自动生效,需要把启动类所在包也显式写进去,否则该包下的组件不会被扫描。
方式三:使用 AnnotationConfigApplicationContext 手动指定
在非 Spring Boot 的纯 Spring 项目中,可以通过 AnnotationConfigApplicationContext 手动指定扫描路径:
java
// 只注册 AppConfig 中的 @Bean,不会扫描 @Service 等类注解
ApplicationContext context1 = new AnnotationConfigApplicationContext(AppConfig.class);
// 指定包路径进行扫描,会扫描该包下所有带类注解的类
ApplicationContext context2 = new AnnotationConfigApplicationContext("com.example.demo");
// 从 context2 中可以获取 @Service 创建的 Bean
UserService userService = context2.getBean(UserService.class);
userService.sayHello();
扫描路径的常见问题
问题 1:Bean 找不到(NoSuchBeanDefinitionException)
最常见的原因是组件不在扫描路径内。解决方案:
java
// 1. 检查类是否在启动类所在包或其子包中
// 2. 如果不在,使用 @ComponentScan 或 scanBasePackages 指定路径
// 3. 或者把类移到启动类所在包内
问题 2:@SpringBootApplication 与 @ComponentScan 同时使用
@SpringBootApplication 已经包含了 @ComponentScan,如果同时使用,@ComponentScan 会覆盖默认的扫描路径,导致启动类所在包不再被扫描。
java
// ❌ 错误用法:@ComponentScan 覆盖了默认扫描路径
@SpringBootApplication
@ComponentScan("com.other.module") // 此时 com.example.demo 不会被扫描
public class DemoApplication { ... }
// ✅ 正确用法:把默认包也加进去
@SpringBootApplication
@ComponentScan({"com.example.demo", "com.other.module"})
public class DemoApplication { ... }
// ✅ 或者使用 scanBasePackages 属性
@SpringBootApplication(scanBasePackages = {"com.example.demo", "com.other.module"})
public class DemoApplication { ... }