IOC 容器原理
IOC 基本概念
IOC(Inversion of Control,控制反转) 是一种设计思想,指将对象的创建、管理和依赖关系的维护从程序本身转移到外部容器中。传统开发中,对象之间的依赖关系由程序自身控制,而 IOC 容器通过配置信息(如 XML、注解)接管对象的生命周期,实现对象间的松耦合。
核心本质:将 "程序主动创建对象" 转变为 "容器为程序提供对象",控制权发生反转。
IOC 与依赖注入(DI)的关系
- DI(Dependency Injection,依赖注入) 是 IOC 的具体实现方式,指容器在创建对象时,将其依赖的对象通过参数、属性等方式注入,而非对象自己创建依赖。
- 三者关系:IOC 是思想,DI 是实现方式,IOC 容器是 DI 的载体。
传统方式与 IOC 方式的完整流程对比
传统三层架构(手动控制)的完整执行流程
java
// 1. 定义接口与实现(数据库层)
interface Database {
Connection getConnection();
}
class MySQLDatabase implements Database {
@Override
public Connection getConnection() {
System.out.println("[MySQLDatabase] 获取数据库连接");
return null; // 简化示例
}
}
// 2. 数据访问层(Repository)
class UserRepository {
private Database db;
public UserRepository(Database db) {
this.db = db;
System.out.println("[UserRepository] 初始化,依赖 Database");
}
public void saveUser(String username) {
System.out.println("[UserRepository] 执行 SQL: INSERT INTO user (name) VALUES ('" + username + "')");
Connection conn = db.getConnection();
// 实际会执行 SQL 操作
}
}
// 3. 业务逻辑层(Service)
class UserService {
private UserRepository repo;
public UserService(UserRepository repo) {
this.repo = repo;
System.out.println("[UserService] 初始化,依赖 UserRepository");
}
public void createUser(String username) {
System.out.println("[UserService] 校验用户合法性: " + username);
// 业务逻辑(如参数校验、权限检查)
repo.saveUser(username);
System.out.println("[UserService] 用户创建成功");
}
}
// 4. 控制层(Controller)
class UserController {
private UserService service;
public UserController(UserService service) {
this.service = service;
System.out.println("[UserController] 初始化,依赖 UserService");
}
public void handleRequest(String action, String username) {
System.out.println("[UserController] 接收到请求: " + action);
if ("createUser".equals(action)) {
service.createUser(username);
}
}
}
// 5. 传统方式主程序
public class TraditionalApp {
public static void main(String[] args) {
System.out.println("=== 传统方式:手动创建对象链 ===");
// 手动创建对象,层级依赖关系:Controller -> Service -> Repository -> Database
Database db = new MySQLDatabase();
UserRepository repo = new UserRepository(db);
UserService service = new UserService(repo);
UserController controller = new UserController(service);
// 调用方法,触发完整流程
controller.handleRequest("createUser", "张三");
System.out.println("=== 传统方式执行完毕 ===");
}
}
执行结果与流程解析
java
=== 传统方式:手动创建对象链 ===
[MySQLDatabase] 获取数据库连接 // Database 构造函数执行
[UserRepository] 初始化,依赖 Database // Repository 初始化
[UserService] 初始化,依赖 UserRepository // Service 初始化
[UserController] 初始化,依赖 UserService // Controller 初始化
[UserController] 接收到请求: createUser // 控制器接收请求
[UserService] 校验用户合法性: 张三 // 服务层业务逻辑
[UserRepository] 执行 SQL: INSERT INTO user (name) VALUES ('张三') // 数据层操作
[MySQLDatabase] 获取数据库连接 // Repository 调用 Database 方法
[UserService] 用户创建成功 // 服务层完成
=== 传统方式执行完毕 ===
核心特点
- 对象创建顺序:从底层(Database)到顶层(Controller)依次创建,耦合紧密。
- 方法调用链:Controller → Service → Repository → Database,层级清晰但无法解耦。
- 问题暴露 :若更换为 Oracle 数据库,需修改
UserRepository
的构造函数(传入OracleDatabase
),并修改TraditionalApp
中db
的创建逻辑。
IOC 方式(容器管理)的完整执行流程
java
// 1. 接口与实现类(同上,不变)
// (Database/MySQLDatabase/UserRepository/UserService/UserController 代码不变)
// 2. 配置类(告知容器如何创建 Bean)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public Database database() {
System.out.println("[AppConfig] 配置 Database Bean: MySQLDatabase");
return new MySQLDatabase();
}
@Bean
public UserRepository userRepository(Database db) {
System.out.println("[AppConfig] 配置 UserRepository Bean");
return new UserRepository(db);
}
@Bean
public UserService userService(UserRepository repo) {
System.out.println("[AppConfig] 配置 UserService Bean");
return new UserService(repo);
}
@Bean
public UserController userController(UserService service) {
System.out.println("[AppConfig] 配置 UserController Bean");
return new UserController(service);
}
}
// 3. IOC 方式主程序
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCApp {
public static void main(String[] args) {
System.out.println("=== IOC 方式:容器自动创建对象链 ===");
// 1. 创建容器并加载配置
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("[IOCApp] 容器初始化完成");
// 2. 从容器获取 Controller
UserController controller = context.getBean(UserController.class);
// 3. 调用方法,触发完整流程
controller.handleRequest("createUser", "张三");
System.out.println("=== IOC 方式执行完毕 ===");
}
}
执行结果与流程解析
java
=== IOC 方式:容器自动创建对象链 ===
[AppConfig] 配置 Database Bean: MySQLDatabase // 容器解析配置,先创建 Database
[AppConfig] 配置 UserRepository Bean // 再创建 Repository(依赖已注入)
[AppConfig] 配置 UserService Bean // 创建 Service
[AppConfig] 配置 UserController Bean // 创建 Controller
[IOCApp] 容器初始化完成 // 容器初始化完毕
[UserController] 接收到请求: createUser // 控制器接收请求
[UserService] 校验用户合法性: 张三 // 服务层业务逻辑
[UserRepository] 执行 SQL: INSERT INTO user (name) VALUES ('张三') // 数据层操作
[MySQLDatabase] 获取数据库连接 // Repository 调用 Database 方法
[UserService] 用户创建成功 // 服务层完成
=== IOC 方式执行完毕 ===
核心特点
- 对象创建顺序:容器根据依赖关系自动推导创建顺序(与传统方式相同,但由容器控制)。
- 配置驱动 :
AppConfig
中的@Bean
方法定义了 Bean 的创建逻辑,业务代码与配置解耦。 - 动态替换能力 :若更换为 Oracle 数据库,只需修改
AppConfig
中的database()
方法。
java
@Bean
public Database database() {
return new OracleDatabase(); // 替换为 Oracle 实现
}
业务代码(UserRepository/UserService/Controller
)无需任何修改。
两种方式的核心差异对比表
对比维度 | 传统方式(手动控制) | IOC 方式(容器管理) |
---|---|---|
对象创建位置 | 主程序(main 方法)中手动创建 |
容器根据配置自动创建 |
依赖关系维护 | 代码中硬编码依赖(如 new MySQLDatabase() ) |
配置中声明依赖(如 @Bean public UserService(UserRepository repo) ) |
耦合度 | 高(修改底层实现需层层修改上层代码) | 低(仅修改配置即可替换实现) |
方法调用透明度 | 直接调用,流程清晰但耦合紧密 | 容器隐藏对象创建细节,调用流程相同但底层解耦 |
扩展性(如更换数据库) | 修改 main 方法和依赖类构造函数 |
仅修改配置类中的 @Bean 方法 |
测试成本 | 需手动创建完整依赖链,难以隔离测试 | 可通过配置注入 Mock 对象,轻松隔离依赖 |
IOC 方式的动态性演示(更换数据库实现)
新增 Oracle 数据库实现
java
class OracleDatabase implements Database {
@Override
public Connection getConnection() {
System.out.println("[OracleDatabase] 获取 Oracle 数据库连接");
return null;
}
}
修改配置类(仅改一行代码)
java
@Bean
public Database database() {
return new OracleDatabase(); // 改为 Oracle 实现
// 原代码:return new MySQLDatabase();
}
执行结果(IOCApp 不变)
plain
=== IOC 方式:容器自动创建对象链 ===
[AppConfig] 配置 Database Bean: OracleDatabase // 注意此处变为 Oracle
[AppConfig] 配置 UserRepository Bean
[AppConfig] 配置 UserService Bean
[AppConfig] 配置 UserController Bean
[IOCApp] 容器初始化完成
[UserController] 接收到请求: createUser
[UserService] 校验用户合法性: 张三
[UserRepository] 执行 SQL: INSERT INTO user (name) VALUES ('张三')
[OracleDatabase] 获取 Oracle 数据库连接 // 此处变为 Oracle 实现
[UserService] 用户创建成功
=== IOC 方式执行完毕 ===
关键结论
- IOC 容器通过 "配置抽象" 隔离了底层实现,业务代码对数据库变更无感知。
- 传统方式需要修改 2 处代码(
main
方法和UserRepository
构造函数),IOC 方式仅需修改配置类 1 处。
总结:从代码执行流程看 IOC 的本质
- 传统方式:开发者像 "手工组装机器",每个零件(对象)的创建和连接都需亲自处理,一旦某个零件更换,整台机器都需拆解重组。
- IOC 方式:开发者像 "设计机器图纸",告诉容器 "需要什么零件" 和 "如何连接",容器自动组装机器,更换零件时只需修改图纸,机器运行逻辑不变。
通过完整的方法调用追踪可以看到,IOC 容器并未改变程序的业务执行流程(Controller → Service → Repository),而是将 "对象如何创建" 和 "依赖如何连接" 的控制权从代码转移到配置,从而实现了松耦合和可扩展性。这正是 "控制反转" 的核心价值 ------ 让程序逻辑专注于 "做什么",而不是 "如何做"。
简易代码实现
定义注解
java
/**
* 依赖注入注解
*
* @author song
* @date 2025/06/20
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR})
public @interface MyAutowired {
}
/**
* 组件扫描注解
*
* @author song
* @date 2025/06/20
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyComponent {
String value() default "";
}
/**
* 包扫描注解
*
* @author song
* @date 2025/06/20
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyComponentScan {
String value();
}
/**
* 包扫描注解
*
* @author song
* @date 2025/06/20
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyComponentScan {
String value();
}
/**
* 初始化方法注解
*
* @author song
* @date 2025/06/20
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyPostConstruct {
}
容器生命周期
java
package com.ssh.wlb.modules.spring.context;
// 1. 注解导入
import com.ssh.wlb.modules.spring.annotation.MyAutowired;
import com.ssh.wlb.modules.spring.annotation.MyComponent;
import com.ssh.wlb.modules.spring.annotation.MyComponentScan;
import com.ssh.wlb.modules.spring.annotation.MyConfiguration;
import com.ssh.wlb.modules.spring.annotation.MyPostConstruct;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import lombok.Data;
/**
* 自定义Spring式IoC容器实现 核心功能:
* 1. 依赖注入(@MyAutowired)
* 2. 循环依赖解决(二级缓存)
* 3. 生命周期回调(@MyPostConstruct)
* 4. 组件扫描(@MyComponentScan)
*/
public class MyApplicationContext {
// ========================= 核心存储结构 =========================
// [作用] 存储Bean元数据(类型/作用域)
private final Map<String, BeanDefinition> beanDefinitions = new HashMap<>();
// [作用] 一级缓存(完整Bean)
// [特性] 存储已完成初始化的单例Bean
private final Map<String, Object> singletonPool = new HashMap<>();
// [作用] 二级缓存(半成品Bean)
// [特性] 解决循环依赖的关键组件
private final Map<String, Object> earlySingletonPool = new HashMap<>();
// ========================= 容器初始化 =========================
/**
* 构造函数
*
* @param configClass 配置类(需带@MyConfiguration注解) 执行流程:
* 1. 解析配置类 → 2. 扫描Bean定义 → 3. 预实例化单例
*/
public MyApplicationContext(Class<?> configClass) {
scanConfiguration(configClass); // 步骤1:配置解析
preInstantiateSingletons(); // 步骤2:单例预加载
}
// ========================= Bean定义模型 =========================
/**
* Bean元数据容器 存储信息:
* - type: Bean的Class对象
* - singleton: 是否单例作用域
*/
@Data
static class BeanDefinition {
private Class<?> type;
private boolean singleton;
}
// ========================= 配置解析阶段 =========================
/**
* 解析配置类 关键步骤:
* 1. 验证@MyConfiguration注解
* 2. 获取@ComponentScan路径
* 3. 启动包扫描
*/
private void scanConfiguration(Class<?> configClass) {
// 1. 配置类注解校验
if (!configClass.isAnnotationPresent(MyConfiguration.class)) {
throw new RuntimeException("配置类缺少@MyConfiguration注解: " + configClass.getName());
}
// 2. 扫描路径提取
String scanPath = "";
if (configClass.isAnnotationPresent(MyComponentScan.class)) {
scanPath = configClass.getAnnotation(MyComponentScan.class).value();
}
// 3. 启动递归扫描
scanPackage(scanPath);
}
// ========================= 组件扫描阶段 =========================
/**
* 包扫描入口 执行流程:
* 1. 包路径转文件路径 → 2. 获取类加载器资源 → 3. 启动目录扫描
*/
private void scanPackage(String basePackage) {
// 路径格式转换 (com.example → com/example)
String path = basePackage.replace(".", "/");
try {
// 获取类路径资源
Enumeration<URL> resources = Thread.currentThread()
.getContextClassLoader()
.getResources(path);
// 遍历所有资源目录
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
scanDirectory(basePackage, new File(resource.getFile()));
}
} catch (IOException e) {
throw new RuntimeException("包扫描失败: " + basePackage, e);
}
}
/**
* 递归扫描目录 处理逻辑:
* - 目录 → 递归扫描子目录
* - .class文件 → 注册Bean定义
*/
private void scanDirectory(String currentPackage, File dir) {
for (File file : Objects.requireNonNull(dir.listFiles())) {
if (file.isDirectory()) {
// 递归处理子目录: 包名追加目录名
scanDirectory(currentPackage + "." + file.getName(), file);
} else if (file.getName().endsWith(".class")) {
// 类文件处理: 去除.class后缀
String className = currentPackage + "." +
file.getName().replace(".class", "");
registerBeanDefinition(className);
}
}
}
// ========================= Bean注册阶段 =========================
/**
* 注册Bean定义 核心逻辑:
* 1. 类加载 → 2. @MyComponent注解过滤 → 3. Bean命名规则
*/
private void registerBeanDefinition(String className) {
try {
Class<?> clazz = Class.forName(className);
// 仅处理带@MyComponent注解的类
if (clazz.isAnnotationPresent(MyComponent.class)) {
BeanDefinition bd = new BeanDefinition();
bd.setType(clazz);
bd.setSingleton(true); // 默认单例作用域
// Bean命名规则:
// 1. 优先使用注解指定名称
// 2. 默认首字母小写类名
String beanName = clazz.getAnnotation(MyComponent.class).value();
if (beanName.isEmpty()) {
beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() +
clazz.getSimpleName().substring(1);
}
// 防重复注册
if (!beanDefinitions.containsKey(beanName)) {
beanDefinitions.put(beanName, bd);
}
}
} catch (Exception e) {
throw new RuntimeException("注册Bean定义失败: " + className, e);
}
}
// ========================= Bean实例化阶段 =========================
/**
* 预实例化单例Bean 执行策略:遍历所有Bean定义,初始化单例作用域Bean
*/
private void preInstantiateSingletons() {
beanDefinitions.keySet().forEach(beanName -> {
BeanDefinition bd = beanDefinitions.get(beanName);
if (bd != null && bd.isSingleton()) {
getBean(beanName); // 触发初始化
}
});
}
/**
* 获取Bean实例(核心入口) 三级缓存查询顺序:
* 1. 一级缓存(完整Bean) → 2. 二级缓存(半成品) → 3. 新建实例
*/
public Object getBean(String beanName) {
// 1. 一级缓存查询
if (singletonPool.containsKey(beanName)) {
return singletonPool.get(beanName);
}
// 2. 二级缓存查询(解决循环依赖)
if (earlySingletonPool.containsKey(beanName)) {
return earlySingletonPool.get(beanName);
}
// 3. 安全校验
if (!beanDefinitions.containsKey(beanName)) {
throw new RuntimeException("未找到Bean定义: " + beanName);
}
// 4. 创建新实例
Object instance = createBean(beanName);
// 5. 单例Bean注册
BeanDefinition bd = beanDefinitions.get(beanName);
if (bd != null && bd.isSingleton()) {
singletonPool.put(beanName, instance);
earlySingletonPool.remove(beanName); // 关键:移出二级缓存
}
return instance;
}
/**
* 创建Bean实例 四步生命周期:
* 1. 实例化 → 2. 提前暴露 → 3. 依赖注入 → 4. 初始化回调
*/
private Object createBean(String beanName) {
BeanDefinition bd = beanDefinitions.get(beanName);
// 防御性检查
if (bd == null) {
throw new RuntimeException("Bean定义不存在: " + beanName);
}
try {
// [阶段1] 反射实例化
Object instance = bd.getType().getDeclaredConstructor().newInstance();
// [阶段2] 提前暴露半成品(解决循环依赖)
earlySingletonPool.put(beanName, instance);
// [阶段3] 依赖注入
for (Field field : bd.getType().getDeclaredFields()) {
if (field.isAnnotationPresent(MyAutowired.class)) {
// 突破私有访问限制
field.setAccessible(true);
// 递归依赖解析
Object fieldBean = getBean(field.getName());
field.set(instance, fieldBean);
}
}
// [阶段4] 初始化回调
invokeInitMethods(instance, bd.getType());
return instance;
} catch (Exception e) {
// 异常时清理二级缓存
earlySingletonPool.remove(beanName);
throw new RuntimeException("创建Bean失败: " + beanName, e);
}
}
// ========================= 生命周期回调 =========================
/**
* 执行初始化方法 过滤条件:带@MyPostConstruct注解的方法
*/
private void invokeInitMethods(Object bean, Class<?> beanClass) {
for (Method method : beanClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(MyPostConstruct.class)) {
try {
// 突破私有访问限制
method.setAccessible(true);
// 执行初始化逻辑
method.invoke(bean);
} catch (Exception e) {
throw new RuntimeException("初始化方法执行失败: " + method.getName(), e);
}
}
}
}
}
容器初始化流程图

循环依赖解决序列图

Bean生命周期状态图
stateDiagram-v2
[*] --> 未实例化
未实例化 --> 实例化中: getBean调用
实例化中 --> 半成品: 加入二级缓存
半成品 --> 依赖注入中: 开始注入
依赖注入中 --> 初始化中: 开始初始化
初始化中 --> 完整Bean: 加入一级缓存
完整Bean --> [*]: 可被获取
note right of 半成品
循环依赖解决方案:
当BeanB需要BeanA时,
直接返回半成品BeanA
end note
缓存机制图解
graph TD
A[1. 获取Bean定义] --> B[2. 实例化]
B --> C[3. 加入二级缓存]
C --> D[4. 依赖注入]
D --> E{需要其他Bean?}
E -->|是| F[递归getBean]
E -->|否| G[5. 执行初始化]
F -->|返回半成品/完整Bean| D
G --> H[6. 移入一级缓存]
H --> I[7. 清理二级缓存]
I --> J[8. 返回完整Bean]
容器使用
java
@MyConfiguration
@MyComponentScan("com.ssh.wlb.modules.spring")
public class AppConfig {
// 配置类:指定组件扫描路径
}
@MyComponent
public class UserService {
@MyAutowired
private UserRepository userRepository; // 需要依赖注入的字段
@MyPostConstruct
public void init() {
System.out.println("UserService初始化完成"); // 初始化回调
}
public void showUser() {
userRepository.showUser(); // 业务方法
}
}
@MyComponent
public class UserRepository {
@MyPostConstruct
public void init() {
System.out.println("UserRepository初始化完成"); // 初始化回调
}
public void showUser() {
System.out.println("UserRepository查询用户"); // 业务方法
}
}
public class Test {
public static void main(String[] args) {
// 12. 创建容器 - 触发整个初始化流程
MyApplicationContext ctx = new MyApplicationContext(AppConfig.class);
/*
容器创建流程:
1. 解析配置类,获取扫描路径
2. 扫描包路径,识别@MyComponent类
3. 创建Bean定义(UserService, UserRepository)
4. 初始化一级/二级缓存
*/
// 12.1 按名称获取Bean - 触发UserService初始化
UserService service = (UserService) ctx.getBean("userService");
/*
getBean("userService")流程:
1. 检查一级缓存 → 未找到
2. 创建UserService实例(步骤2:实例化)
3. 将半成品UserService放入二级缓存(步骤3)
4. 发现@MyAutowired字段 → 触发UserRepository创建
*/
// 13. 调用业务方法
service.showUser(); // 输出:"UserRepository查询用户"
}
}
完整执行流程图解

缓存状态变化图解
plain
时间线 | 触发操作 | 一级缓存内容 | 二级缓存内容 | 关键状态说明
--------------|---------------------------|----------------------|----------------------------|-------------------------------------------
T0 (容器启动) | 扫描注册Bean定义 | ∅ | ∅ | 加载UserService/UserRepository定义
| | | |
T1 (获取Bean) | getBean("userService") | ∅ | ∅ | 开始创建UserService
| ↓ | | |
T2 | UserService实例化 | ∅ | userService(半成品) | 对象已创建(userRepository=null)
| ↓ | | |
T3 | 发现@MyAutowired依赖 | ∅ | userService(半成品) | 需要注入userRepository
| ↓ | | |
T4 | getBean("userRepository") | ∅ | userService(半成品) | 开始创建UserRepository
| ↓ | | |
T5 | UserRepository实例化 | ∅ | userService(半成品) |
| | | userRepository(半成品) | UserRepository对象创建完成
| ↓ | | |
T6 | UserRepository初始化 | userRepository(成品) | userService(半成品) | 执行@MyPostConstruct
| (移入一级缓存) | | | 控制台输出:"UserRepository初始化完成"
| ↓ | | |
T7 | 注入UserService | userRepository(成品) | userService(半成品) | userService.userRepository已赋值
| ↓ | | |
T8 | UserService初始化 | userRepository(成品) | ∅ | 执行@MyPostConstruct
| (移入一级缓存) | userService(成品) | | 控制台输出:"UserService初始化完成"
| ↓ | | |
T9 (业务调用) | service.showUser() | userService(成品) | ∅ | 调用链:userService → userRepository
| | userRepository(成品) | | 控制台输出:"UserRepository查询用户"
关键变化节点图解
graph LR
A[容器启动] --> B[实例化UserService]
B --> C[加入二级缓存]
C --> D[发现userRepository依赖]
D --> E[实例化UserRepository]
E --> F[加入二级缓存]
F --> G[初始化UserRepository]
G --> H[移入一级缓存]
H --> I[从二级缓存移除]
I --> J[注入UserService]
J --> K[初始化UserService]
K --> L[移入一级缓存]
L --> M[从二级缓存移除]
M --> N[返回完整Bean]
关键点说明
- 二级缓存的核心作用 :
- 在 T2-T8 期间保存
userService
半成品 - 在 T5-T6 期间保存
userRepository
半成品 - 解决循环依赖问题:当其他Bean需要注入
userService
时,可以从二级缓存获取半成品
- 在 T2-T8 期间保存
- 状态转换点 :
- 实例化 → 半成品:对象被创建但未初始化(构造器执行完成)
- 依赖注入 → 半成品增强:对象依赖已满足,但未执行初始化方法
- 初始化 → 成品 :执行完
@PostConstruct
方法,进入可用状态
- 缓存清理时机 :
- UserRepository在 T6 初始化后立即移出二级缓存
- UserService在 T8 初始化后移出二级缓存
- 最终二级缓存保持为空(单例模式下)