3. IoC 详解
通过上面的案例,我们已经知道了 Spring IoC 和 DI 的基本操作,接下来我们来系统的学习 Spring IoC 和 DI 的操作。
前面我们提到 IoC 控制反转,就是将对象的控制权交给 Spring 的 IOC 容器,由 IOC 容器创建及管理对象。也就是 bean 的存储。
3.1 Bean 的存储
在之前的入门案例中,要把某个对象交给 IOC 容器管理,需要在类上添加一个注解:@Component。而 Spring 框架为了更好的服务 web 应用程序,提供了更丰富的注解。
共有两类注解类型可以实现:
- 类注解:
@Controller、@Service、@Repository、@Component、@Configuration。 - 方法注解:
@Bean。
接下来我们分别来看。
3.1.1 @Controller(控制器存储)
使用@Controller存储 bean 的代码如下所示:
java
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi() {
System.out.println("hi,UserController...");
}
}
如何观察这个对象已经存在 Spring 容器当中了呢?接下来我们学习如何从 Spring 容器中获取对象。
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下文中获取对象
UserController userController = context.getBean(UserController.class);
//使用对象
userController.sayHi();
}
}
ApplicationContext翻译过来就是:Spring 上下文。因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下文。
关于上下文的概念:上学时,阅读理解经常会这样问:根据上下文,说一下你对 XX 的理解。在计算机领域,上下文这个概念,咱们最早是在学习线程时了解到,比如我们应用进行线程切换的时候,切换前都会把线程的状态信息暂时储存起来,这里的上下文就包括了当前线程的信息,等下次该线程又得到 CPU 时间的时候,从上下文中拿到线程上次运行的信息。这个上下文,就是指当前的运行环境,也可以看作是一个容器,容器里存了很多内容,这些内容是当前运行的环境。
观察运行结果,发现成功从 Spring 中获取到 Controller 对象,并执行 Controller 的 sayHi 方法

java
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAll() {
return userMapper.findAll();
}
@Override
public User findById(int id) {
return userMapper.findById(id);
}
@Override
public void update(User user) {
userMapper.update(user);
}
}


获取 bean 对象的其他方式
上述代码是根据类型来查找对象,如果 Spring 容器中,同一个类型存在多个 bean 的话,怎么来获取呢?
ApplicationContext 也提供了其他获取 bean 的方式,ApplicationContext 获取 bean 对象的功能,是父类 BeanFactory 提供的功能.
java
public interface BeanFactory {
//以上省略...
// 1. 根据bean名称获取bean
Object getBean(String var1) throws BeansException;
// 2. 根据bean名称和类型获取bean
<T> T getBean(String var1, Class<T> var2) throws BeansException;
// 3. 根据bean名称和构造函数参数动态创建bean,只适用于具有原型(prototype)作用域的bean
Object getBean(String var1, Object... var2) throws BeansException;
// 4. 根据类型获取bean
<T> T getBean(Class<T> var1) throws BeansException;
// 5. 按类型和构造函数参数动态创建bean,只适用于具有原型(prototype)作用域的bean
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
//以下省略...
}
常用的是上述 1,2,4 种,这三种方式,获取到的 bean 是一样的
其中 1,2 种都涉及到根据名称来获取对象. bean 的名称是什么呢?
Spring bean 是 Spring 框架在运行时管理的对象,Spring 会给管理的对象起一个名字.
比如学校管理学生,会给每个学生分配一个学号,根据学号,就可以找到对应的学生.
Spring 也是如此,给每个对象起一个名字,根据 bean 的名称 (BeanId) 就可以获取到对应的对象.
Bean 命名约定
我们看下方官方文档的说明: Bean Overview :: Spring Framework

程序开发人员不需要为 bean 指定名称 (BeanId),如果没有显式的提供名称 (BeanId),Spring 容器将为该 bean 生成唯一的名称。
命名约定使用 Java 标准约定作为实例字段名,也就是说,bean 名称以小写字母开头,然后使用驼峰式大小写。
比如:
- 类名: UserController,Bean 的名称为: userController
- 类名: AccountManager,Bean 的名称为: accountManager
- 类名: AccountService,Bean 的名称为: accountService
也有一些特殊情况,当有多个字符并且第一个和第二个字符都是大写时,将保留原始的大小写,这些规则与 java.beans.Introspector.decapitalize (Spring 在这里使用的) 定义的规则相同。
比如:
- 类名: UController,Bean 的名称为: UController
- 类名: AManager,Bean 的名称为: AManager
根据这个命名规则,我们来获取 Bean。
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下文中获取对象
//根据bean类型,从Spring上下文中获取对象
UserController userController1 = context.getBean(UserController.class);
//根据bean名称,从Spring上下文中获取对象
UserController userController2 = (UserController)
context.getBean("userController");
//根据bean类型+名称,从Spring上下文中获取对象
UserController userController3 =
context.getBean("userController",UserController.class);
System.out.println(userController1);
System.out.println(userController2);
System.out.println(userController3);
}
}

地址一样,说明对象是一个
获取 bean 对象,是父类 BeanFactory 提供的功能
ApplicationContext VS BeanFactory(常见面试题)
- 继承关系和功能方面来说:Spring 容器有两个顶级的接口:BeanFactory 和 ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能力,而 ApplicationContext 属于 BeanFactory 的子类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性,还添加了对国际化支持、资源访问支持、以及事件传播等方面的支持。
- 从性能方面来说:ApplicationContext 是一次性加载并初始化所有的 Bean 对象,而 BeanFactory 是需要那个才去加载那个,因此更加轻量。(空间换时间)
| 维度 | ApplicationContext | BeanFactory |
|---|---|---|
| 加载时机 | 容器启动时一次性加载并初始化所有单例 Bean | 延迟加载:调用 getBean() 时才创建目标 Bean |
| 内存占用 | 启动时占用内存多(空间换时间) | 启动时内存占用少(时间换空间) |
| 启动速度 | 启动较慢(初始化所有 Bean) | 启动更快(仅加载必要结构) |
| 适用场景 | 企业级 Web 应用(提前预热,运行时响应快) | 资源受限 / 轻量场景(如嵌入式设备) |
关键补充
getBean()方法本身是BeanFactory定义的,ApplicationContext只是继承并复用了这个能力。- Spring Boot 启动时默认使用的
AnnotationConfigApplicationContext就是ApplicationContext的实现类,所以我们平时写的SpringApplication.run()本质是在创建ApplicationContext容器。💡
基础功能看 BeanFactory,完整能力用 Context; 饿汉加载是 Context,懒汉加载 BeanFactory。
3.1.2@Service 存储 bean 代码
java
@Service
public class UserService {
public void sayHi(String name) {
System.out.println("Hi," + name);
}
}
读取 bean 代码
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring中获取UserService对象
UserService userService = context.getBean(UserService.class);
//使用对象
userService.sayHi();
}
}
代码说明
@Service注解 :标记UserService为业务层组件,Spring 会自动扫描并将其注册为容器中的 bean。ApplicationContext:Spring 上下文对象,用于从 IoC 容器中获取已管理的 bean 实例。context.getBean(UserService.class):通过类型从 Spring 容器中获取UserService实例,无需手动创建对象。

1. @Repository(仓库存储)
作用 :标注数据访问层(DAO)组件,用于数据库操作相关的类。代码示例:
java
@Repository
public class UserRepository {
public void sayHi() {
System.out.println("Hi, UserRepository~");
}
}
读取 Bean 代码:
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserRepository userRepository = context.getBean(UserRepository.class);
userRepository.sayHi();
}
}
运行结果 :Hi, UserRepository~注意 :删除 @Repository 后,Spring 无法识别该类,获取 Bean 会报错。
2. @Component(组件存储)
作用 :通用组件注解,标注普通业务组件,是其他三层注解(@Repository/@Service/@Controller)的父注解。代码示例:
java
@Component
public class UserComponent {
public void sayHi() {
System.out.println("Hi, UserComponent~");
}
}
读取 Bean 代码:
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserComponent userComponent = context.getBean(UserComponent.class);
userComponent.sayHi();
}
}
运行结果 :Hi, UserComponent~注意 :删除 @Component 后,Spring 无法识别该类,获取 Bean 会报错。
3. @Configuration(配置存储)
作用 :标注配置类,用于定义 Bean 和配置 Spring 容器行为。代码示例:
java
@Configuration
public class UserConfiguration {
public void sayHi() {
System.out.println("Hi,UserConfiguration~");
}
}
读取 Bean 代码
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserConfiguration userConfiguration = context.getBean(UserConfiguration.class);
userConfiguration.sayHi();
}
}
运行结果 :Hi,UserConfiguration~注意 :删除 @Configuration 后,Spring 无法识别该类,获取 Bean 会报错。
🔍 核心总结
- 共同特点 :这三个注解都能将类标记为 Spring 管理的 Bean,通过
ApplicationContext.getBean()可以获取实例并调用方法;删除注解后,Spring 不会扫描并创建该类的 Bean。 - 语义区别 :
@Repository:专门用于数据访问层(DAO),和数据库交互。@Component:通用组件层,无特定业务语义。@Configuration:配置层,用于定义配置和 Bean 注册。
- 本质关系 :
@Repository、@Service、@Controller本质上都是@Component的派生注解,只是语义更明确。
@Configuration 和 @Component之间的区别?
| 特性 | @Component |
@Configuration |
|---|---|---|
| 核心定位 | 通用组件标记(如工具类、业务类) | 配置类标记(专门用来定义 Bean) |
| 底层逻辑 | 普通 Bean,调用方法返回新对象 | 代理 Bean,调用方法返回容器中已有的单例 Bean |
| 主要用途 | 标记需要被 Spring 扫描管理的普通类 | 集中定义 / 配置 Bean(替代 XML 配置) |
| 依赖注入 | 被动接收注入(@Autowired) |
主动定义 Bean,可依赖其他 Bean |
1. 先看 @Component 的常规用法
@Component 是通用注解 ,标记任意需要被 Spring 管理的类(@Service/@Repository 都是它的子类),核心是让 Spring 扫描并创建实例。
java
import org.springframework.stereotype.Component;
// 通用组件:工具类
@Component
public class DateUtils {
public String getCurrentTime() {
return "2026-03-14 12:00:00";
}
}
// 业务类(@Service 本质是 @Component)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
// 注入 @Component 标记的 Bean
@Autowired
private DateUtils dateUtils;
public void createOrder() {
System.out.println("订单创建时间:" + dateUtils.getCurrentTime());
}
}
2. 再看 @Configuration 的核心用法
@Configuration 是配置专用注解 ,核心是通过 @Bean 方法定义 Bean,且保证 Bean 的单例特性(关键区别)。
场景 1:错误用 @Component 替代 @Configuration(暴露问题)
java
import org.springframework.stereotype.Component;
// 错误示范:用 @Component 做配置类
@Component
public class WrongConfig {
// 定义 Bean 方法
public DateUtils dateUtils() {
return new DateUtils();
}
// 依赖 dateUtils Bean
public OrderService orderService() {
OrderService service = new OrderService();
// 调用 dateUtils() 方法 → 每次返回新对象(非单例)
service.setDateUtils(dateUtils());
return service;
}
}
问题 :每次调用 dateUtils() 都会创建新的 DateUtils 对象,破坏 Spring 单例特性。
场景 2:正确用 @Configuration 做配置类
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 正确示范:配置类专用注解
@Configuration
public class CorrectConfig {
// 定义 Bean(单例)
@Bean
public DateUtils dateUtils() {
return new DateUtils();
}
// 依赖 dateUtils Bean
@Bean
public OrderService orderService() {
OrderService service = new OrderService();
// 调用 dateUtils() → Spring 代理,返回容器中已有的单例对象
service.setDateUtils(dateUtils());
return service;
}
}
关键 :@Configuration 会被 Spring 生成代理类,调用内部 @Bean 方法时,不会创建新对象,而是返回容器中已存在的单例 Bean。
使用场景
| 注解 | 什么时候用? | 典型例子 |
|---|---|---|
@Component |
标记普通业务类 / 工具类 / 自定义组件 | 日期工具类、通用常量类 |
@Configuration |
定义 Bean / 配置第三方组件 / 数据源 | 数据源配置、MyBatis 配置、Redis 配置 |
3.2 为什么要这么多类注解?
这个也是和咱们前面讲的应用分层是呼应的。让程序员看到类注解之后,就能直接了解当前类的用途。
- @Controller:控制层,接收请求,对请求进行处理,并进行响应。
- @Service:业务逻辑层,处理具体的业务逻辑。
- @Repository:数据访问层,也称为持久层。负责数据访问操作。
- @Configuration:配置层。处理项目中的一些配置信息。
这和每个省 / 市都有自己的车牌号是一样的。车牌号都是唯一的,标识一个车辆的。但是为什么还需要设置不同的车牌开头呢?
比如陕西的车牌号就是:陕 X:XXXXXX,北京的车牌号:京 X:XXXXXX,甚至一个省不同的县区也是不同的,比如西安就是,陕 A:XXXXXX,咸阳:陕 B:XXXXXX,宝鸡,陕 C:XXXXXX,一样。这样做的好处除了可以节约号码之外,更重要的作用是可以直观的标识一辆车的归属地。
程序的应用分层,调用流程如下:

类注解之间的关系 查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发 现

其实这些注解里面都有一个注解 @Component,说明它们本身就是属于 @Component 的 "子类"。
@Component 是一个元注解,也就是说可以注解其他类注解,如 @Controller,@Service,@Repository 等。这些注解被称为 @Component 的衍生注解。
@Controller,@Service 和 @Repository 用于更具体的用例(分别在控制层、业务逻辑层、持久化层),在开发过程中,如果你要在业务逻辑层使用 @Component 或 @Service,显然 @Service 是更好的选择。
比如杯子有喝水杯、刷牙杯等,但是我们更倾向于在日常喝水时使用水杯,洗漱时使用刷牙杯。