目录
- 一、Spring核心篇
- [1.1 IOC容器与Bean生命周期](#1.1 IOC容器与Bean生命周期)
- [1.2 循环依赖与三级缓存机制](#1.2 循环依赖与三级缓存机制)
- [1.3 AOP面向切面编程](#1.3 AOP面向切面编程)
- [1.4 Spring事务管理](#1.4 Spring事务管理)
- [1.5 单例Bean的线程安全问题](#1.5 单例Bean的线程安全问题)
- [1.6 Spring常用注解汇总](#1.6 Spring常用注解汇总)
- 二、SpringMVC篇
- [2.1 SpringMVC执行流程详解](#2.1 SpringMVC执行流程详解)
- [2.2 SpringMVC常用注解](#2.2 SpringMVC常用注解)
- 三、SpringBoot篇
- [3.1 自动配置原理深度解析](#3.1 自动配置原理深度解析)
- [3.2 SpringBoot核心注解](#3.2 SpringBoot核心注解)
- 四、MyBatis篇
- [4.1 MyBatis执行流程](#4.1 MyBatis执行流程)
- [4.2 延迟加载原理](#4.2 延迟加载原理)
- [4.3 一级缓存与二级缓存](#4.3 一级缓存与二级缓存)
一、Spring核心篇
Spring是Java企业级开发的基石,其核心思想是控制反转(IOC)和面向切面编程(AOP)。面试中Spring相关问题占比最高,需要重点掌握。
1.1 IOC容器与Bean生命周期
什么是IOC?
IOC(Inversion of Control,控制反转) 是Spring的核心思想,它将对象的创建和依赖关系的维护交给Spring容器来管理,而不是由开发者手动new对象。
- 控制反转:对象的创建权从程序员手中反转到了Spring容器
- DI(Dependency Injection,依赖注入):是IOC的实现方式,Spring通过依赖注入将对象需要的依赖自动注入进去
Bean的完整生命周期
Bean的生命周期是Spring面试最高频的考点之一,必须完整掌握。整个流程可以分为以下几个阶段:
BeanDefinition → 实例化 → 属性注入 → Aware接口 → 前置处理器 → 初始化 → 后置处理器 → 使用 → 销毁
详细步骤拆解:
|------------------------------------------|-------------------------------------------------------------|-----------------------------------------------------|
| 阶段 | 说明 | 关键接口/注解 |
| 1. BeanDefinition加载 | 通过BeanDefinition获取bean的定义信息,封装了bean的所有元数据(类全路径、是否单例、是否懒加载等) | BeanDefinition接口 |
| 2. 实例化Bean | 调用构造函数创建bean对象(此时只是一个空对象,属性还未赋值) | 构造函数 |
| 3. 属性注入 | 完成依赖注入,如setter方法注入、@Autowired注入等 | @Autowired、setter方法 |
| 4. Aware接口处理 | 如果bean实现了Aware相关接口,会回调相应方法注入资源 | BeanNameAware、ApplicationContextAware等 |
| 5. BeanPostProcessor前置处理 | 在初始化之前执行,可以对bean进行修改包装 | BeanPostProcessor.postProcessBeforeInitialization() |
| 6. 初始化Bean | 执行初始化逻辑 | InitializingBean接口、init-method、@PostConstruct |
| 7. BeanPostProcessor后置处理 | 在初始化之后执行,AOP代理就是在这里产生的 | BeanPostProcessor.postProcessAfterInitialization() |
| 8. 使用Bean | 从容器中获取并使用bean | - |
| 9. 销毁Bean | 容器关闭时执行销毁逻辑 | DisposableBean接口、destroy-method、@PreDestroy |
面试回答要点:
- 先总述大概分几个阶段
- 重点突出BeanPostProcessor后置处理器是AOP代理生成的地方
- 提到BeanDefinition这个核心概念,体现对源码的理解
- 可以举例说明Aware接口有哪些(BeanNameAware、ApplicationContextAware、BeanFactoryAware)
1.2 循环依赖与三级缓存机制
什么是循环依赖?
循环依赖就是循环引用,指两个或两个以上的bean互相持有对方,最终形成闭环。例如:A依赖B,B又依赖A。
A → B → A (形成闭环)
Spring能解决哪些循环依赖?
|-------------------|-------|----------------------|
| 循环依赖场景 | 是否能解决 | 原因 |
| 单例bean的setter注入 | ✅ 可以 | 通过三级缓存解决 |
| 单例bean的构造器注入 | ❌ 不行 | 构造函数执行时对象还没创建,无法提前暴露 |
| prototype(多例)bean | ❌ 不行 | 多例bean每次都新建,不缓存 |
三级缓存详解
Spring通过三级缓存来解决单例bean的循环依赖问题,这是面试必问的核心知识点。
三级缓存分别是:
|--------------|-----------------------|-------------------|---------------------------|
| 缓存级别 | 缓存名称 | 存储内容 | 说明 |
| 一级缓存 | singletonObjects | 完整的bean对象 | 已经经历完整生命周期、初始化完成的bean |
| 二级缓存 | earlySingletonObjects | 早期的bean对象 | 实例化完成但属性还没注入的bean(半成品) |
| 三级缓存 | singletonFactories | ObjectFactory对象工厂 | 用来创建bean的工厂,可以生成普通对象或代理对象 |
三级缓存对应的源码Map:
// 一级缓存:单例池,存放完全初始化好的bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存放早期的bean(实例化了但还没属性注入)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:存放bean工厂对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
循环依赖解决流程(A依赖B,B依赖A)
这是面试最常考的流程题,必须背下来:
第1步:创建A → 实例化A → 将A的ObjectFactory放入三级缓存
第2步:A属性注入 → 发现需要B → 去创建B
第3步:创建B → 实例化B → 将B的ObjectFactory放入三级缓存
第4步:B属性注入 → 发现需要A → 去获取A
第5步:获取A → 先查一级缓存(没有)→ 查二级缓存(没有)→ 查三级缓存
第6步:从三级缓存拿到A的ObjectFactory → 调用getObject()生成A对象
第7步:将生成的A对象放入二级缓存 → 从三级缓存删除A的工厂
第8步:B拿到A对象 → B属性注入完成 → B初始化完成 → B放入一级缓存
第9步:回到A的创建流程 → A拿到B对象 → A属性注入完成
第10步:A初始化完成 → A放入一级缓存 → 清除二级缓存中的A
流程图示意:
创建A
├─ 实例化A
├─ 三级缓存放入A的工厂
└─ 属性注入 → 需要B → 创建B
├─ 实例化B
├─ 三级缓存放入B的工厂
└─ 属性注入 → 需要A → 获取A
├─ 一级缓存?无
├─ 二级缓存?无
├─ 三级缓存?有A的工厂
├─ 工厂生成A对象
├─ A放入二级缓存
└─ 返回A给B
├─ B注入A完成
├─ B初始化完成
└─ B放入一级缓存
├─ A注入B完成
├─ A初始化完成
└─ A放入一级缓存
为什么需要三级缓存?二级缓存不行吗?
这是一个高频追问问题,核心原因是:
如果bean需要被AOP代理,三级缓存可以决定返回普通对象还是代理对象。
- 如果只有二级缓存,那二级缓存里放的是普通对象还是代理对象?不确定
- 三级缓存的ObjectFactory是一个工厂,可以根据情况生成不同的对象:
- 如果bean需要AOP增强 → 生成代理对象
- 如果不需要 → 生成普通对象
- 这就是三级缓存存在的核心意义:支持AOP代理情况下的循环依赖
构造方法的循环依赖怎么解决?
Spring无法解决构造方法的循环依赖,因为构造函数执行时对象还没创建出来,无法提前暴露到三级缓存中。
解决方案:
- 使用 @Lazy 懒加载注解,延迟加载依赖的bean
- 改用setter注入
- 重新设计代码,避免循环依赖(推荐)
面试回答要点:
- 先解释什么是循环依赖
- 说出三级缓存分别存什么
- 按步骤详细描述解决流程
- 解释为什么需要三级缓存(AOP代理)
- 说明构造器循环依赖的解决办法
1.3 AOP面向切面编程
什么是AOP?
AOP(Aspect Oriented Programming,面向切面编程),将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取为公共模块复用,降低系统耦合度。
通俗理解: 在不修改原有业务代码的前提下,对方法进行增强。
AOP的应用场景
|--------------|--------------------|
| 场景 | 说明 |
| 日志记录 | 方法执行前后记录日志 |
| 事务管理 | 方法开启前开启事务,结束后提交/回滚 |
| 权限校验 | 方法执行前检查是否有权限 |
| 性能监控 | 统计方法执行耗时 |
| 异常处理 | 统一捕获处理异常 |
AOP核心概念
|------------------------|-------------------------|
| 概念 | 说明 |
| Aspect(切面) | 横切关注点的模块化,就是你写的增强逻辑所在的类 |
| JoinPoint(连接点) | 程序执行过程中的某个点,一般指方法 |
| Pointcut(切点) | 对哪些连接点进行拦截的定义(表达式匹配) |
| Advice(通知/增强) | 在切点上执行的动作,分前置/后置/环绕等 |
| Target(目标对象) | 被代理的原始对象 |
| Proxy(代理对象) | AOP框架生成的代理对象 |
| Weaving(织入) | 将切面应用到目标对象并创建代理对象的过程 |
五种通知类型
|--------------|-----------------|--------------------------|
| 通知类型 | 注解 | 执行时机 |
| 前置通知 | @Before | 方法执行前执行 |
| 后置通知 | @After | 方法执行后执行(无论成功还是异常) |
| 返回通知 | @AfterReturning | 方法正常返回后执行 |
| 异常通知 | @AfterThrowing | 方法抛出异常后执行 |
| 环绕通知 | @Around | 方法执行前后都执行,最强大,可以控制方法是否执行 |
AOP的底层实现:动态代理
Spring AOP的底层是通过动态代理实现的,有两种代理方式:
|-------------------|---------------|------------|
| 代理方式 | 实现原理 | 适用场景 |
| JDK动态代理 | 基于接口,实现目标类的接口 | 目标类有接口时使用 |
| CGLIB动态代理 | 基于继承,继承目标类 | 目标类没有接口时使用 |
Spring AOP默认策略:
- 如果目标对象实现了接口 → 默认用JDK动态代理
- 如果目标对象没有实现接口 → 默认用CGLIB动态代理
- 可以通过配置强制使用CGLIB
项目中AOP的实际应用
以操作日志为例,典型实现思路:
- 定义一个日志注解(如 @OperationLog)
- 定义切面类,使用环绕通知 + 切点表达式匹配带注解的方法
- 在环绕通知中获取方法信息、参数、返回结果、操作人等
- 将日志信息保存到数据库
面试回答要点:
- 先解释AOP的概念和作用
- 说出几个核心概念(切面、切点、通知等)
- 说明五种通知类型
- 提到底层是动态代理(JDK和CGLIB)
- 结合项目实际举例(如操作日志、事务)
1.4 Spring事务管理
Spring事务的实现原理
Spring事务的本质就是AOP,通过动态代理对目标方法进行增强:
- 方法执行前:开启事务
- 方法正常执行完:提交事务
- 方法抛出异常:回滚事务
事务的传播行为
事务传播行为是指当一个事务方法被另一个事务方法调用时,事务如何传播。
7种传播行为:
|-----------------------|---------------------------|
| 传播行为 | 说明 |
| REQUIRED | 默认值,如果当前有事务就加入,没有就新建一个 |
| SUPPORTS | 有事务就加入,没有就以非事务方式执行 |
| MANDATORY | 必须在事务中运行,没有事务就抛异常 |
| REQUIRES_NEW | 不管有没有事务,都新建一个事务,挂起当前事务 |
| NOT_SUPPORTED | 以非事务方式执行,如果有事务就挂起当前事务 |
| NEVER | 必须非事务运行,有事务就抛异常 |
| NESTED | 如果有事务就嵌套事务执行,没有就新建(保存点机制) |
最常用的是REQUIRED和REQUIRES_NEW,必须掌握。
事务的隔离级别
|--------------------------|---------------------------|
| 隔离级别 | 说明 |
| DEFAULT | 默认,使用数据库默认的隔离级别 |
| READ_UNCOMMITTED | 读未提交,会有脏读 |
| READ_COMMITTED | 读已提交,解决脏读,有不可重复读 |
| REPEATABLE_READ | 可重复读,解决不可重复读,有幻读(MySQL默认) |
| SERIALIZABLE | 串行化,解决所有问题,性能最差 |
事务失效的场景(高频考点)
这是面试必问题,必须熟练掌握所有场景:
|---------------------------------|-------------------------------------------|--------------------------------------------------|
| 失效场景 | 原因分析 | 解决方案 |
| 1. 异常被捕获了 | 方法内部try-catch了异常,没有抛出,Spring感知不到异常 | catch后手动抛出 throw new RuntimeException() |
| 2. 抛出的是检查异常 | Spring默认只回滚RuntimeException和Error | 配置 @Transactional(rollbackFor = Exception.class) |
| 3. 方法不是public的 | Spring AOP只能拦截public方法 | 将方法改为public |
| 4. 同类内部调用 | 同一个类中方法A调用方法B,B的事务不生效(因为走的是this调用,不是代理对象) | 注入自己,用注入的对象调用;或拆到不同类 |
| 5. 类没有被Spring管理 | 类上没有加@Service等注解,没有被Spring容器管理 | 加上注解让Spring管理 |
| 6. 多线程调用 | 事务是基于线程的,多线程下不在同一个事务中 | 避免在事务中开新线程操作数据库 |
| 7. 数据库不支持事务 | 如MyISAM引擎不支持事务 | 使用支持事务的引擎(InnoDB) |
面试回答技巧:
- 先讲原理(AOP动态代理)
- 按重要性排序说出3-5个最常见的场景
- 每个场景说清楚原因和解决办法
- 可以说"我在项目中实际遇到过XX场景"增加真实感
1.5 单例Bean的线程安全问题
Spring单例bean是线程安全的吗?
答案:不是线程安全的。
为什么?
当多个请求(多个线程)同时调用同一个单例bean的方法时:
- 如果方法中没有对成员变量进行修改 → 线程安全
- 如果方法中有对成员变量的写操作 → 线程不安全
Spring容器中的单例bean,整个应用只有一个实例,所有线程共享这个实例。如果bean有状态(成员变量会被修改),就会有线程安全问题。
什么情况下是安全的?
我们平时开发的Service、DAO类,通常都是无状态的(没有会被修改的成员变量),所以是线程安全的。
// 线程安全:没有可修改的成员变量
@Service
public class UserService {
@Autowired
private UserDao userDao; // 依赖注入的也是单例,不会被修改
public User getById(Long id) {
return userDao.selectById(id); // 方法内的局部变量是线程安全的
}
}
什么情况下不安全?
// 线程不安全:有会被修改的成员变量
@Service
public class CountService {
private int count = 0; // 成员变量,多线程同时修改会有问题
public void add() {
count++; // 不是原子操作,多线程下会丢失更新
}
}
解决方案
|----------------------------------|------------------------------|
| 方案 | 说明 |
| 1. 改为多例 | @Scope("prototype"),每个线程一个实例 |
| 2. 使用ThreadLocal | 将状态变量放在ThreadLocal中,线程隔离 |
| 3. 加锁 | 使用synchronized或Lock保证同步 |
| 4. 用原子类 | 如AtomicInteger,保证操作原子性 |
| 5. 避免有状态 | 尽量设计成无状态的bean(推荐) |
面试回答要点:
- 明确回答:不是线程安全的
- 解释原因:单例模式下多线程共享实例
- 区分有状态和无状态bean
- 给出几种解决方案
1.6 Spring常用注解汇总
声明Bean的注解
|-------------------------|-----------|-------------------------------|
| 注解 | 说明 | 使用场景 |
| @Component | 通用组件注解 | 不属于以下几类的通用组件 |
| @Service | 业务层组件 | Service层 |
| @Repository | 数据访问层组件 | DAO层,持久层 |
| @Controller | 控制层组件 | Controller层,SpringMVC中 |
| @RestController | REST风格控制层 | = @Controller + @ResponseBody |
依赖注入相关注解
|--------------------|-------------------|----------------------------|
| 注解 | 说明 | 区别 |
| @Autowired | 按类型注入(byType) | Spring提供的注解,required默认true |
| @Qualifier | 配合@Autowired按名称注入 | 当一个类型有多个bean时使用 |
| @Resource | 默认按名称注入,找不到按类型 | JDK提供的注解,name属性指定名称 |
| @Value | 注入普通值/配置文件值 | 注入基本类型、字符串、配置项 |
作用域注解
|----------------|------------------|
| 注解 | 说明 |
| @Scope | 设置bean的作用域 |
| | singleton:单例(默认) |
| | prototype:多例 |
| | request:每次请求一个 |
| | session:每个会话一个 |
配置类相关注解
|-------------------------|------------------|
| 注解 | 说明 |
| @Configuration | 声明配置类,替代xml配置 |
| @ComponentScan | 组件扫描,指定扫描哪些包 |
| @Bean | 在配置类中声明一个bean |
| @Import | 导入其他配置类 |
| @PropertySource | 加载properties配置文件 |
AOP相关注解
|---------------------------------|-----------|
| 注解 | 说明 |
| @Aspect | 声明切面类 |
| @Pointcut | 定义切点表达式 |
| @Before | 前置通知 |
| @After | 后置通知 |
| @AfterReturning | 返回通知 |
| @AfterThrowing | 异常通知 |
| @Around | 环绕通知 |
| @EnableAspectJAutoProxy | 开启AOP注解支持 |
事务相关注解
|--------------------------------------|----------|
| 注解 | 说明 |
| @Transactional | 声明事务 |
| @EnableTransactionManagement | 开启事务注解支持 |
二、SpringMVC篇
SpringMVC是Spring框架的一个模块,用于Web层开发,基于MVC设计模式。
2.1 SpringMVC执行流程详解
这是SpringMVC面试最高频的问题,必须完整背下来。
完整执行流程(11步)
用户请求 → DispatcherServlet → HandlerMapping → HandlerAdapter → Handler → ModelAndView → ViewResolver → View → 响应
详细步骤:
|------------|----------------------------|----------------------------------------------------------------|
| 步骤 | 组件 | 说明 |
| 1 | DispatcherServlet | 用户发送请求到前端控制器DispatcherServlet(调度中心) |
| 2 | DispatcherServlet | DispatcherServlet收到请求,调用HandlerMapping处理器映射器 |
| 3 | HandlerMapping | 处理器映射器找到具体的处理器(根据xml配置或注解查找),生成处理器对象和拦截器链,返回给DispatcherServlet |
| 4 | DispatcherServlet | DispatcherServlet调用HandlerAdapter处理器适配器 |
| 5 | HandlerAdapter | 处理器适配器经过适配,调用具体的Handler(Controller) |
| 6 | Handler/Controller | Controller执行业务逻辑,返回ModelAndView对象 |
| 7 | HandlerAdapter | HandlerAdapter将执行结果ModelAndView返回给DispatcherServlet |
| 8 | DispatcherServlet | DispatcherServlet将ModelAndView传给ViewResolver视图解析器 |
| 9 | ViewResolver | 视图解析器解析后返回具体的View视图 |
| 10 | DispatcherServlet | DispatcherServlet根据View进行视图渲染,将模型数据填充到视图中 |
| 11 | DispatcherServlet | DispatcherServlet响应用户请求 |
核心组件说明
|---------------------------|-------------------------------------|
| 组件 | 作用 |
| DispatcherServlet | 前端控制器,整个流程的调度中心,负责接收请求、分发请求、响应结果 |
| HandlerMapping | 处理器映射器,根据请求URL找到对应的Handler(处理器) |
| HandlerAdapter | 处理器适配器,按照特定规则去执行Handler(适配不同类型的处理器) |
| Handler | 处理器,就是我们写的Controller,处理具体业务逻辑 |
| ViewResolver | 视图解析器,根据视图名解析出具体的视图对象 |
| View | 视图,将模型数据渲染成页面展示给用户 |
前后端分离模式下的变化
现在的开发基本都是前后端分离,Controller方法返回JSON数据而不是视图:
- Controller方法上加 @ResponseBody 注解
- 返回的对象会被转换成JSON直接写入响应体
- 流程中就没有ViewResolver和View渲染这两步了
- @RestController = @Controller + @ResponseBody
面试回答要点:
- 按顺序说出11步流程
- 说出每个核心组件的作用
- 提到前后端分离模式下的变化(没有视图解析)
- 可以画个流程图辅助说明
2.2 SpringMVC常用注解
请求映射注解
|-------------------------|-----------------------------------------|
| 注解 | 说明 |
| @RequestMapping | 通用请求映射,可以指定路径、请求方法等 |
| @GetMapping | GET请求映射 = @RequestMapping(method = GET) |
| @PostMapping | POST请求映射 |
| @PutMapping | PUT请求映射 |
| @DeleteMapping | DELETE请求映射 |
| @PatchMapping | PATCH请求映射 |
请求参数相关注解
|------------------------|------------------------|---------------------------------------------|
| 注解 | 说明 | 示例 |
| @RequestParam | 获取请求参数(query参数或form表单) | @RequestParam("id") Long id |
| @PathVariable | 获取路径变量 | @PathVariable("id") Long id → /user/{id} |
| @RequestBody | 获取请求体,将JSON转成Java对象 | @RequestBody User user |
| @RequestHeader | 获取请求头 | @RequestHeader("token") String token |
| @CookieValue | 获取Cookie值 | @CookieValue("JSESSIONID") String sessionId |
| @RequestPart | 获取文件上传的文件 | @RequestPart("file") MultipartFile file |
响应相关注解
|-------------------------|-------------------------------|
| 注解 | 说明 |
| @ResponseBody | 将返回值转成JSON写入响应体 |
| @ResponseStatus | 设置响应状态码 |
| @RestController | = @Controller + @ResponseBody |
其他常用注解
|----------------------------|-----------------------------------|
| 注解 | 说明 |
| @ControllerAdvice | 全局异常处理/全局数据绑定/全局数据预处理 |
| @ExceptionHandler | 异常处理方法,配合@ControllerAdvice做全局异常处理 |
| @InitBinder | 请求数据绑定预处理 |
| @ModelAttribute | 模型数据绑定 |
| @CrossOrigin | 跨域支持 |
| @SessionAttributes | 将模型数据存入session |
三、SpringBoot篇
SpringBoot是Spring的脚手架,约定大于配置,简化了Spring应用的初始搭建和开发过程。
3.1 自动配置原理深度解析
自动配置是SpringBoot最核心的特性,也是面试必问。
@SpringBootApplication注解
SpringBoot启动类上的 @SpringBootApplication 是一个组合注解,由三个核心注解组成:
@SpringBootApplication
├─ @SpringBootConfiguration → 标记这是一个配置类(本质就是@Configuration)
├─ @EnableAutoConfiguration → 开启自动配置(核心!)
└─ @ComponentScan → 组件扫描,默认扫描启动类所在包及其子包
自动配置核心:@EnableAutoConfiguration
@EnableAutoConfiguration 是自动配置的核心注解,它通过 @Import 导入了一个配置选择器 AutoConfigurationImportSelector。
这个选择器会做什么呢?
-
读取 classpath 下 META-INF/spring.factories 文件
-
找到 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的配置
-
获取所有自动配置类的全类名
-
根据条件注解(@Conditional系列)判断哪些配置类需要生效
-
将生效的配置类中的Bean注册到Spring容器
spring.factories文件
这是自动配置的关键文件,位于SpringBoot的autoconfigure包中:
自动配置类列表
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
... 很多很多自动配置类
条件注解
自动配置类上会有各种条件注解,决定这个配置类是否生效:
|-----------------------------------------|---------------------------|
| 条件注解 | 说明 |
| @ConditionalOnClass | classpath下有指定的类才生效 |
| @ConditionalOnMissingClass | classpath下没有指定的类才生效 |
| @ConditionalOnBean | 容器中有指定的Bean才生效 |
| @ConditionalOnMissingBean | 容器中没有指定的Bean才生效(用户自定义的优先) |
| @ConditionalOnProperty | 配置文件中有指定的属性才生效 |
| @ConditionalOnWebApplication | 是Web应用才生效 |
| @ConditionalOnNotWebApplication | 不是Web应用才生效 |
| @ConditionalOnExpression | 满足SpEL表达式才生效 |
举个例子:Redis自动配置
@Configuration
@ConditionalOnClass(RedisOperations.class) // 有RedisOperations类才生效
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 容器中没有这个bean才注册
public RedisTemplate redisTemplate() {
// ...
}
}
自动配置流程总结
启动应用
↓
@SpringBootApplication
↓
@EnableAutoConfiguration
↓
@Import(AutoConfigurationImportSelector)
↓
读取META-INF/spring.factories
↓
获取所有自动配置类
↓
条件注解过滤(@ConditionalOnClass等)
↓
生效的配置类注册Bean到容器
↓
自动配置完成
自定义starter的原理(扩展知识)
面试可能会追问:如何自定义一个SpringBoot Starter?
步骤:
- 创建一个autoconfigure模块,写自动配置类
- 在META-INF/spring.factories中配置自动配置类
- 创建starter模块,依赖autoconfigure模块
- 引入starter后自动配置就会生效
面试回答要点:
- 先说@SpringBootApplication是组合注解,包含哪三个
- 重点讲@EnableAutoConfiguration
- 提到spring.factories文件
- 说明条件注解的作用
- 可以举个具体的例子(如RedisAutoConfiguration)
3.2 SpringBoot核心注解
启动类注解
|----------------------------------|------------------------------------------------------------------------|
| 注解 | 说明 |
| @SpringBootApplication | 启动类核心注解,组合注解 |
| | = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan |
| @EnableAutoConfiguration | 开启自动配置 |
| @SpringBootConfiguration | 标记为配置类(就是@Configuration) |
配置相关注解
|----------------------------------------|------------------------------|
| 注解 | 说明 |
| @ConfigurationProperties | 将配置文件中的属性绑定到JavaBean |
| @EnableConfigurationProperties | 开启@ConfigurationProperties支持 |
| @Value | 注入单个配置值 |
| @PropertySource | 加载指定的properties文件 |
条件注解
见上一节,各种@ConditionalOnXxx注解。
Web相关注解
SpringBoot的Web开发注解基本和SpringMVC一样,额外的有:
|--------------------------------|--------------------------|
| 注解 | 说明 |
| @SpringBootApplication | 启动类 |
| @EnableWebMvc | 完全自己控制MVC配置(不推荐,会丢失自动配置) |
测试相关注解
|-------------------------|-----------------------|
| 注解 | 说明 |
| @SpringBootTest | SpringBoot测试注解,启动整个容器 |
| @WebMvcTest | 只测试Web层 |
| @DataJpaTest | 只测试JPA数据层 |
| @MockBean | 模拟一个Bean |
四、MyBatis篇
MyBatis是一个半ORM持久层框架,支持定制化SQL、存储过程和高级映射。
4.1 MyBatis执行流程
这是MyBatis面试最基础也是最高频的问题。
完整执行流程(7步)
读取配置文件 → 创建SqlSessionFactory → 创建SqlSession → Executor → MappedStatement → 参数映射 → 结果映射
详细步骤:
|-----------|----------------------------------|--------------------------------------------------------------|
| 步骤 | 组件 | 说明 |
| 1 | 读取配置文件 | 读取MyBatis配置文件(mybatis-config.xml),加载运行环境和映射文件(Mapper.xml) |
| 2 | SqlSessionFactoryBuilder | 根据配置信息构建SqlSessionFactory会话工厂 |
| 3 | SqlSessionFactory | 会话工厂创建SqlSession对象(包含执行SQL的所有方法) |
| 4 | Executor | SqlSession内部通过Executor执行器来操作数据库,同时负责查询缓存的维护 |
| 5 | MappedStatement | Executor的执行方法接收MappedStatement参数,封装了SQL映射信息(SQL语句、参数类型、结果类型) |
| 6 | 输入参数映射 | 将Java对象(如Map、POJO)映射到SQL的参数 |
| 7 | 输出结果映射 | 将SQL执行结果(ResultSet)映射到Java对象 |
核心组件说明
|----------------------------------|------------------------------------|
| 组件 | 作用 |
| SqlSessionFactoryBuilder | 构建器,根据配置创建SqlSessionFactory,用完就可以丢 |
| SqlSessionFactory | 会话工厂,创建SqlSession,单例,整个应用一个就够了 |
| SqlSession | 会话,和数据库交互的入口,不是线程安全的,每次请求新建一个 |
| Executor | 执行器,SqlSession内部通过它来执行SQL |
| MappedStatement | 映射语句,封装了一条SQL的所有信息 |
和Spring整合后的变化
和Spring整合后,SqlSessionFactory由Spring管理:
- Spring通过SqlSessionFactoryBean创建SqlSessionFactory
- Mapper接口由Spring扫描并创建代理对象
- 我们直接@Autowired注入Mapper使用就行
面试回答要点:
- 按顺序说出7步流程
- 说明每个核心组件的作用
- 提到SqlSessionFactory是单例,SqlSession不是线程安全的
- 可以说和Spring整合后的使用方式
4.2 延迟加载原理
什么是延迟加载?
延迟加载(懒加载):就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。
使用场景: 主要用于关联查询。比如查询用户时,用户关联的订单数据,只有当你调用 user.getOrders() 时才去查订单表,而不是一开始就把订单一起查出来。
好处: 提高性能,避免一次性加载过多数据。
MyBatis支持哪些延迟加载?
|--------------------------|----------|
| 关联类型 | 是否支持延迟加载 |
| 一对一(association) | ✅ 支持 |
| 一对多(collection) | ✅ 支持 |
如何开启延迟加载?
全局配置(mybatis-config.xml):
<settings>
<!-- 开启延迟加载,默认是false -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭积极加载,按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
单个映射文件配置:
<!-- 一对一,设置fetchType="lazy" -->
<association property="dept" column="dept_id"
select="com.example.mapper.DeptMapper.findById"
fetchType="lazy"/>
<!-- 一对多,设置fetchType="lazy" -->
<collection property="orders" column="user_id"
select="com.example.mapper.OrderMapper.findByUserId"
fetchType="lazy"/>
延迟加载的底层原理
核心:CGLIB动态代理
延迟加载的底层是通过CGLIB动态代理实现的,具体流程:
-
使用CGLIB创建目标对象的代理对象
-
当调用代理对象的getXxx()方法时(如user.getOrders())
-
进入拦截器intercept方法
-
检查orders属性是否为null
-
如果为null → 执行SQL查询数据
-
查询到数据后,调用set方法设置属性值
-
再调用get方法就能拿到数据了
通俗理解: 给你返回的不是真实的User对象,而是一个代理对象。当你调用getOrders()时,代理对象发现orders还没加载,就先去查数据库,查完了再返回给你。
面试回答要点:
- 解释什么是延迟加载
- 说明适用场景(关联查询)
- 说清楚底层原理是CGLIB动态代理
- 描述代理对象拦截方法、判断null、执行SQL的过程
4.3 一级缓存与二级缓存
MyBatis有两级缓存机制,用于提高查询性能。
一级缓存(本地缓存)
|---------------|-------------------------------------|
| 特性 | 说明 |
| 作用域 | SqlSession级别,同一个SqlSession内共享 |
| 底层实现 | PerpetualCache,基于HashMap存储 |
| 默认状态 | 默认开启,不能关闭 |
| 缓存key | MappedStatementId + 参数 + 分页 + SQL语句 |
一级缓存的工作流程:
- 第一次查询:执行SQL,查询数据库,结果放入一级缓存
- 第二次查询:先查一级缓存,如果有直接返回,不查数据库
- 如果中间执行了增删改操作 → 清空一级缓存
一级缓存失效的情况:
- 不同的SqlSession(每个SqlSession有自己的缓存)
- 同一个SqlSession但查询条件不同
- 同一个SqlSession两次查询之间执行了增删改
- 同一个SqlSession两次查询之间手动清空了缓存(sqlSession.clearCache())
二级缓存(全局缓存)
|---------------|-----------------------------------|
| 特性 | 说明 |
| 作用域 | Mapper级别(namespace),跨SqlSession共享 |
| 底层实现 | PerpetualCache,基于HashMap存储 |
| 默认状态 | 默认关闭,需要手动开启 |
| 缓存key | 和一级缓存一样 |
| 数据结构 | 每个namespace一个缓存Map |
如何开启二级缓存:
第一步:全局配置开启
<settings>
<setting name="cacheEnabled" value="true"/> <!-- 开启二级缓存总开关 -->
</settings>
第二步:Mapper映射文件中配置
<mapper namespace="com.example.mapper.UserMapper">
<cache/> <!-- 开启这个namespace的二级缓存 -->
</mapper>
或者在Mapper接口上加注解:
@CacheNamespace
public interface UserMapper {
// ...
}
二级缓存的清理时机
当某个namespace下执行了增删改操作时,该namespace下的所有select缓存都会被清空。
注意: 二级缓存是按namespace隔离的。如果一个Mapper的增删改影响了另一个Mapper的查询结果,可能会出现脏读问题。
两级缓存的工作流程
查询请求
↓
先查二级缓存(跨SqlSession)
├─ 命中 → 直接返回
└─ 没命中 → 查一级缓存
├─ 命中 → 放入二级缓存,返回
└─ 没命中 → 查数据库
↓
放入一级缓存
↓
放入二级缓存
↓
返回结果
缓存的使用建议
|------------------------|---------------------------------|
| 建议 | 说明 |
| 一级缓存默认开启 | 一般不用管,同一个SqlSession内自动生效 |
| 二级缓存谨慎使用 | 多表联查容易出现脏数据 |
| 更新频繁的表不建议用缓存 | 缓存经常被清空,反而影响性能 |
| 以Redis等分布式缓存替代 | 生产环境更多用Redis做缓存,而不是MyBatis的二级缓存 |
面试回答要点:
- 先说有两级缓存,一级是SqlSession级别,二级是Mapper级别
- 分别说明各自的特点、作用域、底层实现
- 说清楚什么时候缓存会被清空(增删改)
- 可以说二级缓存要谨慎使用,容易有脏读问题
五、面试高频考点速查表
Spring核心
|----------|-------|----------------------------------------------------------------|
| 考点 | 重要程度 | 关键词 |
| Bean生命周期 | ⭐⭐⭐⭐⭐ | BeanDefinition → 实例化 → 属性注入 → Aware → 前置处理器 → 初始化 → 后置处理器 → 销毁 |
| 循环依赖 | ⭐⭐⭐⭐⭐ | 三级缓存、singletonObjects、earlySingletonObjects、singletonFactories |
| AOP | ⭐⭐⭐⭐⭐ | 切面、切点、通知、JDK动态代理、CGLIB |
| 事务失效场景 | ⭐⭐⭐⭐⭐ | 异常被吞、检查异常、非public、同类调用、没被Spring管理 |
| 单例线程安全 | ⭐⭐⭐⭐ | 有状态/无状态、ThreadLocal、prototype |
| IOC/DI | ⭐⭐⭐⭐ | 控制反转、依赖注入 |
SpringMVC
|------|-------|----------------------------------------------------------------------------------------------------|
| 考点 | 重要程度 | 关键词 |
| 执行流程 | ⭐⭐⭐⭐⭐ | DispatcherServlet → HandlerMapping → HandlerAdapter → Handler → ModelAndView → ViewResolver → View |
| 常用注解 | ⭐⭐⭐⭐ | @RequestMapping、@RequestBody、@ResponseBody、@PathVariable |
SpringBoot
|-----------|-------|-----------------------------------------------------------------------|
| 考点 | 重要程度 | 关键词 |
| 自动配置原理 | ⭐⭐⭐⭐⭐ | @SpringBootApplication、@EnableAutoConfiguration、spring.factories、条件注解 |
| starter原理 | ⭐⭐⭐⭐ | 自动配置类、spring.factories |
MyBatis
|---------|-------|-------------------------------------------------------------|
| 考点 | 重要程度 | 关键词 |
| 执行流程 | ⭐⭐⭐⭐⭐ | SqlSessionFactory → SqlSession → Executor → MappedStatement |
| 延迟加载 | ⭐⭐⭐⭐ | CGLIB动态代理、代理对象拦截 |
| 一级/二级缓存 | ⭐⭐⭐⭐ | SqlSession级别、namespace级别、PerpetualCache |
六、总结与复习建议
复习优先级建议
- 第一优先级(必背):Bean生命周期、循环依赖三级缓存、SpringMVC执行流程、SpringBoot自动配置原理、事务失效场景
- 第二优先级(熟练掌握):AOP原理、MyBatis执行流程、延迟加载原理、两级缓存、单例线程安全
- 第三优先级(了解即可):各种注解分类、传播行为、隔离级别等
面试回答技巧
- 先总后分:先总述大概分几个部分,再展开细说
- 结合源码:适当提到一些源码中的类名或方法名,体现深度
- 结合项目:说"我在项目中遇到过XX问题,当时是这样解决的"
- 画图辅助:如果是现场面试,可以画流程图帮助说明
- 诚实面对:不会的就说不太了解,但可以说说自己的理解,不要瞎编
学习路径建议
基础使用 → 原理理解 → 源码阅读 → 项目实践 → 面试输出
- 先会用这些框架做项目
- 再理解核心原理(看博客、看视频)
- 有能力的话读读源码(重点看核心流程)
- 在项目中遇到问题,深入排查
- 面试前系统梳理,按考点输出