Spring IoC 容器与 Bean 管理核心考点解析

3. IoC 详解

通过上面的案例,我们已经知道了 Spring IoC 和 DI 的基本操作,接下来我们来系统的学习 Spring IoC 和 DI 的操作。

前面我们提到 IoC 控制反转,就是将对象的控制权交给 Spring 的 IOC 容器,由 IOC 容器创建及管理对象。也就是 bean 的存储。

3.1 Bean 的存储

在之前的入门案例中,要把某个对象交给 IOC 容器管理,需要在类上添加一个注解:@Component。而 Spring 框架为了更好的服务 web 应用程序,提供了更丰富的注解。

共有两类注解类型可以实现:

  1. 类注解:@Controller@Service@Repository@Component@Configuration
  2. 方法注解:@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 是更好的选择。

比如杯子有喝水杯、刷牙杯等,但是我们更倾向于在日常喝水时使用水杯,洗漱时使用刷牙杯。

更多资料参考:https://docs.spring.io/spring-framework/reference/core/beans/classpath-scanning.html#beans-stereotype-annotations

相关推荐
polaris06301 小时前
Java集合进阶
java·开发语言
tant1an2 小时前
Spring Boot 基础入门:从核心配置到 SSMP 整合实战
java·数据库·spring boot·sql·spring
客卿1232 小时前
力扣--组合,子集--回溯法的再探索--总结回溯法
java·算法·leetcode
毕设源码-赖学姐2 小时前
【开题答辩全过程】以 高校晚查寝系统为例,包含答辩的问题和答案
java
xiaoye37082 小时前
某大厂java面试题二面20260313
java·开发语言·spring
Full Stack Developme2 小时前
Java -jar 命令 可以有哪些参数设置
java·开发语言·jar
一只程序熊3 小时前
vite-cool-unix-ctx] Unexpected token l in JSON at position 0
java·服务器·前端
晨晖23 小时前
idea2017的下载,破解及使用
java·ide·intellij-idea
摇滚侠3 小时前
Java 项目教程《黑马商城-MQ 篇》,分布式架构项目,从开发到部署
java·分布式·架构