3.3.1 方法注解要配合类注解使用
在 Spring 框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中,如下代码所示:
java
@Component
public class BeanConfig {
@Bean
public User user(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
再次执行以上代码,运行结果如下:

3.3.2 定义多个对象
对于同一个类,如何定义多个对象呢?比如多数据源的场景,类是同一个,但是配置不同,指向不同的数据源。
我们看下@Bean的使用:
java
@Component
public class BeanConfig {
@Bean
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2(){
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
定义了多个对象的话,我们根据类型获取对象,获取的是哪个对象呢?
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下文中获取对象
User user = context.getBean(User.class);
//使用对象
System.out.println(user);
}
}
运行结果:

报错信息与解决方案
报错信息显示:期望只有一个匹配,结果发现了两个,user1, user2从报错信息中,可以看出来,@Bean 注解的 bean, bean 的名称就是它的方法名
接下来我们根据名称来获取 bean 对象
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//根据bean名称, 从Spring上下文中获取对象
User user1 = (User) context.getBean("user1");
User user2 = (User) context.getBean("user2");
System.out.println(user1);
System.out.println(user2);
}
}
运行结果:
User(name=zhangsan, age=18)
User(name=lisi, age=19)
可以看到,@Bean 可以针对同一个类,定义多个对象.
3.3.3 重命名 Bean

1. 五大注解(@Component/@Service/@Controller/@Repository/@Configuration)
Spring 会根据类名自动生成 bean name,规则分两种:
- 情况 1 :类名前两个字母都是大写 → bean name 直接等于类名本身
- 例:
class HTTPClient {}→ bean name =HTTPClient - 例:
class SQLParser {}→ bean name =SQLParser
- 例:
- 情况 2 :其他情况 → bean name 是类名的小驼峰写法 (首字母小写)
- 例:
class UserService {}→ bean name =userService - 例:
class OrderController {}→ bean name =orderController
- 例:
2. @Bean 注解
当你在配置类里用 @Bean 标注一个方法时:
- bean name 直接等于方法名
-
例:
java@Bean public UserService userService() { return new UserService(); }→ bean name =
userService
-
举个完整例子对比
| 代码写法 | 生成的 bean name | 规则 |
|---|---|---|
class UserService {} |
userService |
小驼峰 |
class HTTPClient {} |
HTTPClient |
前两位大写,保持原样 |
@Bean public OrderDao orderDao() {} |
orderDao |
方法名 |
💡 小提示:
- 如果你想自定义 bean name,可以直接在注解里写,比如
@Service("myUserService")或@Bean(name = "myOrderDao")。 - 这个规则是 Spring 的
BeanNameGenerator实现的,目的是保证 bean 名称的可读性和唯一性
可以通过设置 name 属性给 Bean 对象进行重命名操作,如下代码所示:
java
@Bean(name = {"u1","user1"})
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
此时我们使用 u1 就可以获取到 User 对象了,如下代码所示:
此时可通过 context.getBean("u1") 或 context.getBean("user1") 获取对象:
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
User u1 = (User) context.getBean("u1");
System.out.println(u1);
}
}
3.3.3.2语法简写规则
-
name=可省略 :java@Bean({"u1","user1"}) public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; } -
单个名称时,大括号也可省略 :
java@Bean("u1") public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; }

3.3.3.3 扫描路径知识点
Q :使用 @Component/@Service 等四个注解声明的 bean,一定会生效吗?A:不一定。原因:bean 想要生效,还需要被 Spring 扫描到,未被扫描的注解不会被注册为 bean。
A:不一定。原因:bean 想要生效,还需要被 Spring 扫描到,未被扫描的注解不会被注册为 bean。
下⾯我们通过修改项⽬⼯程的⽬录结构,来测试bean对象是否⽣效:

java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下文中获取对象
User u1 = (User) context.getBean("u1");
//使用对象
System.out.println(u1);
}
}
运行结果: 
结果解读
这个异常表示:Spring 容器中不存在名为 u1 的 Bean。
- 可能原因:
@Bean(name = "u1")的配置类没有被 Spring 扫描到。@Bean注解没有被正确添加到类注解(如@Component/@Configuration)的类中。- Bean 名称拼写错误(如
u1写成ui或其他)。


注意:运行的这个类在扫描五大注解的时候只扫描在同一个路径下的文件,如果把他放到的service里面,其他路径的就不会扫描了
java
@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下文中获取对象
User u1 = (User) context.getBean("u1");
//使用对象
System.out.println(u1);
}
}
Spring Boot 的 @SpringBootApplication 已经包含了 @ComponentScan,默认扫描范围是 "启动类所在的包 + 所有子包",所以大部分场景不用手动加。
但遇到以下情况,就需要手动配置 @ComponentScan:
- Bean 所在的包不在启动类的包 / 子包下 (比如启动类在
com.example.demo,但 Bean 写在com.example.service,不在子包); - 想精确控制扫描范围 (比如只扫描
service和controller包,排除entity包); - 想排除某些不想被扫描的类 (比如某个类加了
@Service,但暂时不想让它变成 Bean)。
@ComponentScan 的核心用法
1. 基础用法:指定扫描的包路径
java
// 启动类
// 手动指定扫描 com.example.service 和 com.example.controller 两个包
@ComponentScan({"com.example.service", "com.example.controller"})
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
}
}
- 语法:
@ComponentScan({"包路径1", "包路径2"}),大括号里可以写多个包; - 注意:包路径要写完整的包名 (比如
com.example.service,不是service)。
2. 进阶用法:包含 / 排除指定类
java
// 扫描 com.example 包,但排除 UserService 类(即使它加了 @Service 也不会变成 Bean)
@ComponentScan(
basePackages = "com.example",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = UserService.class
)
)
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
}
}
这种用法适合临时排除某个类,或者只扫描特定类型的注解(比如只扫描 @Controller,不扫描 @Service)。
3.和 @Configuration 的关联
@Configuration 标记的配置类,也需要被 @ComponentScan 扫描到 ,否则里面的 @Bean 方法不会被执行:
java
// 配置类放在 com.example.config 包下
@Configuration
public class BeanConfig {
@Bean
public User user() {
return new User("zhangsan", 18);
}
}
// 启动类如果不扫描 com.example.config,BeanConfig 不会被识别,user() 也不会执行
@ComponentScan({"com.example.controller", "com.example.config"}) // 必须包含 config 包
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
User user = context.getBean(User.class); // 能拿到对象,因为扫描到了 BeanConfig
}
}
4.关键注意点(避坑)
- 不要重复扫描 :手动加
@ComponentScan时,不要和@SpringBootApplication的默认扫描范围重复,否则可能导致 Bean 重复创建; - 包路径要写对 :如果包路径写错(比如少写一层),Spring 找不到注解类,会报
NoSuchBeanDefinitionException; - Spring Boot 不推荐手动加:除非特殊场景,否则尽量把所有 Bean 放在启动类的包 / 子包下,利用默认扫描,减少配置。
{} 里可以配置多个包路径这种做法仅做了解,不做推荐使用
那为什么前面没有配置 @ComponentScan 注解也可以呢?
@ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中了
默认扫描的范围是 SpringBoot 启动类所在包及其子包
在配置类上添加 @ComponentScan 注解,该注解默认会扫描该类所在的包下所有的配置类

推荐做法:
把启动类放在我们希望扫描的包的路径下,这样我们定义的bean就都可以被扫描到

DI 详解
依赖注入(DI)是 IoC 容器在创建 Bean 时,为其提供运行所依赖资源(对象)的过程,也被称为 "对象注入" 或 "属性装配"。
Spring 提供了三种依赖注入方式:
- 属性注入(Field Injection)
- 构造方法注入(Constructor Injection)
- Setter 注入(Setter Injection)
4.1 属性注入
属性注入通过 @Autowired 注解实现,将 Service 类注入到 Controller 类中。
Service 类实现:
java
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void sayHi() {
System.out.println("Hi,UserService");
}
}
Controller 类实现:
java
@Controller
public class UserController {
// 注入方法1:属性注入
@Autowired
private UserService userService;
public void sayHi(){
System.out.println("hi,UserController...");
userService.sayHi();
}
}
获取并调用 Controller 方法:
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
// 获取Spring上下文对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
// 从Spring上下文中获取对象
UserController userController = (UserController) context.getBean("userController");
// 使用对象
userController.sayHi();
}
}
运行结果: 
4.2 构造方法注入
构造方法注入是在类的构造方法中实现注入,代码如下:
java
@Controller
public class UserController2 {
//注入方法2:构造方法
private UserService userService;
@Autowired
public UserController2(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,UserController2...");
userService.sayHi();
}
}
注意事项:
- 如果类只有一个构造方法,
@Autowired注解可以省略; - 如果类中有多个构造方法,需要添加
@Autowired来明确指定使用哪个构造方法。
4.3 Setter 注入
Setter 注入通过类的 Setter 方法实现,需在 set 方法上添加 @Autowired 注解,代码如下:
java
@Controller
public class UserController3 {
//注入方法3:Setter方法注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,UserController3...");
userService.sayHi();
}
}
练习思考 :尝试一下 set 方法如果不加 @Autowired 注解能注入成功吗?
4.4 三种注入优缺点分析
属性注入
- 优点:简洁,使用方便;
- 缺点:
- 只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)
- 不能注入一个 Final 修饰的属性
构造函数注入 (Spring 4.X 推荐)
- 优点:
- 可以注入 final 修饰的属性
- 注入的对象不会被修改
- 依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法.
- 通用性好,构造方法是 JDK 支持的,所以更换任何框架,他都是适用的
- 缺点:
- 注入多个对象时,代码会比较繁琐
Setter 注入 (Spring 3.X 推荐)
- 优点:方便在类实例化之后,重新对该对象进行配置或者注入
- 缺点:
- 不能注入一个 Final 修饰的属性
- 注入对象可能会被改变,因为 setter 方法可能会被多次调用,就有被修改的风险.
4.5 @Autowired 存在问题
当同一类型存在多个 bean 时,使用 @Autowired 会存在问题
java
@Component
public class BeanConfig {
@Bean("u1")
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
java
@Controller
public class UserController {
@Autowired
private UserService userService;
//注入user
@Autowired
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
userService.sayHi();
System.out.println(user);
}
}
运行结果:

报错的原因是,非唯一的 Bean 对象。
解决方案
Spring 提供了以下几种解决方案:
@Primary@Qualifier@Resource
1. 使用 @Primary 注解
当存在多个相同类型的 Bean 注入时,加上 @Primary 注解,来确定默认的实现。
java
@Component
public class BeanConfig {
@Primary //指定该bean为默认bean的实现
@Bean("u1")
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
2. 使用 @Qualifier 注解
指定当前要注入的 bean 对象。在 @Qualifier 的 value 属性中,指定注入的 bean 的名称。
@Qualifier注解不能单独使用,必须配合@Autowired使用
java
@Controller
public class UserController {
@Qualifier("user2") //指定bean名称
@Autowired
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
3. 使用 @Resource 注解
是按照 bean 的名称进行注入。通过 name 属性指定要注入的 bean 的名称。
java
@Controller
public class UserController {
@Resource(name = "user2")
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
常见面试题:@Autowired 与 @Resource 的区别
@Autowired是 Spring 框架提供的注解,而@Resource是 JDK 提供的注解@Autowired默认是按照类型注入,而@Resource是按照名称注入。相比于@Autowired来说,@Resource支持更多的参数设置,例如name设置,根据名称获取 Bean。
@Autowired 与 @Resource 核心区别
这张图总结了 Spring 中两个依赖注入注解的核心差异,我帮你补充完整并做进一步解读:
1. 来源不同
- @Autowired :由 Spring 框架 提供(
org.springframework.beans.factory.annotation.Autowired),是 Spring 专属的注入注解。 - @Resource :由 JDK 提供(
javax.annotation.Resource,Java EE 标准),不依赖 Spring 框架,可在其他 IoC 容器中使用。
2. 注入规则不同
- @Autowired :默认 ** 按类型(byType)** 注入。
- 先根据属性类型在容器中找匹配的 Bean;
- 若存在多个同类型 Bean,会再按属性名匹配;
- 需指定名称时,要配合
@Qualifier("beanName")使用。
- @Resource :默认 ** 按名称(byName)** 注入。
- 优先根据
name属性或属性名匹配 Bean; - 若名称匹配不到,才会降级为按类型匹配;
- 原生支持
name参数,可直接指定要注入的 Bean 名称,无需额外注解。
- 优先根据
3. 功能与参数对比
| 特性 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring 框架 | JDK (Java EE) |
| 默认注入方式 | 按类型 (byType) | 按名称 (byName) |
| 指定名称 | 需配合 @Qualifier |
直接用 name 属性 |
| 支持参数 | 仅 required |
name, type, lookup 等 |
| 兼容性 | 仅 Spring 容器 | 通用,兼容所有支持 Java EE 的容器 |
4. 典型使用场景
-
@Autowired :适合单类型 Bean 场景,代码更简洁,Spring 生态中最常用。
@Autowired private UserService userService; -
@Resource :适合多实例场景,可直接指定名称获取目标 Bean,避免类型匹配歧义。
// 直接指定注入名为 userService1 的 Bean @Resource(name = "userService1") private UserService userService;
💡 补充说明
- 在 Spring 4.3+ 后,构造方法注入成为官方推荐,
@Autowired可省略,此时@Resource的优势主要体现在精确按名匹配上。 @Resource属于 Java EE 规范,在高版本 JDK 中需要手动引入jakarta.annotation-api依赖才能使用。- 若项目仅使用 Spring 生态,推荐优先用
@Autowired+@Qualifier;若需跨容器兼容,可考虑@Resource。
