📅 2026-07-25 | 🏷️ Java · 后端方向 | ⏱️ 建议 5h | 🎯 Spring 是 Java 面试的绝对核心------IOC/AOP/Boot 三位一体
📌 今日知识地图
Spring 面试全景
│
├── 模块一:Spring IOC
│ ├── BeanFactory vs ApplicationContext
│ ├── Bean 生命周期(12 步完整版)
│ ├── 三级缓存 & 循环依赖(面试最难!)
│ └── 依赖注入:@Autowired/@Resource/构造器注入
│
├── 模块二:Spring AOP
│ ├── JDK 动态代理 vs CGLIB
│ ├── @Transactional 原理 & 失效的 6 种场景
│ ├── 事务传播行为(7 种)
│ └── 事务隔离级别
│
├── 模块三:Spring Boot
│ ├── 自动配置原理(@SpringBootApplication 拆解)
│ ├── starter 机制 & 自定义 starter
│ ├── @Conditional 条件装配
│ └── 配置文件加载优先级
│
├── 模块四:Spring MVC
│ ├── DispatcherServlet 完整请求流程
│ ├── 拦截器 vs 过滤器 vs AOP
│ └── 统一异常处理
│
├── 模块五:MyBatis
│ ├── 核心执行流程(SqlSession → Executor → StatementHandler)
│ ├── 一级缓存 vs 二级缓存
│ ├── #{} vs ${}(SQL 注入)
│ └── 插件机制(Interceptor + 动态代理)
│
└── 面试题精选(10 道 + 公司标签)
模块一:Spring IOC
1.1 IOC 解决了什么问题?
没有 IOC:
class UserService {
private UserDao dao = new UserDaoImpl(); // 自己 new 依赖
}
问题:UserService 和 UserDaoImpl 紧耦合,换实现要改代码
有 IOC:
class UserService {
@Autowired
private UserDao dao; // Spring 帮你注入
}
优势:UserService 只依赖接口,实现可以随时换(DI 实现 IOC)
IOC(控制反转):把对象的创建和管理权交给 Spring 容器,需要时注入。
DI(依赖注入):IOC 的具体实现方式。三种注入方式:
| 方式 | 示例 | 推荐度 |
|---|---|---|
| 构造器注入 | public X(Service s) {} |
推荐(不可变、非空、易测试) |
| Setter 注入 | public void setS(Service s) {} |
可选依赖用 |
| 字段注入 | @Autowired private Service s; |
不推荐(难以测试、隐藏依赖) |
1.2 BeanFactory vs ApplicationContext
| 维度 | BeanFactory | ApplicationContext |
|---|---|---|
| 定位 | 底层容器,IOC 核心 | 高级容器,企业级功能 |
| Bean 加载 | 懒加载(首次 getBean 才初始化) | 预加载(启动时初始化所有单例 Bean) |
| 功能 | 基本 DI | DI + AOP + 事件 + 国际化 + 资源加载 + ... |
| 使用 | 几乎不直接用 | 一切 Spring 应用的入口 |
ApplicationContext 常用实现:
ClassPathXmlApplicationContext ← XML 配置
AnnotationConfigApplicationContext ← 注解配置
Spring Boot:自动创建 AnnotationConfigServletWebServerApplicationContext
1.3 Bean 生命周期(面试必背 12 步)
1. 实例化(Instantiation)
→ 通过反射调用构造器创建对象实例
2. 属性赋值(Populate Properties)
→ @Autowired / @Value / @Resource 注入
3. BeanNameAware.setBeanName()
→ 让 Bean 知道自己在容器中的名称
4. BeanFactoryAware.setBeanFactory()
→ 让 Bean 获取 BeanFactory 引用
5. ApplicationContextAware.setApplicationContext()
→ 让 Bean 获取 ApplicationContext 引用
6. BeanPostProcessor.postProcessBeforeInitialization()
→ 初始化前置处理(如 @PostConstruct 注解的处理)
7. @PostConstruct 标注的方法
→ 初始化回调
8. InitializingBean.afterPropertiesSet()
→ 属性设置完毕后的回调
9. 自定义 init-method
→ @Bean(initMethod = "init")
10. BeanPostProcessor.postProcessAfterInitialization()
→ 初始化后置处理(AOP 代理对象在这里生成!)
11. Bean 就绪,可以被使用了
12. @PreDestroy / DisposableBean.destroy() / destroy-method
→ 销毁回调
java
// 最精简的记忆方式:
// 创建 → 注入 → Aware → 前置处理 → 初始化 → 后置处理(AOP) → 就绪 → 销毁
1.4 三级缓存 & 循环依赖(面试最难知识点!)
java
// ═══════════════════════════════════════
// 三级缓存的定义
// ═══════════════════════════════════════
// 一级缓存(singletonObjects): 完全初始化好的单例 Bean
// 二级缓存(earlySingletonObjects):早期暴露的半成品 Bean(已实例化但未属性填充)
// 三级缓存(singletonFactories): 生成半成品 Bean 的工厂(Lambda 表达式)
// ═══════════════════════════════════════
// 循环依赖场景:A 依赖 B,B 依赖 A
// ═══════════════════════════════════════
// 解决流程:
// 1. 创建 A:实例化 → 将 A 的 ObjectFactory 放入三级缓存 → 属性填充时发现需要 B
// 2. 创建 B:实例化 → 将 B 的 ObjectFactory 放入三级缓存 → 属性填充时发现需要 A
// 3. B 从三级缓存获取 A 的 ObjectFactory → 调用 getObject() 获取 A 的半成品
// → 将 A 的半成品放入二级缓存 → 移除三级缓存中 A 的工厂
// 4. B 注入 A 的半成品 → B 完成初始化 → 将完整的 B 放入一级缓存
// 5. A 注入完整的 B → A 完成初始化 → 将 A 的半成品升级为完整的 A
// → 将完整的 A 放入一级缓存 → 移除二级缓存中 A 的半成品
// ═══════════════════════════════════════
// 为什么需要三级缓存,而不是两级?
// ═══════════════════════════════════════
// 答案:AOP!
// 如果只有两级缓存(二级直接存半成品对象):
// → 当 B 注入 A 时,拿到的是原始对象
// → 但 A 可能需要被 AOP 代理
// → 如果 A 被代理了,B 注入的应该是代理对象而非原始对象
// → 但此时 A 还没完成属性填充,无法确定是否要生成代理
// 三级缓存的精妙之处:
// → 三级缓存存的不是对象,而是 ObjectFactory(Lambda)
// → getObject() 内部会检查是否需要 AOP 代理 → 需要就返回代理对象,不需要就返回原始对象
// → 无论后续属性如何变化,这个 Lambda 都能返回"正确的版本"
// ═══════════════════════════════════════
// 循环依赖不能解决的场景
// ═══════════════════════════════════════
// ① 构造器注入的循环依赖 → 实例化就无法完成,三级缓存救不了
// 解决:改用 Setter 注入或 @Lazy(延迟注入)
// ② prototype 作用域的循环依赖 → 不会用缓存
// 解决:改成单例或重新设计
// Spring 解决循环依赖的前提:
// → 单例 Bean
// → Setter/字段注入(不是构造器注入)
模块二:Spring AOP
2.1 AOP 核心概念
AOP(面向切面编程)= 把横切关注点(日志、事务、权限)从业务代码中抽离
核心术语:
Aspect(切面) = 横切逻辑 + 切点
JoinPoint(连接点)= 能插入切面的位置(方法执行)
Pointcut(切点) = 匹配连接点的表达式
Advice(通知) = 在切点做什么、什么时候做
@Before → 方法执行前
@After → 方法执行后(无论是否异常)
@AfterReturning → 方法正常返回后
@AfterThrowing → 方法抛异常后
@Around → 环绕(最强大,手动调 joinPoint.proceed())
Weaving(织入) = 将切面应用到目标对象(编译期/类加载期/运行期)
2.2 JDK 动态代理 vs CGLIB
java
// ═══════════════════════════════════════
// JDK 动态代理
// ═══════════════════════════════════════
// 原理:实现目标类的接口 → 生成代理类 $Proxy0 extends Proxy implements Interface
// 要求:目标类必须有接口
// 创建:Proxy.newProxyInstance(classLoader, interfaces, invocationHandler)
// 每次调用代理方法 → InvocationHandler.invoke() → 可加通知
// ═══════════════════════════════════════
// CGLIB 代理
// ═══════════════════════════════════════
// 原理:继承目标类 → 生成子类 Enhancer extends TargetClass
// 通过 ASM 修改字节码
// 要求:目标类不能是 final(final 类不能继承)
// 方法不能是 final(final 方法不能重写)
// 创建:Enhancer.create(targetClass, methodInterceptor)
// ═══════════════════════════════════════
// Spring AOP 的选择策略
// ═══════════════════════════════════════
// Spring Boot 1.x:默认 JDK 动态代理
// Spring Boot 2.x+:默认 CGLIB(即使有接口也用 CGLIB)
// 配置 spring.aop.proxy-target-class=true(默认)
// 手动指定:@EnableAspectJAutoProxy(proxyTargetClass = true) → CGLIB
2.3 @Transactional 失效的 6 种场景(面试必问!)
java
// ═══════════════════════════════════════
// 场景 1:方法不是 public(最常见!)
// ═══════════════════════════════════════
@Transactional
private void doSomething() {} // ❌ 私有方法,代理对象调不到
// ═══════════════════════════════════════
// 场景 2:同类方法调用(this 调用绕过代理)
// ═══════════════════════════════════════
@Service
public class UserService {
public void methodA() {
this.methodB(); // ❌ this 是原始对象,不是代理对象!
}
@Transactional
public void methodB() {} // 事务失效!
}
// 解决:注入自己 @Autowired private UserService self; → self.methodB();
// ═══════════════════════════════════════
// 场景 3:异常被 try-catch 吞掉
// ═══════════════════════════════════════
@Transactional
public void save() {
try {
// 数据库操作
int i = 1 / 0; // 抛出 RuntimeException
} catch (Exception e) {
log.error("出错了", e); // ❌ 吞掉异常,Spring 感知不到 → 不回滚!
}
}
// 解决:catch 中手动抛出异常 或 设置 rollbackFor
// ═══════════════════════════════════════
// 场景 4:rollbackFor 设置错误
// ═══════════════════════════════════════
@Transactional // 默认只回滚 RuntimeException 和 Error
public void save() throws Exception {
throw new Exception("检查异常"); // ❌ 不会回滚!
}
// 解决:@Transactional(rollbackFor = Exception.class)
// ═══════════════════════════════════════
// 场景 5:数据库引擎不支持事务
// ═══════════════════════════════════════
// MyISAM 不支持事务 → 怎么配置 @Transactional 都没用 → 用 InnoDB
// ═══════════════════════════════════════
// 场景 6:多线程调用
// ═══════════════════════════════════════
@Transactional
public void save() {
new Thread(() -> {
mapper.insert(data); // ❌ 新线程不在当前事务中
}).start();
}
2.4 事务传播行为(7 种)
| 传播行为 | 当前有事务 | 当前无事务 | 典型场景 |
|---|---|---|---|
| REQUIRED(默认) | 加入当前事务 | 新建事务 | 大多数场景 |
| REQUIRES_NEW | 挂起当前事务,新建 | 新建事务 | 日志记录(独立提交) |
| SUPPORTS | 加入当前事务 | 不开启事务 | 查询 |
| NOT_SUPPORTED | 挂起当前事务,非事务运行 | 非事务运行 | 不需要事务的操作 |
| MANDATORY | 加入当前事务 | 抛异常 | 必须在事务中 |
| NEVER | 抛异常 | 非事务运行 | 必须在非事务中 |
| NESTED | 创建嵌套事务(Savepoint) | 新建事务 | 部分回滚 |
模块三:Spring Boot
3.1 @SpringBootApplication 拆解
java
@SpringBootApplication
// = @SpringBootConfiguration ← 等同于 @Configuration,标记这是一个配置类
// + @EnableAutoConfiguration ← 自动配置的核心!
// + @ComponentScan ← 扫描当前包及子包的组件
// ═══════════════════════════════════════
// @EnableAutoConfiguration 的奥秘
// ═══════════════════════════════════════
@EnableAutoConfiguration
// = @AutoConfigurationPackage ← 注册当前包路径(给 JPA 等用的)
// + @Import(AutoConfigurationImportSelector.class)
// AutoConfigurationImportSelector:
// 1. 从 META-INF/spring.factories(老版)
// 或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(新版)
// 中读取所有自动配置类的全限定名
// 2. 每个自动配置类上都有 @ConditionalOnXxx 注解
// 3. 满足条件 → 加载配置 → 创建对应的 Bean
// 4. 不满足 → 跳过
// 例子:DataSourceAutoConfiguration
// @ConditionalOnClass(DataSource.class) ← classpath 中有 DataSource 才生效
// @ConditionalOnMissingBean(DataSource.class) ← 用户没自定义 DataSource 才自动创建
3.2 starter 机制
一个 starter = 一个 JAR 包,包含:
→ 所依赖的库(pom.xml 中声明为传递依赖)
→ 自动配置类(AutoConfiguration)
→ spring.factories / AutoConfiguration.imports 文件
命名规范:
官方:spring-boot-starter-xxx(如 spring-boot-starter-web)
第三方:xxx-spring-boot-starter(如 mybatis-spring-boot-starter)
自定义 starter 要点:
① 写一个配置类,用 @Bean 创建需要自动配置的对象
② 用 @ConditionalOnXxx 控制生效条件
③ 在 AutoConfiguration.imports 中注册配置类
④ @ConfigurationProperties(prefix = "my.starter") 读取 application.yml 配置
3.3 配置文件加载优先级(高到低)
1. 命令行参数(--server.port=8080)
2. SPRING_APPLICATION_JSON 环境变量
3. OS 环境变量(SPRING_SERVER_PORT)
4. application-{profile}.yml(外部 jar 包外)
5. application-{profile}.yml(jar 包内)
6. application.yml(外部)
7. application.yml(内部)
8. @PropertySource 导入的配置
外部 > 内部,带 profile > 不带 profile,命令行 > 一切
模块四:Spring MVC
4.1 DispatcherServlet 完整请求流程
用户请求 /user/123
│
▼
┌──────────────┐
│ 过滤器链 │ ← Filter(不是 Spring MVC 的一部分,是 Servlet 规范)
└──────┬───────┘
│
▼
┌──────────────────┐
│ DispatcherServlet │ ← Spring MVC 的核心前端控制器
└──────┬───────────┘
│
▼
① HandlerMapping:找处理器
"根据 /user/123 → UserController.getUser()"
│
▼
② HandlerAdapter:执行处理器
反射调用 getUser(123),返回 ModelAndView 或 @ResponseBody 对象
│
▼
③ [拦截器 postHandle]
│
▼
④ ViewResolver:解析视图
将逻辑视图名 "user" 解析为 /WEB-INF/views/user.jsp
如果是 @ResponseBody → 走 HttpMessageConverter → 写 JSON 到响应
│
▼
⑤ 渲染视图 / 写响应体
│
▼
⑥ [拦截器 afterCompletion]
4.2 拦截器 vs 过滤器 vs AOP
| 维度 | 过滤器 (Filter) | 拦截器 (Interceptor) | AOP |
|---|---|---|---|
| 规范 | Servlet 规范 | Spring MVC | Spring |
| 容器 | Servlet 容器管理 | Spring 容器管理 | Spring 容器管理 |
| 范围 | 所有请求 | 仅 DispatcherServlet 处理的请求 | Spring Bean 的方法 |
| 能力 | 只能操作 Request/Response | 可访问 Handler(Controller)+ ModelAndView | 可访问方法参数和返回值 |
| 粒度 | URL 级别 | URL 级别 | 方法级别 |
模块五:MyBatis
5.1 核心执行流程
SqlSession(门面)
│
▼
Executor(执行器)
├── SimpleExecutor ← 每次新 PreparedStatement(默认)
├── ReuseExecutor ← 复用 PreparedStatement
└── BatchExecutor ← 批量操作
│
▼
StatementHandler(语句处理器)
├── RoutingStatementHandler(路由)
├── SimpleStatementHandler
└── PreparedStatementHandler
│
▼
ParameterHandler(参数处理)
│
▼
TypeHandler(类型转换)
│
▼
JDBC PreparedStatement → 数据库
│
▼
ResultSetHandler(结果集映射)
5.2 一级缓存 vs 二级缓存
| 维度 | 一级缓存(Local Cache) | 二级缓存(Second Level Cache) |
|---|---|---|
| 作用域 | SqlSession 级别 | Mapper 级别(Namespace) |
| 生命周期 | 随 SqlSession 关闭而清除 | 应用级别 |
| 默认开启 | 是 | 否 |
| 清除 | update/delete/insert 后自动清除 | 同左 + 可定时清除 |
| 问题 | 不同 SqlSession 间缓存不一致 | 脏读风险,分布式需额外处理 |
5.3 #{} vs ${}(SQL 注入核心)
xml
<!-- #{}:预编译占位符 → 安全! -->
<select id="getUser" resultType="User">
SELECT * FROM users WHERE name = #{name}
</select>
<!-- 编译为:SELECT * FROM users WHERE name = ? → 参数安全绑定 -->
<!-- ${}:字符串替换 → 危险!SQL 注入风险! -->
<select id="getUser" resultType="User">
SELECT * FROM users ORDER BY ${column}
</select>
<!-- 直接拼接:SELECT * FROM users ORDER BY name -->
<!-- 如果 column = "name; DROP TABLE users;" → 灾难 -->
java
// ${} 的唯一合法场景:
// ① 动态表名/列名(ORDER BY / GROUP BY 的列名)
// ② LIKE 模糊查询(但 #{} + CONCAT('%', #{keyword}, '%') 更好)
// 其他所有场景一律用 #{}
面试题精选(10 道)
Q1. Spring IOC 的理解?Bean 的生命周期?(字节/阿里/美团 最高频)
标准回答:
IOC(控制反转)把对象的创建和管理交给 Spring 容器,通过 DI(依赖注入)将依赖注入给使用方。
Bean 生命周期:实例化 → 属性注入 → Aware 回调 → BeanPostProcessor 前置处理 → @PostConstruct → InitializingBean → init-method → BeanPostProcessor 后置处理(AOP 代理在此生成!)→ 就绪 → 容器关闭时 @PreDestroy → DisposableBean → destroy-method。
最精简记忆:创建 → 注入 → 初始化前后处理 → 就绪 → 销毁。
Q2. Spring 怎么解决循环依赖?为什么用三级缓存?(阿里/字节 超高频)
标准回答:
Spring 通过三级缓存解决单例 Bean 的 Setter/字段注入的循环依赖。一级缓存存成品 Bean,二级缓存存半成品,三级缓存存 ObjectFactory(Lambda)。
流程:A 和 B 互相依赖 → A 实例化后把 ObjectFactory 放入三级缓存 → 发现需要 B → B 实例化 → B 需要 A → 从三级缓存获取 A 的 ObjectFactory → 调用 getObject() 获取 A 的半成品(此处判断是否需 AOP 代理)→ 移入二级缓存 → B 注入 A 完成后放入一级缓存 → A 注入完整的 B 后也放入一级缓存。
为什么需要三级而非两级?因为 AOP。二级存对象则无法在后续得知是否需要代理。三级存 Lambda,getObject() 内部会判断并返回正确的版本(代理或原始对象)。
Q3. @Transactional 失效的场景?(阿里/美团/字节 高频)
标准回答:
六种:① 方法非 public(代理调不到);② 同类方法调用 this.method()(绕过了代理对象);③ try-catch 吞掉异常(Spring 感知不到);④ rollbackFor 未正确设置(默认只回滚 RuntimeException,检查异常不回滚);⑤ 数据库引擎不支持事务(MyISAM);⑥ 多线程调用(新线程不在原事务中)。
Q4. Spring Boot 自动配置原理?(阿里/字节 高频)
标准回答:
@SpringBootApplication 包含 @EnableAutoConfiguration → @Import(AutoConfigurationImportSelector) → 读取 META-INF/spring/...AutoConfiguration.imports 文件 → 加载 N 个自动配置类 → 每个类上都有 @ConditionalOnXxx 条件注解 → 满足条件则创建 Bean。
例如 DataSourceAutoConfiguration:@ConditionalOnClass(DataSource.class) 检查 classpath,@ConditionalOnMissingBean 检查用户是否已自定义,两者都满足才自动创建数据源。
Q5. Spring AOP 的原理?JDK 动态代理和 CGLIB 区别?(阿里/美团)
标准回答:
AOP 通过代理模式将横切逻辑(日志/事务/权限)织入目标方法。JDK 动态代理要求目标类有接口,生成 Proxy 子类实现接口,InvocationHandler 拦截调用。CGLIB 通过 ASM 修改字节码,生成目标类的子类来代理,不能代理 final 类和方法。
Spring Boot 2.x+ 默认使用 CGLIB(即使有接口)。AOP 代理生成发生在 Bean 生命周期的 BeanPostProcessor 后置处理阶段。
Q6. Spring MVC 的请求处理流程?(字节/阿里)
标准回答:
DispatcherServlet 统一接收请求 → HandlerMapping 找到对应的 Controller 方法 → HandlerAdapter 执行 → Controller 返回 ModelAndView 或 @ResponseBody 对象 → ViewResolver 解析视图 或 HttpMessageConverter 写 JSON → 响应返回。
拦截器有 preHandle(Controller 前)、postHandle(Controller 后、视图前)、afterCompletion(视图后)三个切入时机。
Q7. MyBatis #{} 和 ${} 的区别?(阿里/美团 高频)
标准回答:
#{} 是预编译占位符(?),安全防 SQL 注入,自动加引号。{} 是字符串直接拼接,有 SQL 注入风险,只在动态表名/列名/ORDER BY 等必须拼接的场景使用。开发规范:能用 #{} 绝不用 {}。
Q8. Spring 事务传播行为 REQUIRED 和 REQUIRES_NEW 的区别?(字节/阿里)
标准回答:
REQUIRED(默认)有事务则加入,没有则创建。REQUIRES_NEW 不管当前有没有事务都创建新事务,有则挂起当前事务。典型场景:订单创建(REQUIRED) + 日志记录(REQUIRES_NEW),日志写入独立提交,不受订单事务回滚的影响。
Q9. BeanFactory 和 ApplicationContext 区别?(腾讯/阿里)
标准回答:
BeanFactory 是底层 IOC 容器,懒加载(首次 getBean 才初始化),功能简单。ApplicationContext 继承 BeanFactory,预加载(启动时初始化所有单例 Bean),提供 AOP、事件发布、国际化、资源加载等企业功能。日常开发只用 ApplicationContext。
Q10. MyBatis 一级缓存和二级缓存的区别?(美团/字节)
标准回答:
一级缓存是 SqlSession 级别,默认开启,同 Session 内相同查询走缓存,update/delete/insert 后清空。二级缓存是 Mapper 级别(跨 Session),需手动开启,所有 Session 共享,有脏读风险。生产环境慎用二级缓存(分布式场景建议用 Redis 等分布式缓存替代)。
📊 今日知识图谱
Spring 全家桶 DAY 11
│
├── Spring IOC
│ ├── Bean 生命周期 12 步(重点:后置处理生成 AOP 代理)
│ ├── 三级缓存:一级(成品)/二级(半成品)/三级(ObjectFactory)
│ │ └── 为什么三级?AOP 代理的不确定性!
│ └── 循环依赖前提:单例 + Setter/字段注入(构造器不行)
│
├── Spring AOP
│ ├── JDK 动态代理(接口) vs CGLIB(继承, Spring Boot 2.x+ 默认)
│ └── @Transactional 失效 6 大场景(非public/this调用/吞异常最常考)
│
├── Spring Boot
│ ├── @SpringBootApplication = @Config + @EnableAutoConfig + @ComponentScan
│ └── 自动配置:读取 .imports → @ConditionalOnXxx → 创建 Bean
│
├── Spring MVC
│ ├── DispatcherServlet → HandlerMapping → HandlerAdapter → ViewResolver
│ └── Filter(最外层) vs Interceptor(Spring MVC) vs AOP(方法级)
│
└── MyBatis
├── SqlSession → Executor → StatementHandler → JDBC
├── #{}预编译(安全) vs ${}字符串拼接(SQL注入风险)
└── 一级缓存(SqlSession,默认开启) vs 二级缓存(Mapper,慎用)
🔜 明日预告
Day 12 --- MySQL + Redis 深度拆解
- B+ 树索引 + 聚簇 vs 非聚簇 + 联合索引最左前缀 + 索引失效 10 场景
- MVCC(ReadView + Undo Log)+ 事务隔离级别 + 锁(Record/Gap/Next-Key)
- SQL 优化:explain 分析 + 覆盖索引 + 索引下推 + 分库分表
- Redis 5 种结构底层(SDS/ziplist/skiplist)+ 缓存穿透/击穿/雪崩
- 持久化 RDB vs AOF vs 混合 + 分布式锁(setnx+Lua)
- 10 道 MySQL + Redis 高频真题
💡 速通心法:Spring 面试三座大山------IOC 的循环依赖(三级缓存 + 为什么不是两级)、AOP 的 @Transactional 失效(6 种场景必须倒背如流)、Boot 的自动配置(读 imports → @Conditional 判断 → 创建 Bean)。这三个讲清楚,Spring 面试就过了一半。另一半是 MyBatis 的 ${} SQL 注入风险和缓存对比。