Java框架篇:Spring + SpringMVC + SpringBoot + MyBatis深度复习

目录

  • 一、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的实际应用

以操作日志为例,典型实现思路:

  1. 定义一个日志注解(如 @OperationLog)
  2. 定义切面类,使用环绕通知 + 切点表达式匹配带注解的方法
  3. 在环绕通知中获取方法信息、参数、返回结果、操作人等
  4. 将日志信息保存到数据库

面试回答要点:

  • 先解释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。

这个选择器会做什么呢?

  1. 读取 classpath 下 META-INF/spring.factories 文件

  2. 找到 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的配置

  3. 获取所有自动配置类的全类名

  4. 根据条件注解(@Conditional系列)判断哪些配置类需要生效

  5. 将生效的配置类中的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?

步骤:

  1. 创建一个autoconfigure模块,写自动配置类
  2. 在META-INF/spring.factories中配置自动配置类
  3. 创建starter模块,依赖autoconfigure模块
  4. 引入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动态代理实现的,具体流程:

  1. 使用CGLIB创建目标对象的代理对象

  2. 当调用代理对象的getXxx()方法时(如user.getOrders())

  3. 进入拦截器intercept方法

  4. 检查orders属性是否为null

  5. 如果为null → 执行SQL查询数据

  6. 查询到数据后,调用set方法设置属性值

  7. 再调用get方法就能拿到数据了

通俗理解: 给你返回的不是真实的User对象,而是一个代理对象。当你调用getOrders()时,代理对象发现orders还没加载,就先去查数据库,查完了再返回给你。

面试回答要点:

  • 解释什么是延迟加载
  • 说明适用场景(关联查询)
  • 说清楚底层原理是CGLIB动态代理
  • 描述代理对象拦截方法、判断null、执行SQL的过程

4.3 一级缓存与二级缓存

MyBatis有两级缓存机制,用于提高查询性能。

一级缓存(本地缓存)

|---------------|-------------------------------------|
| 特性 | 说明 |
| 作用域 | SqlSession级别,同一个SqlSession内共享 |
| 底层实现 | PerpetualCache,基于HashMap存储 |
| 默认状态 | 默认开启,不能关闭 |
| 缓存key | MappedStatementId + 参数 + 分页 + SQL语句 |

一级缓存的工作流程:

  1. 第一次查询:执行SQL,查询数据库,结果放入一级缓存
  2. 第二次查询:先查一级缓存,如果有直接返回,不查数据库
  3. 如果中间执行了增删改操作 → 清空一级缓存

一级缓存失效的情况:

  • 不同的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 |

六、总结与复习建议

复习优先级建议

  1. 第一优先级(必背):Bean生命周期、循环依赖三级缓存、SpringMVC执行流程、SpringBoot自动配置原理、事务失效场景
  2. 第二优先级(熟练掌握):AOP原理、MyBatis执行流程、延迟加载原理、两级缓存、单例线程安全
  3. 第三优先级(了解即可):各种注解分类、传播行为、隔离级别等

面试回答技巧

  1. 先总后分:先总述大概分几个部分,再展开细说
  2. 结合源码:适当提到一些源码中的类名或方法名,体现深度
  3. 结合项目:说"我在项目中遇到过XX问题,当时是这样解决的"
  4. 画图辅助:如果是现场面试,可以画流程图帮助说明
  5. 诚实面对:不会的就说不太了解,但可以说说自己的理解,不要瞎编

学习路径建议

基础使用 → 原理理解 → 源码阅读 → 项目实践 → 面试输出

  1. 先会用这些框架做项目
  2. 再理解核心原理(看博客、看视频)
  3. 有能力的话读读源码(重点看核心流程)
  4. 在项目中遇到问题,深入排查
  5. 面试前系统梳理,按考点输出
相关推荐
Lyra_Infra1 小时前
Java 应用启动脚本 JDK 路径适配优化文档
java·shell
CodeHackerBhx2 小时前
Spring Boot 4 防重复提交:从接口幂等到 Redis 分布式锁的完整实践
java·数据库·spring boot·redis·分布式
xlxxy_2 小时前
外部系统调用SAP接口遇到的一些报错
开发语言·数据库·sap·abap
八角.。2 小时前
方法参数与Debug按键
java·开发语言·jvm
BerryS3N2 小时前
Java在人工智能与大模型时代的深度演进:从工程落地、高性能计算到企业级Agent与RAG架构实战指南
java·人工智能·架构
郝亚军3 小时前
FE1.1S-BQFN24BT可以指定IP端口吗?
开发语言·php
阿里嘎多学长3 小时前
2026-08-04 GitHub 热点项目精选
开发语言·程序员·github·代码托管
蔬菜_3 小时前
前端转全栈-day2(修饰符)
java
fīɡЙtīиɡ ℡3 小时前
怎么是用JWT
java·状态模式