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

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 构建) ---

完整讲解

第一部分:知识重点剖析

**核心考点定位**:考察对 Netty 架构设计的理解,以及能否把设计模式与 Netty 的实际实现对应。

**知识点拆解**:

1. **责任链模式**(Netty 核心):
- **ChannelPipeline**:每个 Channel 都有一条 Pipeline,由多个 ChannelHandler 组成
- 请求沿着 Pipeline 向上传递(Inbound),响应沿着 Pipeline 向下传递(Outbound)
- 每个 Handler 可以处理特定事件,然后决定是否传递给下一个 Handler

2. **观察者模式**(事件驱动):
- **EventLoop**:每个 EventLoop 处理多个 Channel 的事件
- **ChannelHandler**:实现事件回调(channelRead、exceptionCaught 等)
- 事件驱动架构,解耦了事件产生和处理

3. **策略模式**(灵活选择):
- **传输层选择**:NioSocketChannel(TCP)、NioDatagramChannel(UDP)
- **编解码器**:ByteBufDecoder、MessageToByteEncoder 等
- 运行时选择不同的实现

4. **模板方法模式**(抽象处理流程):
- **SimpleChannelInboundHandler**:定义了 channelRead0 模板,子类实现具体处理
- **ChannelDuplexHandler**:定义了 inbound/outbound 两套模板

5. **外观模式**(简化复杂操作):
- **ChannelPipeline**:简化了事件处理的复杂性
- **Bootstrap**:简化了 Netty 服务的启动过程

6. **建造者模式**(构建复杂对象):
- **Bootstrap**:通过 builder 模式配置 Netty 服务
- 链式调用设置参数,最后 build() 创建实例

7. **单例模式**(谨慎使用):
- **EventLoopGroup**:通常每个服务创建一组 EventLoopGroup
- 不是严格的单例,但可以复用

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

**考察方向**:
- **深度**:Netty 源码中对设计模式的具体实现类
- **广度**:设计模式之间的对比和适用边界
- **应用**:业务开发中如何借鉴 Netty 的设计思路

**延伸递进面试题**:
1. **Netty 的 Pipeline 和 Servlet 的 FilterChain 有什么区别?**
- 解答要点:Pipeline 是双向的(Inbound/Outbound),FilterChain 是单向的;Pipeline 支持 handler 的添加/移除,FilterChain 是固定的;Pipeline 的事件驱动更灵活
2. **Netty 的 EventLoop 模型和 Reactor 模型有什么关系?**
- 解答要点:EventLoop 是 Reactor 模型的具体实现;BossGroup 接收连接,WorkerGroup 处理 IO 事件;每个 EventLoop 是单线程的,无锁化设计
3. **Netty 如何扩展自定义 Handler?**
- 解答要点:继承 ChannelInboundHandlerAdapter 或 ChannelOutboundHandlerAdapter;实现感兴趣的事件回调;通过 pipeline.addLast() 添加

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

面试官您好,Netty 是设计模式的应用典范,我按**结构型、行为型、创建型**三类来讲。

**结构型模式**:
- **责任链模式**(Netty 核心):ChannelPipeline 由多个 ChannelHandler 组成,请求沿着 Pipeline 向上传递,响应向下传递。每个 Handler 可以处理特定事件,决定是否传递给下一个 Handler
- **外观模式**:ChannelPipeline 简化了事件处理的复杂性,Bootstrap 简化了 Netty 服务的启动过程

**行为型模式**:
- **观察者模式**:EventLoop 处理多个 Channel 的事件,ChannelHandler 实现事件回调(channelRead、exceptionCaught 等),事件驱动架构解耦了事件产生和处理
- **模板方法**:SimpleChannelInboundHandler 定义了 channelRead0 模板,子类实现具体处理;ChannelDuplexHandler 定义了 inbound/outbound 两套模板
- **策略模式**:传输层选择(NioSocketChannel/NioDatagramChannel)、编解码器(ByteBufDecoder/MessageToByteEncoder),运行时选择不同的实现

**创建型模式**:
- **建造者模式**:Bootstrap 通过 builder 模式配置 Netty 服务,链式调用设置参数,最后 build() 创建实例
- **单例模式**:EventLoopGroup 通常每个服务创建一组,可以复用

**关键区分点**:
- **Netty vs NGINX**:Netty 是 Java NIO 框架,NGINX 是 C 语言 Web 服务器,完全不同的框架
- **责任链 vs 管道**:Netty 的 Pipeline 是双向的责任链,支持动态添加/移除 Handler

**实际工程借鉴**:我在开发 RPC 框架时会借鉴 Netty 的设计——用 Pipeline 处理协议解析、编解码、业务处理;用 EventLoop 实现无锁化的高并发处理;用 Builder 模式简化客户端配置。

#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()` ---

完整讲解

第一部分:知识重点剖析

**核心考点定位**:能否说清 Netty 通过"单线程独占 Channel"和"MPSC 无锁队列 + ThreadLocal 池化"来消除锁竞争,而不是简单理解为"无状态"。

**知识点拆解**:
1. **先纠正概念**:
- **无锁化(lock-free)**:用 CAS、原子变量、MPSC 队列等**无锁数据结构**替代 synchronized/Lock
- **无状态**:代码不保留可变数据(如纯函数、单例工具类)
- Netty 的无锁化是**消除锁竞争**,不是"没有状态"——Channel、EventLoop、ByteBuf 都有大量状态,但它们通过"单线程独占"避免了跨线程共享,因此不需要锁
2. **Reactor 单线程模型(核心)**:
- **主 Reactor(BossGroup)**:负责 accept 连接,把新建的 Channel 注册到 Worker
- **子 Reactor(WorkerGroup)**:每个 Worker 是一个 EventLoop 线程,管理多个 Channel
- **一对一绑定**:每个 Channel 绑定到一个 EventLoop 线程,所有 IO 事件和业务回调都在**同一个线程**里顺序执行
- **关键收益**:单线程内不需要锁;跨线程调用要用 `channel.eventLoop().submit(task)` 把任务投递到 EventLoop 队列
3. **MPSC 无锁任务队列**:
- 多个线程可以 `submit` 任务到 EventLoop 队列(多生产者)
- EventLoop 单线程从队列消费(单消费者)
- 实现用 `MpscLinkedQueue`,基于 CAS 操作无锁完成入队出队
4. **PooledByteBufAllocator 线程局部化**:
- 每个 EventLoop 线程持有自己的 ThreadLocal Chunk 链表
- 分配、释放 ByteBuf 都在同一线程完成,不跨线程竞争
- 相比 JDK 原生 ByteBuffer,内存分配/回收更快、GC 压力更低
5. **Recycler 对象池**:
- 用 ThreadLocal 无锁回收频繁创建的对象(如 ByteBuf、ChannelHandlerContext)
- 避免 GC 压力,也避免多线程竞争
6. **零拷贝(Zero-Copy)**:
- 用 DirectByteBuffer 减少 JVM 堆到内核的内存拷贝
- 用 FileChannel.transferTo 直接在内核态搬运大文件
7. **对比 NIO**:
- NIO 是 Reactor 模式的简化版,需要业务代码自己处理线程安全
- Netty 通过封装 + 单线程独占,把"线程安全"变成了框架的内置能力

> 按「底层原理 → 设计权衡 → 工程实践」递进排列。

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

**考察方向**:
- **深度**:能否说清 Reactor 单线程模型 + MPSC 队列 + ThreadLocal 池化这条链路
- **广度**:能否对比 NIO 原生实现
- **应用**:能否举出业务代码里的"跨 EventLoop 调用"场景

**延伸递进面试题**:
1. **业务代码里要修改 Channel 状态怎么办?**
- 解答要点:必须 `channel.eventLoop().submit(...)` 把任务投递到 Channel 绑定的 EventLoop 线程执行,避免跨线程访问
2. **Netty 为什么用 PooledByteBufAllocator 而不用 JDK 原生 ByteBuffer?**
- 解答要点:原生 ByteBuffer 每次 new 都堆外分配,GC 压力大;池化复用减少分配/回收,且线程局部化避免竞争
3. **Netty 的零拷贝体现在哪几个层面?**
- 解答要点:堆外内存、CompositeByteBuf 组合、FileChannel.transferTo 内核态搬运、SliceByteBuf 引用计数切片

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

面试官您好,我先纠正一个常见的误解:**Netty 的无锁化不是"没有状态"**,而是通过"单线程独占 + 无锁数据结构"来**消除锁竞争**。Channel、EventLoop、ByteBuf 都有大量状态,但它们被设计成不需要多线程访问,所以也就不需要锁。

**第一层是 Reactor 单线程模型**,这是 Netty 的核心。Netty 有一个主 Reactor(BossGroup)负责 accept 新连接,然后把 Channel 注册到子 Reactor(WorkerGroup)。每个 Worker 是一个 EventLoop 线程,管理多个 Channel。**关键点是每个 Channel 绑定到唯一一个 EventLoop 线程**——这个 Channel 的所有 IO 事件、所有业务回调,都在同一个线程里顺序执行。因为单线程内不需要互斥,自然就不需要锁。如果业务代码要从别的线程改 Channel 状态,必须用 `channel.eventLoop().submit(task)` 把任务投递过去,让 EventLoop 线程自己执行。

**第二层是 MPSC 无锁任务队列**。多个线程都可以 submit 任务到 EventLoop 的任务队列(多生产者),EventLoop 单线程从队列消费(单消费者)。Netty 用 `MpscLinkedQueue` 实现,基于 CAS 操作无锁完成入队出队,避免了 synchronized 队列的开销。

**第三层是 PooledByteBufAllocator 线程局部化**。每个 EventLoop 线程持有自己 ThreadLocal 的 Chunk 链表,分配和释放 ByteBuf 都在同一线程完成,不跨线程竞争。相比 JDK 原生 ByteBuffer,内存分配/回收更快、GC 压力更低。这里也用了 **Recycler 对象池**,通过 ThreadLocal 无锁回收频繁创建的对象。

**第四层是零拷贝**。用 DirectByteBuffer 避免 JVM 堆到内核的拷贝;用 FileChannel.transferTo 直接在内核态搬运大文件;用 CompositeByteBuf 组合避免内存复制;用 SliceByteBuf 引用计数切片。

**对比 NIO 原生实现**:NIO 只是给了 Reactor 的骨架,业务代码自己处理线程安全;Netty 通过封装 + 单线程独占,把"线程安全"变成了框架的内置能力,业务代码只需要写单线程逻辑,框架保证了并发正确性。

总结一句话:**Netty 的无锁化是"通过避免共享来实现",而不是"通过没有状态来实现"**。

这就是我对 Netty 无锁化设计的理解。

---

#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 里没有明显实现 ---

完整讲解

第一部分:知识重点剖析

**核心考点定位**:考察对 Spring 源码架构的理解深度,以及能否把设计模式与 Spring 的实际实现一一对应。

**知识点拆解**:

1. **创建型模式**:
- **单例模式**:Bean 默认作用域是 singleton,容器保证每个 bean 定义只有一个实例。**注意区分 Spring 单例 vs GoF 单例**:Spring 是容器级单例(可多容器并存),GoF 是全局唯一(静态实例 + 私有构造器)
- **工厂方法**:BeanFactory.getBean() 按名称/类型获取 bean,本质是工厂方法模式
- **抽象工厂**:FactoryBean 是"生产其他 bean 的工厂",Spring 在容器启动时调用 FactoryBean.getObject() 得到真正的业务 bean
- **建造者**:@Configuration 类中的 @Bean 方法内部多行初始化代码,本质是建造者模式构建复杂对象

2. **结构型模式**:
- **代理模式**(Spring 最大特色):AOP 通过 JDK 动态代理(基于接口)或 CGLIB(基于继承)在运行时包装目标对象
- **装饰器模式**:代理增强、HttpServletRequestWrapper 等包装类
- **适配器模式**:AbstractCachingErrorHandler、HandlerMethodReturnValueHandler 系列把不同格式的返回值适配到统一响应
- **外观模式(Facade)**:JdbcTemplate、RestTemplate 把 JDBC/HTTP 客户端的复杂操作收敛为几个简单方法

3. **行为型模式**:
- **模板方法**:JdbcTemplate.execute()、RestTemplate.exchange() 定义了"操作 - 预处理 - 后置处理"的算法骨架,子类/lambda 实现具体细节
- **观察者模式**:ApplicationEvent + ApplicationListener + @EventListener 实现事件驱动,@TransactionalEventListener 更进一步结合事务
- **策略模式**:PlatformTransactionManager 有多种实现(DataSourceTransactionManager / JtaTransactionManager),ResourceLoader 有多种实现(ClassLoaderResourceLoader / DefaultResourceLoader),运行时选不同实现
- **责任链模式**:SpringMVC 的 HandlerInterceptor 链、Spring Security 的 FilterChain
- **中介者模式**:ApplicationContext 协调 Bean 之间的通信
- **状态模式**:TransactionSynchronizationManager 管理事务同步状态

4. **几个容易搞混的对比**:
- **代理 vs 装饰器**:代理是控制访问(AOP、远程代理、保护代理),装饰器是动态添加功能(IO 流装饰)
- **工厂 vs 建造者**:工厂一次性创建,建造者分步构建复杂对象
- **观察者 vs 发布 - 订阅**:观察者是同步通知,发布 - 订阅通常引入消息中间件

5. **享元模式的常见误用**(本题失分点):
- 享元模式是共享细粒度对象以减少内存开销,如 Integer 缓存、字符串常量池、Character 缓存
- Spring 中并没有明显的享元模式实现,容易和"单例"混淆

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

**考察方向**:
- **深度**:Spring 源码中对设计模式的具体实现类
- **广度**:设计模式之间的对比和适用边界
- **应用**:业务开发中如何借鉴 Spring 的设计思路

**延伸递进面试题**:
1. **Spring 的三级缓存用了什么设计模式?如果不用工厂模式(ObjectFactory)会怎样?**
- 解答要点:三级缓存本质是抽象工厂模式(L3 存的是 ObjectFactory,延迟决定"要不要代理")。如果不用工厂模式,只能用两级缓存,但会导致 AOP 场景下"B 持有 A 原始对象而 A 自身持有代理对象"的引用分裂问题。三级缓存的核心价值是让"是否代理"的决策延迟到运行时按需生成
2. **Spring 里有哪些地方用到了责任链?如果让你自定义一条拦截链怎么做?**
- 解答要点:SpringMVC 的 HandlerInterceptor(preHandle/afterCompletion/postHandle)、Spring Security 的 FilterChain、Tomcat 的 Pipeline、Spring AOP 的 AdviceChain 都用责任链。自定义方式:定义接口 Handler { boolean handle(Request ctx, Handler next); },构造一个 List<Handler>,运行时依次调用 handler.handle(ctx, next),最后一个 handler 处理业务逻辑,异常向上传播
3. **Spring 的观察者模式如何结合事务使用?@TransactionalEventListener 的 phase 有哪些?**
- 解答要点:@TransactionalEventListener 结合事务使用,支持 BEFORE_COMMIT、AFTER_COMMIT、AFTER_ROLLBACK、BEFORE_COMPLETION 四种 phase,默认是 AFTER_COMMIT。适合"事务提交后再发通知/发消息"的场景,避免事务回滚后消息已经发出去的问题

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

面试官您好,Spring 是设计模式的集大成者,我按**创建型、结构型、行为型**三大类来讲。

**创建型模式**:
- **单例模式**:Bean 默认作用域是 singleton,容器保证每个 bean 定义只有一个实例。这里有个关键点:Spring 的"单例"是**容器级单例**(可多容器并存),和 GoF 的"全局唯一单例"(静态实例 + 私有构造器)不完全一样
- **工厂方法**:BeanFactory.getBean() 按名称/类型获取 bean,本质是工厂方法模式
- **抽象工厂**:FactoryBean 是"生产其他 bean 的工厂",Spring 在容器启动时调用 FactoryBean.getObject() 得到真正的业务 bean
- **建造者**:@Configuration 类中的 @Bean 方法内部多行初始化代码,本质是建造者模式

**结构型模式**:
- **代理模式**(Spring 最大特色):AOP 通过 JDK 动态代理(基于接口)或 CGLIB(基于继承)在运行时包装目标对象
- **装饰器模式**:代理增强、HttpServletRequestWrapper 等包装类
- **适配器模式**:AbstractCachingErrorHandler、HandlerMethodReturnValueHandler 系列把不同格式的返回值适配到统一响应
- **外观模式(Facade)**:JdbcTemplate、RestTemplate 把 JDBC/HTTP 客户端的复杂操作收敛为几个简单方法

**行为型模式**:
- **模板方法**:JdbcTemplate.execute()、RestTemplate.exchange() 定义了"操作 - 预处理 - 后置处理"的算法骨架,子类/lambda 实现具体细节
- **观察者模式**:ApplicationEvent + ApplicationListener + @EventListener 实现事件驱动,@TransactionalEventListener 更进一步结合事务
- **策略模式**:PlatformTransactionManager 有多种实现(DataSourceTransactionManager / JtaTransactionManager),ResourceLoader 有多种实现(ClassLoaderResourceLoader / DefaultResourceLoader),运行时选不同实现
- **责任链模式**:SpringMVC 的 HandlerInterceptor 链、Spring Security 的 FilterChain
- **中介者模式**:ApplicationContext 协调 Bean 之间的通信
- **状态模式**:TransactionSynchronizationManager 管理事务同步状态

**几个容易搞混的对比**:
- **代理 vs 装饰器**:代理是控制访问(AOP、远程代理),装饰器是动态添加功能(IO 流装饰)
- **工厂 vs 建造者**:工厂一次性创建,建造者分步构建复杂对象

**关于享元模式的一个澄清**:享元模式是共享细粒度对象以减少内存开销(如 Integer 缓存、字符串常量池),Spring 中并没有明显的享元模式实现,容易和"单例"混淆。

**实际工程借鉴**:我在写业务代码时会借鉴 Spring 的设计思路——比如用模板方法把"参数校验 - 业务执行 - 后置日志"的重复代码抽象成 Template;用观察者模式通过 Spring 事件解耦业务模块(订单支付完成后触发积分、通知、库存回滚);用工厂模式把复杂的 Bean 构建封装在 @Configuration 类里。

📚 查看全部面试记录