← 学习门户 每日面试题 学习看板

2026-10-11

Java 框架综合 —— Spring / MyBatis / Netty / Dubbo / 微服务治理

10 题 · 平均 3.1/5 · low 3 · medium 3.5 · high 2.67 · 第 1 轮

#1 Hystrix和Sentinel的区别是什么? low java

题目

Hystrix和Sentinel的区别是什么?

我的回答

首先二者都是处理熔断降级操作的。其中 Hystrix 主要就是提供熔断降级操作,但是 Sentinel 除了提供熔断降级,还提供包括限流等一系列其他功能。并且它有一个外部的扩展,可以通过外部的配置去对 Controller 进行指定的控制限流和熔断处理。CentOS 更灵活一些,并且 Debian 现在已经不维护了,CentOS 还在持续的更新维护中。

点评

评分:⭐⭐⭐☆☆
优点:核心区别说对(Sentinel 覆盖限流+熔断+系统保护,Hystrix 只熔断降级);提到 Sentinel Dashboard 配置能力;提到维护状态(Hystrix 2018 Netflix 停维护)
不足:- ❌ **术语严重混淆**:"CentOS / Debian" 应是 "Sentinel / Hystrix"(Linux 发行版 ≠ 熔断框架),是本轮最大失分点 - 漏掉熔断策略区别(Hystrix 滑动窗口 vs Sentinel 慢调用比例/异常比例/异常数) - 漏掉流控效果(快速失败/Warm Up/排队等待/匀速通过) - 漏掉热点参数限流、系统自适应保护
补充:Sentinel 支持热点参数限流、系统自适应保护;Hystrix 基于线程池隔离,Sentinel 基于流量控制;选型:高流量选 Sentinel,需要线程隔离选 Hystrix ---

完整讲解

第一部分:知识重点剖析

**核心考点定位**:考察微服务熔断降级组件的选型理解,区分 Hystrix 和 Sentinel 的功能边界与设计哲学。

**知识点拆解**:
1. **功能范围**:Hystrix 专注熔断降级 + 线程池隔离;Sentinel 覆盖限流 + 熔断降级 + 系统自适应保护 + 热点参数限流 + 实时分析
2. **熔断策略**:Hystrix 基于滑动窗口(统计窗口内请求量和错误率);Sentinel 支持慢调用比例/异常比例/异常数三种熔断策略,更灵活
3. **流控效果**:Sentinel 支持快速失败/Warm Up/排队等待/匀速通过四种流控效果;Hystrix 只有简单的线程池/信号量隔离
4. **性能开销**:Hystrix 基于线程池隔离,开销大;Sentinel 基于流量控制 + 熔断降级,开销小
5. **维护状态**:Hystrix 2018 年 Netflix 宣布停止维护,进入维护模式;Sentinel 阿里持续更新维护

第二部分:考察方向与延伸递进

**考察方向**:
- **深度**:理解熔断降器的核心机制(滑动窗口 vs 实时统计)
- **广度**:关联限流、熔断、降级、隔离四大微服务治理策略
- **应用**:能根据业务场景选型(高流量场景选 Sentinel,需要线程隔离选 Hystrix)

**延伸递进面试题**:
1. **Sentinel 的流控效果有哪些?分别适用什么场景?**
- 解答要点:快速失败(默认)/Warm Up(渐进式流量)/排队等待(匀速通过)/匀速通过(队列长度限制)
2. **Hystrix 的线程池隔离和信号量隔离有什么区别?**
- 解答要点:线程池隔离开销大但隔离性强;信号量隔离开销小但只能限制并发数
3. **Sentinel 的系统自适应保护是什么原理?**
- 解答要点:基于系统负载(load、CPU、网络等指标)自动调整限流阈值,防止系统过载

第三部分:面试者参考回答(三步法口语化,300-400 字)

**【一句话结论】**
面试官您好,Hystrix 专注熔断降级,Sentinel 覆盖限流 + 熔断 + 自适应保护,Sentinel 功能更全且持续维护。

**【三点展开】**
第一,功能范围不同。Hystrix 主要做熔断降级和线程池隔离,Sentinel 除了熔断降级还提供限流、热点参数限流、系统自适应保护,功能更全。

第二,熔断策略不同。Hystrix 基于滑动窗口统计错误率,Sentinel 支持慢调用比例、异常比例、异常数三种熔断策略,更灵活。Sentinel 还有四种流控效果:快速失败、Warm Up、排队等待、匀速通过。

第三,性能和维护状态不同。Hystrix 基于线程池隔离开销大,2018 年 Netflix 停止维护;Sentinel 基于流量控制开销小,阿里持续更新维护。

**【一句边界】**
当然,如果需要强线程隔离(防止下游慢调用拖垮上游),Hystrix 的线程池隔离仍有用武之地;但新项目推荐用 Sentinel,生态更活跃。

#2 Spring中shutdownhook作用是什么? low java

题目

Spring中shutdownhook作用是什么?

我的回答

这个东西具体我没用过,但是从它名字字面理解,应该是在停机时进行的后续处理。在停机的前处理哪些事情,应该是和优雅停机相关的,可以在其中释放资源,做通知、告警之类的操作。

点评

评分:⭐⭐⭐☆☆
优点:诚实承认没直接用过(态度好);核心理解正确(停机时执行);用途理解正确(释放资源、通知、告警)
不足:- 漏掉具体实现方式(DisposableBean、@PreDestroy、Runtime.addShutdownHook) - 漏掉 JVM shutdownhook vs Spring shutdownhook 的层次关系 - 漏掉 Spring Boot 2.3+ 优雅停机配置(server.shutdown=graceful)
补充:JVM shutdownhook 通过 Runtime.addShutdownHook 注册;Spring 通过 @PreDestroy/DisposableBean 生命周期回调;Spring Boot 2.3+ 支持 `server.shutdown=graceful` + `spring.lifecycle.timeout-per-shutdown-phase` ---

完整讲解

第一部分:知识重点剖析

**核心考点定位**:考察 JVM 和 Spring 容器关闭时的生命周期管理,理解 shutdownhook 的用途和实现方式。

**知识点拆解**:
1. **JVM shutdownhook**:
- 通过 `Runtime.getRuntime().addShutdownHook(new Thread(...))` 注册
- JVM 关闭时(正常退出或 System.exit())执行
- 典型用途:释放资源、关闭连接、清理临时文件
2. **Spring shutdownhook**:
- 通过 Spring 容器关闭时的生命周期回调实现
- 方式 1:实现 `DisposableBean` 接口,重写 `destroy()` 方法
- 方式 2:标注 `@PreDestroy` 注解(JSR-250 规范)
- 方式 3:在 `application.yml` 配置自定义 shutdown 方法
3. **Spring Boot 优雅停机**:
- Spring Boot 2.3+ 支持优雅停机
- 配置 `server.shutdown=graceful` 开启
- 配置 `spring.lifecycle.timeout-per-shutdown-phase=30s` 设置超时时间
4. **关闭顺序**:
- Spring 容器关闭 → 调用所有 Bean 的 destroy 方法 → JVM shutdownhook 执行
- Spring 容器关闭时,会触发所有注册了 shutdownhook 的 Bean 的销毁回调

第二部分:考察方向与延伸递进

**考察方向**:
- **深度**:理解 JVM shutdownhook 和 Spring shutdownhook 的区别
- **广度**:关联 Spring 容器生命周期、Bean 生命周期、优雅停机
- **应用**:能在服务停机时正确释放资源、清理数据

**延伸递进面试题**:
1. **JVM shutdownhook 和 Spring 的 @PreDestroy 有什么区别?**
- 解答要点:JVM shutdownhook 是 JVM 级别,Spring @PreDestroy 是 Spring 容器级别;执行顺序不同
2. **Spring Boot 优雅停机怎么配置?工作原理是什么?**
- 解答要点:`server.shutdown=graceful` 开启,Spring 会等待所有请求处理完成后再关闭
3. **shutdownhook 执行超时了怎么办?**
- 解答要点:JVM shutdownhook 默认无超时,可以通过 `spring.lifecycle.timeout-per-shutdown-phase` 设置超时

第三部分:面试者参考回答(三步法口语化,300-400 字)

**【一句话结论】**
面试官您好,shutdownhook 是 JVM 或 Spring 容器关闭时的回调机制,用于释放资源、清理数据。

**【三点展开】**
第一,JVM shutdownhook 通过 `Runtime.getRuntime().addShutdownHook()` 注册,JVM 关闭时执行,典型用途是释放资源、关闭连接。

第二,Spring shutdownhook 通过容器关闭时的生命周期回调实现,方式有实现 `DisposableBean` 接口、标注 `@PreDestroy` 注解、或配置自定义 shutdown 方法。Spring 容器关闭时会触发所有 Bean 的销毁回调。

第三,Spring Boot 2.3+ 支持优雅停机,配置 `server.shutdown=graceful` 开启,可以等待所有请求处理完成后再关闭,配合超时时间设置(如 30 秒)。

**【一句边界】**
当然,shutdownhook 只解决"资源清理"问题,如果资源释放耗时超过超时时间,会强制关闭,需要配合监控告警和合理的超时设置。

#3 什么是Dubbo的优雅停机,怎么实现的? low java

题目

什么是Dubbo的优雅停机,怎么实现的?

我的回答

它大多是用来做 RPC 调用的,它的优雅停机理论上应该是指在调用时把,在,优雅停机,在停机时把已经在途的请求都处理完成,而不是直接停机。

点评

评分:⭐⭐⭐☆☆
优点:核心概念说对(先处理在途请求再停);理解 Dubbo 是 RPC 框架
不足:- 漏掉具体三步流程:摘除注册中心 → 等待在途请求 → 强制关闭 - 漏掉 SIGTERM 信号处理机制 - 漏掉超时时间配置(如 30s 强杀) - 只答了"概念"没有答"怎么实现的",题干要求的"实现机制"完全没涉及
补充:三步流程:①从注册中心下线(防止新请求)→ ②等待已连接请求处理完成 → ③超时强关;通过 JVM shutdownhook 注册;捕获 SIGTERM 触发;设置 shutdown.wait 超时 ---

完整讲解

第一部分:知识重点剖析

**核心考点定位**:考察微服务框架的优雅停机机制,理解如何避免服务重启导致请求丢失。

**知识点拆解**:
1. **优雅停机的定义**:服务停机前,先处理完所有在途请求,再关闭服务,避免请求丢失
2. **Dubbo 优雅停机三步**:
- Step 1:从注册中心摘除服务(ZK/Nacos 下线,防止新请求进来)
- Step 2:等待已连接的请求处理完成(设置超时时间,如 30 秒)
- Step 3:超时后强制关闭服务
3. **实现机制**:
- JVM shutdownhook 注册关闭钩子
- 捕获 SIGTERM 信号(kill -15)触发停机流程
- Dubbo 内置的优雅停机扩展点
4. **常见场景**:
- 服务发布/更新(滚动更新)
- 服务器维护
- 容器化部署(K8s Pod 重建)

第二部分:考察方向与延伸递进

**考察方向**:
- **深度**:理解优雅停机的核心机制(摘除 + 等待 + 关闭)
- **广度**:关联微服务发布流程、容器化部署、滚动更新
- **应用**:能设计服务发布的平滑方案(蓝绿部署、金丝雀发布)

**延伸递进面试题**:
1. **Dubbo 优雅停机和 Spring Boot 优雅停机有什么区别?**
- 解答要点:Dubbo 侧重 RPC 连接处理,Spring Boot 侧重 HTTP 连接处理
2. **K8s 中如何实现 Dubbo 服务的优雅停机?**
- 解答要点:preStop hook + readinessProbe + shutdownhook 配合
3. **优雅停机等待超时了怎么办?**
- 解答要点:设置合理的超时时间,超时后强制关闭,配合监控告警

第三部分:面试者参考回答(三步法口语化,300-400 字)

**【一句话结论】**
面试官您好,Dubbo 优雅停机是停机前先摘除服务、处理完在途请求再关闭,避免请求丢失。

**【三点展开】**
第一,核心目标是避免请求丢失。服务停机前,先处理完所有在途请求,再关闭服务,而不是直接停。

第二,实现三步走。先从注册中心摘除服务(ZK/Nacos 下线,防止新请求进来),然后等待已连接的请求处理完成(设置超时时间,如 30 秒),最后关闭服务。

第三,通过 JVM shutdownhook 实现。捕获 SIGTERM 信号(kill -15)触发停机流程,Dubbo 内置优雅停机扩展点自动执行三步。

**【一句边界】**
当然,优雅停机只解决"在途请求"问题,如果请求处理超过超时时间(如 30 秒),会强制关闭,需要配合监控告警和合理的超时设置。

#4 Mybatis插件的运行原理 medium java

题目

Mybatis插件的运行原理

我的回答

MyBatis 插件的运行原理主要是还是基于代理,将数据库方面的,比如说分页的操作,它将通过代理在执行数据库 SQL 的时候,在数据库执行的 SQL 语句中进行插入代理的操作,把分页方面的内容植入到当前执行的 SQL 中,最终实现插件的分页。其他的插件也是类似原理,主要是在数据库 SQL 执行前进行植入,将自己目标的一些内容植入进去,然后最终实现插件的目标。

点评

评分:⭐⭐⭐☆☆
优点:核心机制说对(JDK 动态代理);抓住分页插件的核心(植入分页 SQL);识别了"before 阶段拦截"
不足:- 漏掉 4 个可拦截接口:Executor / StatementHandler / ParameterHandler / ResultSetHandler - 漏掉 @Intercepts 注解和 Interceptor 接口 - 漏掉 JSqlParser 等 SQL 解析库 - 没讲插件链的拦截顺序
补充:JDK 动态代理包装 4 个接口;@Intercepts 声明拦截点;分页插件用 JSqlParser 解析原 SQL 后加 LIMIT/offset ---

完整讲解

第一部分:知识重点剖析

**核心考点定位**:考察 MyBatis 插件机制的实现原理,理解动态代理在框架中的应用。

**知识点拆解**:
1. **插件机制核心**:
- 基于 JDK 动态代理实现
- 核心接口:`Interceptor`
- 核心注解:`@Intercepts`(标注拦截目标)
2. **4 个可拦截的接口**:
- `Executor`:执行 SQL 操作(增删改查)
- `StatementHandler`:处理 SQL 语句(预编译、参数绑定)
- `ParameterHandler`:处理参数(设置参数值)
- `ResultSetHandler`:处理结果集(结果映射)
3. **拦截流程**:
- `intercept(Object target, Method method, Object[] args)` 方法
- `before` 阶段:修改 SQL 或参数
- `after` 阶段:处理结果
- 调用 `method.invoke()` 执行原始方法
4. **分页插件原理**:
- 拦截 `StatementHandler` 的 `prepare` 方法
- 使用 JSqlParser 解析 SQL
- 添加 LIMIT/OFFSET 分页语句
- 优化 COUNT 查询(去掉 ORDER BY)
5. **常见插件**:
- PageHelper(分页)
- MyBatis-Plus(数据权限、审计字段)
- 多租户插件
- SQL 性能分析插件

第二部分:考察方向与延伸递进

**考察方向**:
- **深度**:理解动态代理的底层实现
- **广度**:关联 JDK 动态代理、CGLIB、MyBatis 核心接口
- **应用**:能开发自定义插件(如数据权限、审计字段)

**延伸递进面试题**:
1. **MyBatis 插件可以拦截哪些接口?**
- 解答要点:Executor、StatementHandler、ParameterHandler、ResultSetHandler
2. **分页插件是怎么实现分页的?**
- 解答要点:拦截 StatementHandler,使用 JSqlParser 解析 SQL,添加 LIMIT/OFFSET
3. **JDK 动态代理和 CGLIB 代理有什么区别?**
- 解答要点:JDK 代理基于接口,CGLIB 基于继承;JDK 代理不能代理 final 方法

第三部分:面试者参考回答(三步法口语化,300-400 字)

**【一句话结论】**
面试官您好,MyBatis 插件基于 JDK 动态代理实现,通过拦截核心接口在 SQL 执行前后植入逻辑。

**【三点展开】**
第一,核心机制是基于 JDK 动态代理。实现 `Interceptor` 接口,通过 `@Intercepts` 注解标注拦截目标,在 `intercept()` 方法中执行 before/after 逻辑。

第二,可以拦截 4 个核心接口:Executor(执行 SQL)、StatementHandler(处理 SQL 语句)、ParameterHandler(处理参数)、ResultSetHandler(处理结果集)。分页插件通常拦截 StatementHandler 的 prepare 方法。

第三,分页插件使用 JSqlParser 解析 SQL,添加 LIMIT/OFFSET 分页语句,优化 COUNT 查询(去掉 ORDER BY)。MyBatis-Plus 的多租户插件也是类似原理。

**【一句边界】**
当然,插件基于动态代理实现,只能拦截接口方法,不能拦截 final 方法或私有方法。

#5 Spring 中注入 Bean 有几种方式 medium java

题目

Spring 中注入 Bean 有几种方式

我的回答

Spring 中注入变量方式,核心应该有 3 种。首先是构造器注入,其次是属性注入,然后是属性 set、参数 set 的注入。理论上应该都使用构造器注入才更安全稳健,但是更多的其实是使用属性注入,直接在参数上做注入,连 set 的注入都很少用。参数注入的注入方式相对构造器跟 set 来说更简洁。

点评

评分:⭐⭐⭐☆☆
优点:3 种核心方式说对(构造器/字段/setter);提到构造器更稳健(不可变);提到实际开发字段注入更常用
不足:- **术语混乱**:"属性注入" 和 "参数 set 的注入" 概念不清,把字段注入和构造器参数注入混着说 - 漏掉 @Value(从配置文件/环境变量注入) - 漏掉 @Qualifier(多同类型 Bean 时指定) - 漏掉"一个构造器时 @Autowired 可省略"最佳实践
补充:Spring 4.3+ 推荐构造器注入;setter 适合可选依赖;字段注入代码简洁但不可测试、依赖容器;@Qualifier 用于同类型多 Bean 消歧 ---

完整讲解

第一部分:知识重点剖析

**核心考点定位**:考察 Spring IoC 容器的依赖注入机制,理解不同注入方式的优缺点和适用场景。

**知识点拆解**:
1. **构造器注入**:
- 通过构造函数参数注入依赖
- 优点:不可变对象、必需依赖、单元测试友好
- 缺点:构造函数参数多时不优雅
- Spring 4.3+ 推荐,只有一个构造器时 @Autowired 可省略
2. **setter 注入**:
- 通过 setter 方法注入依赖
- 优点:可选依赖、可重新注入
- 缺点:不可变对象、单元测试不友好
- 适合可选依赖场景
3. **字段注入(@Autowired 字段)**:
- 直接在字段上标注 @Autowired
- 优点:代码简洁
- 缺点:不可测试、依赖 Spring 容器
- 实际开发中最常用,但不推荐
4. **@Value 注解注入**:
- 从配置文件/环境变量注入值
- 格式:`@Value("${config.property}")`
5. **@Qualifier 注解**:
- 当有多个同类型 Bean 时,指定注入哪个
- 格式:`@Qualifier("beanName")`

第二部分:考察方向与延伸递进

**考察方向**:
- **深度**:理解不同注入方式的底层机制和优缺点
- **广度**:关联 IoC 容器、Bean 生命周期、Spring 配置
- **应用**:能根据场景选择合适的注入方式

**延伸递进面试题**:
1. **构造器注入和 setter 注入怎么选?**
- 解答要点:必需依赖用构造器注入,可选依赖用 setter 注入
2. **字段注入为什么被 Spring 官方不推荐?**
- 解答要点:不可测试、依赖 Spring 容器、无法保证不可变性
3. **@Value 和 @Autowired 有什么区别?**
- 解答要点:@Autowired 注入 Bean,@Value 注入值(从配置文件)

第三部分:面试者参考回答(三步法口语化,300-400 字)

**【一句话结论】**
面试官您好,Spring 中注入 Bean 主要有 3 种方式:构造器注入、setter 注入、字段注入,Spring 4.3+ 推荐构造器注入。

**【三点展开】**
第一,构造器注入通过构造函数参数注入,优点是创建不可变对象、必需依赖、单元测试友好,Spring 4.3+ 如果一个类只有一个构造器,@Autowired 可以省略。

第二,setter 注入通过 setter 方法注入,适合可选依赖、可重新注入的场景,但不适合创建不可变对象。

第三,字段注入直接在字段上标注 @Autowired,代码最简洁,实际开发中最常用,但 Spring 官方不推荐,因为不可测试、依赖 Spring 容器。

**【一句边界】**
当然,除了这 3 种,还有 @Value 注解从配置文件注入值,@Qualifier 注解指定同类型 Bean 中的哪一个,实际开发中需要根据场景灵活选择。

#6 Spring中如何开启事务? medium java

题目

Spring中如何开启事务?

我的回答

Spring 启动事务主要有两种方式。一个是通过 XML 进行配置,另外一个是通过注解进行配置。首先需要配置开启事务支持的注解,通过 Configer 配置注入进去。At,具体的注解是 at Enable Configer Transaction 还是什么,具体不记得了。然后在对应的使用的方法上增加 `@Transactional` 注解,这样对应的方法就开启了事务。另外,启动事务需要有相关的前置支持。首先第一点,对应的方法需要是一个公共的方法,并且当前调用是通过代理调用的,而不是在当前的 class 内部进行调用。因为事务是依赖 Spring 提供的代理来实现的。另外,Spring 的事务虽然是通过代理实现的,但代理也是基于数据库的事务支持才能实现的,所以另一重要条件是需要事务支持。

点评

评分:⭐⭐⭐⭐☆
优点:两种方式说对(XML + 注解);提到 @EnableTransactionManagement 和 @Transactional;主动说了事务失效的三个关键条件(public 方法、代理调用、自调用问题);抓到"Spring 事务依赖 JDBC 事务"底层依赖
不足:- 术语念错:"at Enable Configer Transaction" 应为 @EnableTransactionManagement - 漏掉 @Transactional 常用属性(readOnly/timeout/propagation/isolation/rollbackFor) - 漏掉传播行为 7 种、隔离级别 4 种 - 自调用解决方案没具体说(AopContext.currentProxy()、注入代理、切面拆类)
补充:@Transactional 关键属性;7 种传播行为;4 种隔离级别;自调用解决方案三选一 ---

完整讲解

第一部分:知识重点剖析

**核心考点定位**:考察 Spring 声明式事务的配置方式和失效场景,理解事务的底层实现机制。

**知识点拆解**:
1. **开启事务的两种方式**:
- XML 配置:`<tx:annotation-driven/>` + `<tx:advice>` 定义事务属性
- 注解配置:`@EnableTransactionManagement` + `@Transactional`
2. **@Transactional 常用属性**:
- `readOnly`:是否只读(优化性能)
- `timeout`:超时时间(秒)
- `propagation`:传播行为(7 种)
- `isolation`:隔离级别(4 种)
- `rollbackFor`:指定异常类型回滚
3. **事务传播行为(7 种)**:
- REQUIRED(默认):有事务就加入,没事务就创建
- REQUIRES_NEW:总是新建事务,当前事务挂起
- SUPPORTS:有事务就加入,没事务就非事务执行
- NOT_SUPPORTED:非事务执行,当前事务挂起
- MANDATORY:必须在事务中,否则抛异常
- REQUIRES:同 REQUIRED
- NESTED:嵌套事务
4. **事务隔离级别(4 种)**:
- READ_UNCOMMITTED:读未提交(最低,可能有脏读)
- READ_COMMITTED:读已提交(默认)
- REPEATABLE_READ:可重复读(MySQL 默认)
- SERIALIZABLE:串行化(最高,性能最差)
5. **事务失效的常见场景**:
- 方法不是 public
- 自调用(同一个类内部调用)
- 没有数据库事务支持
- 异常被捕获未抛出
- 不是 Spring 管理的 Bean
- 数据库引擎不支持事务(如 MyISAM)

第二部分:考察方向与延伸递进

**考察方向**:
- **深度**:理解事务的底层实现(AOP 代理 + 数据库事务)
- **广度**:关联事务传播行为、隔离级别、失效场景
- **应用**:能诊断事务失效问题,能根据场景选择合适的事务配置

**延伸递进面试题**:
1. **@Transactional 有哪些常用属性?分别是什么作用?**
- 解答要点:readOnly、timeout、propagation、isolation、rollbackFor
2. **事务传播行为有哪些?REQUIRED 和 REQUIRES_NEW 有什么区别?**
- 解答要点:REQUIRED 有事务就加入,REQUIRES_NEW 总是新建事务
3. **自调用为什么会导致事务失效?怎么解决?**
- 解答要点:自调用不走代理,通过 AopContext.currentProxy() 或代理注入解决

第三部分:面试者参考回答(三步法口语化,300-400 字)

**【一句话结论】**
面试官您好,Spring 开启事务主要有两种方式:XML 配置和注解配置,注解方式更常用。

**【三点展开】**
第一,配置方式有两种。XML 方式用 `<tx:annotation-driven/>`,注解方式用 `@EnableTransactionManagement` 开启事务支持,然后在方法上标注 `@Transactional`。

第二,@Transactional 常用属性包括 readOnly(只读优化)、timeout(超时时间)、propagation(传播行为)、isolation(隔离级别)、rollbackFor(指定异常回滚)。

第三,事务失效的常见场景有:方法不是 public、自调用(同一个类内部调用)、异常被捕获未抛出、不是 Spring 管理的 Bean。自调用是因为不走代理,可以通过 AopContext.currentProxy() 或代理注入解决。

**【一句边界】**
当然,Spring 事务是基于 AOP 代理和数据库事务实现的,如果数据库引擎不支持事务(如 MyISAM),事务也会失效。

#7 使用quartz定时任务实现支付单自动关单功能,并引入多线程+分段解决扫表延迟的问题 medium practice

题目

使用quartz定时任务实现支付单自动关单功能,并引入多线程+分段解决扫表延迟的问题

我的回答

这是一个场景设计,要解决的问题是实现支付订单自动关闭功能。正常情况下使用支付订单自动到期关闭,要么使用 XxLJob 去进行操作,然后在操作中去进行对于历史订单的扫描,然后以相关处理。这边处理的时候,我会涉及几个方向,首先是近期的,也就是短期内,创建时间在半个小时,或者是业务的规定时间,比如半个小时或者一个小时来进行扫描,扫描这个阶段的订单,并且状态为未关闭的、未支付的,然后去进行关闭,扫描出这些订单,然后进行关闭。如果有历史的,会有一个单独的大的任务去在每天进行一轮补偿,防止历史数据污染近期数据;如果近期要更新的订单数量级比较多,需要使用线程并行的去进行对订单的状态更新扫描,以及对应的触发操作。订单状态更新可能会涉及包括发通知,然后对应的库存释放等等操作。这些基本上是异步的,在扫描到这些订单之后,发送消息到对应的 MQ Topic 上,然后做对应的处理操作。

点评

评分:⭐⭐⭐⭐☆
优点:核心方案完整(近期任务 10-30min + 历史补偿 24h);提到多线程并行;提到异步 MQ 处理库存释放;主动想到"防历史数据污染"这个工程细节
不足:- 分段扫表没具体讲(ID 分段 vs LIMIT 大偏移) - 防重复关闭机制没提(状态机校验、唯一索引) - 定时任务框架选型对比没做(Quartz/XxlJob/Elastic-Job) - 分段并行时线程池大小和 DB 连接池的关系没讨论
补充:按 ID 分段(每 1000 一段)避免 LIMIT 大偏移;状态机校验(只能从未支付→已关闭);线程池 size 匹配连接池 ---

完整讲解

第一部分:知识重点剖析

**核心考点定位**:考察定时任务的设计能力,理解如何高效处理大批量订单的自动关单。

**知识点拆解**:
1. **关单任务设计**:
- 近期任务:每 10-30 分钟扫描一次,处理创建时间在关单时限内的订单
- 历史补偿任务:每天凌晨扫描一次,处理遗漏的过期订单
- 防止历史数据污染近期数据
2. **分段扫表技术**:
- 问题:LIMIT 大偏移(如 LIMIT 1000000, 100)性能差
- 方案:按 ID 分段(如 ID 1000 个一段),每段单独查询
- 避免全表扫描,只扫相关时间范围的订单
3. **多线程并行处理**:
- 大量订单时,单线程处理慢
- 使用线程池并行处理订单关闭
- 控制并发度,避免数据库连接池耗尽
4. **防重复关闭机制**:
- 唯一索引:order_id + status
- 状态机校验:只能从未支付→已关闭
- 乐观锁:版本号控制
5. **关单后异步操作**:
- 库存释放:发送 MQ 消息
- 通知发送:短信/邮件通知用户
- 风控标记:记录关单原因
- 通过 MQ 消息异步处理,避免阻塞主流程
6. **定时任务框架选择**:
- Quartz:本地定时任务,单机运行
- XxlJob:分布式定时任务,可视化管理
- Elastic-Job:分布式定时任务,支持分片

第二部分:考察方向与延伸递进

**考察方向**:
- **深度**:理解分段扫表的底层原理
- **广度**:关联定时任务框架、多线程、MQ 消息
- **应用**:能设计高效的关单任务,避免性能问题

**延伸递进面试题**:
1. **为什么不能用 LIMIT 大偏移扫表?怎么解决?**
- 解答要点:LIMIT 大偏移性能差,按 ID 分段,每段单独查询
2. **如何防止重复关闭订单?**
- 解答要点:唯一索引、状态机校验、乐观锁
3. **定时任务框架怎么选?**
- 解答要点:单机用 Quartz,分布式用 XxlJob/Elastic-Job

第三部分:面试者参考回答(三步法口语化,300-400 字)

**【一句话结论】**
面试官您好,关单任务分近期任务和历史补偿任务,用分段扫表避免 LIMIT 大偏移,多线程并行处理,关单后通过 MQ 消息异步处理后续操作。

**【三点展开】**
第一,任务设计分两类。近期任务每 10-30 分钟扫描一次,处理创建时间在关单时限内的订单;历史补偿任务每天凌晨扫描一次,处理遗漏的过期订单,防止历史数据污染近期数据。

第二,分段扫表避免 LIMIT 大偏移。按 ID 分段(如 1000 个 ID 一段),每段单独查询,避免全表扫描,只扫相关时间范围的订单。大量订单时用线程池并行处理,控制并发度,避免数据库连接池耗尽。

第三,关单后通过 MQ 消息异步处理后续操作。库存释放、通知发送、风控标记等都通过 MQ 消息异步处理,避免阻塞主流程。

**【一句边界】**
当然,还要防止重复关闭(唯一索引 + 状态机校验),选择合适是定时任务框架(单机 Quartz,分布式 XxlJob)。

#8 Netty 中用了哪些设计模式? high java

题目

Netty 中用了哪些设计模式?

我的回答

NGINX 首先使用的单例模式,它使用多个过滤器来处理过多的请求。另外它使用了责任链设计模式。还有工厂设计模式,创建 netty 对象的时候。

点评

评分:⭐⭐☆☆☆
优点:提到责任链(Pipeline);提到工厂/建造者(Bootstrap)
不足:- ❌❌ **框架混淆**:"NGINX" 和 "Netty" 完全是两个不同框架(NGINX 是 C 语言 Web 服务器,Netty 是 Java NIO 框架)。这是本轮最严重的失分点 - 漏掉观察者(ChannelHandler 事件回调) - 漏掉策略(传输层 TCP/UDP 选择) - 漏掉模板方法(SimpleChannelInboundHandler 定义模板) - 漏掉外观(Bootstrap 简化启动流程)
补充:Netty 6 大设计模式:责任链(Pipeline)、观察者(EventLoop+Handler)、策略(传输层/编解码)、模板方法(Handler 模板)、外观(Bootstrap)、建造者(Bootstrap 构建) ---

完整讲解

> 最后更新:2026-10-27 · 基于第 2 次作答

### L1 · 30 秒速记
> 结论:Netty 常考 6 大件是单例/工厂/建造者/责任链/策略/观察者,每个要能对应 Netty 具体类。
> 关键词:ChannelPipeline 责任链 · Bootstrap 建造者 · ChannelFuture 观察者
> 边界:别背"6 大"清单,要能说"这个模式解决什么、Netty 怎么用的"。

### L2 · 核心讲解

**核心考点定位**:考察能否把设计模式与 Netty 的实际实现类对应,而非只报模式名字。面试官想排除"报 5 个模式名但说不出 Netty 哪个类体现"的空泛回答。

**机制链(≥3 环)**:
1. 请求进入 Channel → 沿 Pipeline 传播:为什么——ChannelPipeline 是责任链,Inbound 向上、Outbound 向下,每个 Handler 决定是否传递
2. 事件产生 → ChannelHandler 回调:为什么——观察者模式,EventLoop 驱动事件,解耦产生与处理
3. 构建服务 → Bootstrap 链式配置:为什么——建造者模式,链式调用最后 build() 创建
4. 异步 IO 完成 → ChannelFuture 通知:为什么——观察者,Promise/Future 承载结果,addListener 回调避免阻塞
5. 传输选择 → NioSocketChannel/NioDatagramChannel:为什么——策略模式,运行时按需选 TCP/UDP

**证据锚点**(≥12):
- `ChannelPipeline`:责任链,多 Handler 串联
- `ChannelHandler`:责任链节点,处理特定事件
- `EventLoop`:观察者,事件驱动回调
- `ChannelFuture`:观察者,异步回调(addListener)
- `Promise`:观察者,Future 的结果载体
- `Bootstrap`:建造者,链式配置服务端/客户端
- `NioSocketChannel`:策略,TCP 传输实现
- `NioDatagramChannel`:策略,UDP 传输实现
- `SimpleChannelInboundHandler`:模板方法,channelRead0 模板
- `ChannelDuplexHandler`:模板方法,inbound/outbound 两套
- `EventLoopGroup`:单例,通常服务级复用一组
- `WrappedByteBuf`:装饰器,透明包装增强
- `ChannelHandlerAdapter`:适配器,连接 Inbound/Outbound 适配
- `NioEventLoopGroup`:工厂,创建 EventLoop

**对比与权衡**:
| 模式 | Netty 体现 | 解决的问题 | 适用 |
|---|---|---|---|
| 责任链 | ChannelPipeline | 事件多级处理解耦 | 协议栈处理 |
| 观察者 | ChannelFuture/Promise | 异步结果通知 | 非阻塞回调 |
| 建造者 | Bootstrap | 复杂对象构建 | 服务启动 |
| 策略 | Channel 传输/编解码 | 运行时选实现 | TCP/UDP 切换 |
| 模板方法 | SimpleChannelInboundHandler | 固定流程+子类细节 | Handler 编写 |
| 装饰器 | WrappedByteBuf | 透明增强 | ByteBuf 包装 |

**常见误区**:
1. 错误:把 Netty 说成 NGINX → 正确:Netty 是 Java NIO 框架 → 为什么:完全不同的框架,NGINX 是 C 语言 Web 服务器
2. 错误:只报模式名 → 正确:说清 Netty 哪个类体现 → 为什么:报名字是敲门砖,对应类才是深度
3. 错误:漏观察者 → 正确:ChannelFuture/Promise 是异步回调观察者 → 为什么:Netty 非阻塞核心靠它通知结果
4. 错误:装饰器和适配器不算 → 正确:WrappedByteBuf 是装饰器,ChannelHandlerAdapter 是适配器 → 为什么:Netty 实际用了,属加分项
5. 错误:Pipeline 是单向 → 正确:Pipeline 是双向责任链 → 为什么:Inbound 向上、Outbound 向下,与 Servlet Filter 单向不同

**适用边界**:设计模式不是越多越好,Netty 在恰当时机用恰当模式;面试重点在"模式解决什么问题 + Netty 哪里用了",不在背清单。单例在 Netty 里是"服务级复用"而非严格单例,别硬套;装饰器/适配器是加分项,但责任链/观察者才是 Netty 的灵魂,先说清这两个。

### 延伸追问(≥3)

1. **Netty 的 Pipeline 和 Servlet 的 FilterChain 有什么区别?**
- 要点:Pipeline 双向(Inbound/Outbound),FilterChain 单向;Pipeline 支持动态增删 Handler
- 追问落点:动态增删 Handler 的场景
2. **Netty 的 EventLoop 模型和 Reactor 模型什么关系?**
- 要点:EventLoop 是 Reactor 具体实现;BossGroup 接收、WorkerGroup 处理;每个 EventLoop 单线程无锁
- 追问落点:为什么单线程无锁
3. **Netty 如何扩展自定义 Handler?**
- 要点:继承 ChannelInboundHandlerAdapter/ChannelOutboundHandlerAdapter,pipeline.addLast() 添加
- 追问落点:Handler 的线程模型(绑定 EventLoop)

### 面试者参考回答(500–800 字)

**【结论】** 面试官您好,Netty 是设计模式的应用典范,我按创建/结构/行为三类讲,每个都对应到具体类。

**【三点】** 第一,创建型——单例:EventLoopGroup 通常服务级复用一组;建造者:Bootstrap 链式配置最后 build();工厂:NioEventLoopGroup 创建 EventLoop。第二,结构型——责任链:ChannelPipeline 由多个 ChannelHandler 组成,Inbound 向上、Outbound 向下,支持动态增删;装饰器:WrappedByteBuf 透明包装;适配器:ChannelHandlerAdapter。第三,行为型——观察者:ChannelFuture/Promise 异步回调(addListener),非阻塞核心;策略:NioSocketChannel/NioDatagramChannel 运行时选传输;模板方法:SimpleChannelInboundHandler 定义 channelRead0 模板。

**【边界】** 当然,别背"6 大"固定清单——面试官更想看"这个模式解决什么、Netty 怎么用的",报名字只是敲门砖;Netty 与 NGINX 是两个完全不同的框架,别混淆。

---

#9 能不能说一说Netty的无锁化设计? high java

题目

能不能说一说Netty的无锁化设计?

我的回答

无锁化设计首先确认为什么要有锁,之前说过关于锁首先是需要多线程,其次是多线程修改有状态的,共享有状态的数据。所以这也就明显了,Netty 首先肯定是多线程的,但保证无锁化需要不共享有状态的内容就可以了,所以这就是 netty 无锁化的设计,它在内部并没有做有状态的数据共享。也就不需要锁,进行限制并发。

点评

评分:⭐⭐⭐☆☆(**显著进步,09-26 首次 1/5 → 此次 3/5**)
优点:- 抓住了锁的前提条件(多线程 + 共享有状态数据) - 抓到 Netty 无锁化的核心:**不共享有状态数据,单线程独占 Channel** - 相比 09-26 "把无锁化误解为无状态",这次逻辑链路正确
不足:- 未讲 Reactor 主从模型(BossGroup/WorkerGroup) - 未讲 MPSC 无锁任务队列(EventLoop 任务提交) - 未讲 PooledByteBufAllocator(ThreadLocal 池化) - 未讲 Recycler(对象池) - 未讲零拷贝(DirectByteBuffer、FileChannel.transferTo) - 未讲"跨 EventLoop 调用要 submit"这个业务约束
补充:Reactor 主从模型(Boss accept + Worker IO);MPSC 队列基于 CAS;ByteBuf 池化线程局部化;跨 EventLoop 用 `channel.eventLoop().submit()` ---

完整讲解

> 最后更新:2026-10-27 · 基于第 1 次作答

### L1 · 30 秒速记
> 结论:Netty 无锁化核心是单线程串行化,配合无锁队列和内存池,而非简单"不加锁"。
> 关键词:EventLoop 单线程 · MPSC 队列 CAS · PooledByteBufAllocator
> 边界:无锁换来单核,并发靠多 EventLoop。

### L2 · 核心讲解

**核心考点定位**:考察无锁化的多个层面,是否理解"为什么单线程就不用锁"。面试官想排除"无锁 = 不用 synchronized"的浅层回答。

**机制链(≥3 环)**:
1. Channel 操作 → 调度到绑定 EventLoop:为什么——单线程串行化,消除并发访问,无需同步
2. 任务提交 → MPSC 队列 CAS 追加:为什么——多生产者单消费者,写端 CAS 无锁,读端单线程取
3. 内存分配 → PooledByteBufAllocator 预分配:为什么——内存池复用,避免频繁分配 GC
4. ByteBuf 释放 → ReferenceCountUtil.release 原子计数:为什么——引用计数归零才回收,防泄漏
5. 线程本地访问 → FastThreadLocal 数组定位:为什么——替代 JDK ThreadLocal 的哈希查找,更快
6. 计数器更新 → AtomicIntegerFieldUpdater:为什么——用字段更新器而非 AtomicInteger,避免额外对象分配

**证据锚点**(≥12):
- `EventLoop`:单线程 + 任务队列,串行执行
- `MpscQueue`:多生产者单消费者队列,CAS 追加
- `MpscArrayQueue`:JCTools 提供的实现
- `PooledByteBufAllocator`:内存池分配器
- `PoolArena`:内存池的 Arena(类似 jemalloc)
- `ReferenceCountUtil.release()`:引用计数释放
- `RefCnt`:引用计数接口
- `FastThreadLocal`:替代 JDK ThreadLocal,数组+索引定位
- `AtomicIntegerFieldUpdater`:原子字段更新,减少对象
- `ByteBuf`:Netty 缓冲区
- `DefaultEventExecutorGroup`:业务线程池(非 IO 无锁区)
- `Channel`:绑定 EventLoop 的抽象
- `ThreadLocal`:JDK 原生,FastThreadLocal 对标它
- `-Dio.netty.leakDetection.level`:泄漏检测级别配置

**对比与权衡**:
| 方案 | 正确性 | 并发度 | 失败模式 | 适用 |
|---|---|---|---|---|
| 单线程串行化 | 高 | 单核 | 一个阻塞全堵 | Channel 操作 |
| MPSC 队列 | 高 | 多写单读 | 消费者慢堆积 | 任务提交 |
| 内存池 | 高 | 高 | 泄漏 | ByteBuf 分配 |
| 传统加锁 | 中 | 中 | 锁竞争 | 跨线程共享 |
| CAS 原子操作 | 高 | 高 | ABA 问题 | 计数器/队列

**常见误区**:
1. 错误:无锁 = 不加 synchronized → 正确:本质是单线程串行 + 无锁数据结构 → 为什么:避免并发访问才是关键,不是去掉关键字
2. 错误:MPSC 是阻塞队列 → 正确:CAS 无锁,不会阻塞 → 为什么:写端 CAS 自旋,读端单线程
3. 错误:ByteBuf 不释放没事 → 正确:必须 ReferenceCountUtil.release → 为什么:引用计数泄漏导致 OOM
4. 错误:单线程串行化没有并发 → 正确:靠多 EventLoop 并行 → 为什么:每个 EventLoop 单核,整体靠多 EventLoop 并行
5. 错误:业务直接放 EventLoop → 正确:丢 DefaultEventExecutorGroup → 为什么:阻塞会堵住整条串行链
6. 错误:FastThreadLocal 和 ThreadLocal 没区别 → 正确:数组索引定位更快 → 为什么:JDK ThreadLocal 哈希查找有冲突,FastThreadLocal 用索引

**适用边界**:无锁换来"一个 EventLoop 单核",要并发得靠多 EventLoop;业务阻塞要丢线程池;跨 EventLoop 共享数据仍需锁或原子类。CAS 有 ABA 问题,高并发场景需 AtomicStampedReference 版本号。无锁不是万能,读多写少场景锁反而更简单。

### 延伸追问(≥3)

1. **MPSC 队列为什么要 CAS?**
- 要点:多生产者并发写,单消费者读,写端无锁用 CAS 原子追加
- 追问落点:MPSC vs MPMC 的区别(多消费者需额外同步)
2. **引用计数泄漏会怎样?**
- 要点:ByteBuf 不 release 导致内存泄漏,Netty 泄漏检测(paranoid)
- 追问落点:泄漏检测如何开启(-Dio.netty.leakDetection.level)
3. **单线程串行化牺牲了什么?**
- 要点:一个线程只能用一核,多 Channel 靠多 EventLoop 并行
- 追问落点:EventLoop 数怎么配(2×CPU)
4. **无锁和 CAS 什么关系?**
- 要点:无锁数据结构的底层是 CAS 原子操作,乐观并发
- 追问落点:CAS 的 ABA 问题及版本号解法

### 面试者参考回答(500–800 字)

**【结论】** 面试官您好,Netty 无锁化核心是单线程串行化,配合无锁队列和内存池,而不是简单"不加锁"。

**【三点】** 第一,单线程串行化——一个 EventLoop 一个线程,同一 Channel 所有操作都在这线程串行执行,所以无需同步,这是基础。第二,无锁队列——MPSC 队列(MpscArrayQueue)多生产者用 CAS 原子追加,单消费者取走,任务提交无锁。第三,无锁内存——PooledByteBufAllocator 预分配内存池 + ReferenceCountUtil.release 引用计数控制回收,还有 FastThreadLocal 替代 JDK ThreadLocal 减少查找开销。

**【边界】** 当然,无锁换来的是"一个 EventLoop 单核",要并发得靠多 EventLoop;业务阻塞要丢线程池,否则阻塞整条串行链;跨 EventLoop 共享数据仍需锁或原子类。

---

#10 Spring中用到了哪些设计模式 high java

题目

Spring中用到了哪些设计模式

我的回答

Spring 中用到很多设计模式,最核心的首先是代理模式,它的很多依赖的基础都是代理,IOC 都是基于代理模式去实现的。然后是模板方法,比如,Rest Template,JDBC Template,这都是基于模板方法去实现。然后是责任链设计模式,有单例模式、享元模式,策略模式,工厂模式等;

点评

评分:⭐⭐⭐☆☆
优点:抓到主要模式(代理、模板方法、责任链、单例、策略、工厂);模板方法配了 RestTemplate/JdbcTemplate 例子
不足:- ❌ **术语错误**:"IoC 都是基于代理" 不准——IoC 是控制反转,代理主要用于 AOP(10-28 类似错) - ❌ **错误答案**:Spring 里"享元模式"是错的(和 09-28 重复犯同样错误);Spring 单例 ≠ GoF 享元 - 漏掉观察者(@EventListener、ApplicationEvent) - 漏掉适配器(HandlerMethodReturnValueHandler) - 漏掉外观(JdbcTemplate 把 JDBC 收敛) - 责任链没配例(SpringMVC HandlerInterceptor、Security FilterChain)
补充:Spring 三大类设计模式;重点区分 Spring 单例(容器级)vs GoF 单例(全局静态);享元是共享细粒度对象,Spring 里没有明显实现 ---

完整讲解

> 最后更新:2026-10-27 · 基于第 1 次作答

### L1 · 30 秒速记
> 结论:Spring 常考单例/工厂/代理/模板/观察者/责任链/策略,每个要能对应具体实现类。
> 关键词:单例默认 scope · AOP 代理 · JdbcTemplate 模板
> 边界:别背清单,要能说"模式解决什么、Spring 哪里用"。

### L2 · 核心讲解

**核心考点定位**:考察设计模式与 Spring 组件的对应,是否理解模式解决什么问题。面试官想排除"报一堆名字但说不出具体类"的空泛回答。

**机制链(≥3 环)**:
1. 请求进入 → BeanFactory 创建单例:为什么——单例模式,默认 scope 容器缓存,避免反复实例化
2. AOP 增强 → JDK 动态代理/CGLIB 代理:为什么——代理模式,拦截方法做增强,解耦横切
3. 模板执行 → JdbcTemplate 固定流程:为什么——模板方法,流程骨架固定,子类/回调定制细节
4. 事件发布 → ApplicationListener 响应:为什么——观察者模式,解耦发布与订阅
5. 请求拦截 → HandlerInterceptor 链:为什么——责任链,多级拦截按序处理,可中断

**证据锚点**(≥12):
- `DefaultSingletonBeanRegistry`:单例缓存(singletonObjects Map)
- `BeanFactory`:工厂模式,顶层容器
- `FactoryBean`:对象工厂钩子
- `JdkDynamicAopProxy`:JDK 动态代理(接口)
- `CglibAopProxy`:CGLIB 代理(类)
- `JdbcTemplate`:模板方法,流程骨架
- `RestTemplate`:模板方法,HTTP 流程
- `RedisTemplate`:模板方法,Redis 操作
- `ApplicationEvent`:观察者,事件
- `ApplicationListener`:观察者,监听器
- `HandlerInterceptor`:责任链,MVC 拦截器
- `Resource`:策略,资源抽象
- `BeanDefinitionBuilder`:建造者,构建 BeanDefinition
- `RequestMappingHandlerMapping`:策略,URL 映射实现
- `ProxyFactoryBean`:工厂,AOP 代理工厂
- `@Transactional`:AOP 事务切面典型应用

**对比与权衡**:
| 模式 | Spring 体现 | 解决的问题 | 适用 |
|---|---|---|---|
| 单例 | 默认 scope | 对象复用 | 无状态服务 |
| 代理 | AOP(JDK/CGLIB) | 横切解耦 | 事务/日志/鉴权 |
| 模板方法 | JdbcTemplate | 固定流程+细节定制 | 资源访问 |
| 观察者 | ApplicationEvent | 事件解耦 | 业务事件 |
| 责任链 | Interceptor/Filter | 请求多级处理 | Web 过滤链 |
| 工厂 | BeanFactory/FactoryBean | 对象创建解耦 | IoC 容器 |

**常见误区**:
1. 错误:Spring 用"创建者模式" → 正确:主要是工厂方法 + 建造者(BeanDefinitionBuilder)→ 为什么:Spring 的 Bean 创建是工厂,Builder 是构建 BeanDefinition
2. 错误:AOP 默认 JDK 代理 → 正确:Spring Boot 2+ 默认 CGLIB → 为什么:CGLIB 不依赖接口,类也可代理
3. 错误:单例是硬编码 → 正确:scope=singleton 容器管理 → 为什么:线程安全靠无状态,不是加锁
4. 错误:观察者一定异步 → 正确:默认同步,@Async 才异步 → 为什么:Spring 事件默认同步发布
5. 错误:模板方法只是封装调用 → 正确:是流程骨架 + 回调定制 → 为什么:JdbcTemplate 固定连接/事务/释放流程,只有 SQL 和 RowMapper 是回调
6. 错误:@Transactional 靠自调用生效 → 正确:外部调用才走代理 → 为什么:自调用不经过代理,AOP 失效(同类 this 调用)

**适用边界**:设计模式在 Spring 是恰当时机恰当使用;单例要求 Bean 无状态;事件监听若处理慢会阻塞发布方,需异步;模板方法不适用于流程需要大改的场景。代理模式对自调用(同类 this 调用)失效,需要暴露代理或拆分方法。

### 延伸追问(≥3)

1. **AOP 为什么默认 CGLIB?**
- 要点:Spring Boot 2+ 默认 CGLIB,类无需接口也能代理;JDK 代理只支持接口
- 追问落点:JDK 代理 vs CGLIB 的性能与限制(CGLIB 不能代理 final 类/方法)
2. **Spring 事件是同步还是异步?**
- 要点:默认同步,@Async 或 ApplicationEventMulticaster 可异步
- 追问落点:@TransactionalEventListener 的时机(事务提交后)
3. **单例怎么保证的?**
- 要点:DefaultSingletonBeanRegistry 的 singletonObjects Map + 双重检查
- 追问落点:单例 Bean 的线程安全(无状态 + 不可变)
4. **责任链和 AOP 都能做日志,区别?**
- 要点:责任链是请求处理链,AOP 是方法级横切;Filter/Interceptor 管请求,AOP 管业务方法
- 追问落点:Interceptor 和 Filter 的区别(Servlet 层 vs Spring 层)

### 面试者参考回答(500–800 字)

**【结论】** 面试官您好,Spring 是设计模式的应用典范,我按创建/结构/行为三类挑核心来讲,每个都对应具体类。

**【三点】** 第一,创建型——单例:默认 scope=singleton,DefaultSingletonBeanRegistry 缓存;工厂:BeanFactory/FactoryBean;建造者:BeanDefinitionBuilder。第二,结构型——代理:AOP 用 JDK 动态代理(接口)或 CGLIB(类),Spring Boot 2+ 默认 CGLIB。第三,行为型——模板:JdbcTemplate/RestTemplate/RedisTemplate 固定流程骨架;观察者:ApplicationEvent + ApplicationListener(默认同步,@Async 可异步);责任链:HandlerInterceptor/Filter 链;策略:Resource 接口多实现。

**【边界】** 当然,设计模式不是越多越好,Spring 在恰当时机用恰当模式,别背清单,要能说清"这个模式解决什么问题、Spring 哪里用了";单例要求 Bean 无状态,事件监听别拖慢发布方。

---

📚 查看全部面试记录