一、引言:为什么架构师必须懂设计模式
设计模式不是面试八股文,而是工程实践中沉淀下来的可复用设计经验。在架构设计层面,GoF 23 种设计模式里,有六种模式的出场率远超其他——工厂、策略、模板方法、责任链、观察者和代理。这六种模式几乎出现在每一个中大型 Java 项目的代码中,Spring 框架本身就是它们的集大成者。
本文不按教科书讲所有模式,而是聚焦这六种模式在工程中的真实应用:Spring 源码中怎么用、业务代码中怎么选、设计时容易踩哪些坑。读完你会发现,设计模式不是背出来的,是你写代码时自然就用到的。
阅读时间:约 40-50 分钟
二、六种模式全景概览
在深入每个模式之前,先建立整体认知:
| 模式 | 核心思想 | 一句话总结 | Spring 中的典型应用 |
|---|---|---|---|
| 工厂模式 | 将对象创建逻辑封装起来,客户端无需关心具体类 | 用工厂方法替代 new | BeanFactory、ApplicationContext |
| 策略模式 | 定义算法族,让它们可以互相替换 | 把 if-else 换成策略类 | Resource 接口、InstantiationStrategy |
| 模板方法 | 父类定义算法骨架,子类实现具体步骤 | 不变的框架,变化的细节 | JdbcTemplate、RestTemplate、AbstractApplicationContext.refresh() |
| 责任链 | 多个处理器串联,请求沿着链传递直到被处理 | 请求的接力赛 | FilterChain、HandlerInterceptor、DispatcherServlet |
| 观察者 | 主题状态变化时自动通知所有观察者 | 一对多的发布-订阅 | ApplicationEvent、ApplicationListener |
| 代理模式 | 为对象提供替身,控制对其的访问 | 在调用前后做增强 | AOP 动态代理、@Transactional、@Cacheable |

图1:六种设计模式在工程应用中的关系全景
三、工厂模式:解耦对象创建
3.1 核心概念
工厂模式解决的核心问题是:客户端不需要知道具体类的名称,也不需要知道创建细节,只需要告诉工厂"我要一个能做什么的"对象即可。
工厂模式有三个层次:
- 简单工厂:一个工厂类根据参数创建不同产品
- 工厂方法:定义一个创建对象的接口,让子类决定实例化哪一个类
- 抽象工厂:创建一系列相关或相互依赖的对象,无需指定具体类

图2:工厂模式三层次进化——简单工厂→工厂方法→抽象工厂
3.2 工程实践
Spring 中的工厂模式是工厂模式的终极范例。BeanFactory 是工厂方法模式的体现:
// ApplicationContext 就是最大的工厂
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = ctx.getBean("userService", UserService.class);
Spring 的 BeanFactory 层次结构:
BeanFactory— 顶层接口,定义了获取 bean 的基本方法HierarchicalBeanFactory— 增加父子容器支持AutowireCapableBeanFactory— 增加自动装配能力ConfigurableBeanFactory— 增加配置能力DefaultListableBeanFactory— 最终落地实现
业务场景:当订单系统需要对接多种支付方式(微信、支付宝、银联),工厂模式可以将支付渠道的创建与业务逻辑解耦:
// 支付工厂
public class PaymentFactory {
private static final Map<String, PaymentService> paymentMap = new HashMap<>();
static {
paymentMap.put("WECHAT", new WechatPaymentService());
paymentMap.put("ALIPAY", new AlipayPaymentService());
paymentMap.put("UNIONPAY", new UnionPayPaymentService());
}
public static PaymentService getPayment(String channel) {
PaymentService service = paymentMap.get(channel);
if (service == null) {
throw new IllegalArgumentException("Unsupported payment channel: " + channel);
}
return service;
}
}
常见误区:在工厂方法里直接写业务逻辑,把工厂类变成"万能类"。工厂只负责创建对象,不负责处理业务。
四、策略模式:消除 if-else 的利器
4.1 核心概念
策略模式定义了一系列算法,将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
策略模式的经典结构:
- Context(上下文):持有一个策略引用,负责调用策略
- Strategy(抽象策略):定义算法的公共接口
- ConcreteStrategy(具体策略):实现具体的算法

图3:策略模式完整类图——Context、Strategy接口与具体实现
4.2 工程实践
Spring 中的策略模式:Resource 接口是策略模式的典型应用。Spring 根据资源路径的前缀选择合适的 Resource 实现:
// Spring 根据路径前缀选择不同的 Resource 策略
Resource res1 = ctx.getResource("classpath:config.xml"); // ClassPathResource
Resource res2 = ctx.getResource("file:/path/to/file.txt"); // FileSystemResource
Resource res3 = ctx.getResource("https://example.com"); // UrlResource
业务场景:电商平台的优惠券计算,不同优惠规则(满减、折扣、立减)用策略模式实现:
// 策略接口
public interface DiscountStrategy {
BigDecimal calculate(BigDecimal originalPrice);
String getType();
}
// 满减策略
@Component
public class FullReductionStrategy implements DiscountStrategy {
public BigDecimal calculate(BigDecimal price) {
return price.compareTo(new BigDecimal("200")) >= 0
? price.subtract(new BigDecimal("30")) : price;
}
public String getType() { return "FULL_REDUCTION"; }
}
// 折扣策略
@Component
public class PercentDiscountStrategy implements DiscountStrategy {
public BigDecimal calculate(BigDecimal price) {
return price.multiply(new BigDecimal("0.85"));
}
public String getType() { return "PERCENT_DISCOUNT"; }
}
// 上下文(Spring 自动注入所有策略实现)
@Service
public class DiscountContext {
private final Map<String, DiscountStrategy> strategyMap;
public DiscountContext(List<DiscountStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(DiscountStrategy::getType, s -> s));
}
public BigDecimal applyDiscount(String type, BigDecimal price) {
DiscountStrategy strategy = strategyMap.get(type);
if (strategy == null) {
return price;
}
return strategy.calculate(price);
}
}
策略 vs 工厂:工厂模式关注的是"创建哪个对象",策略模式关注的是"执行哪个算法"。两者经常配合使用:工厂负责创建策略对象,策略负责执行算法。这种组合在 Spring 中随处可见。
五、模板方法模式:不变的框架,变化的细节
5.1 核心概念
模板方法在一个方法中定义算法的骨架,将某些步骤延迟到子类中实现。模板方法让子类可以重新定义算法的某些步骤,而不改变算法的结构。
核心角色:
- AbstractClass(抽象类):定义模板方法和抽象步骤
- ConcreteClass(具体类):实现具体的步骤
钩子方法(Hook Method)是模板方法的关键扩展——父类提供默认实现(通常是空方法),子类可以选择性覆盖。
5.2 工程实践
Spring 中的模板方法是 Spring 框架最核心的设计理念之一:
**AbstractApplicationContext.refresh()**— 这是 Spring 容器初始化的模板方法,定义了 12 个步骤的骨架:
public void refresh() throws BeansException, IllegalStateException {
// 1. 准备刷新
prepareRefresh();
// 2. 获取 BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 准备 BeanFactory
prepareBeanFactory(beanFactory);
// 4. BeanFactory 后置处理(钩子方法)
postProcessBeanFactory(beanFactory);
// 5. 调用 BeanFactory 后置处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注册 Bean 后置处理器
registerBeanPostProcessors(beanFactory);
// 7. 初始化消息源
initMessageSource();
// 8. 初始化事件广播器
initApplicationEventMulticaster();
// 9. 模板方法——留给子类扩展
onRefresh();
// 10. 注册监听器
registerListeners();
// 11. 实例化所有非懒加载单例
finishBeanFactoryInitialization(beanFactory);
// 12. 完成刷新
finishRefresh();
}
其中 postProcessBeanFactory() 和 onRefresh() 是两个钩子方法,Spring Boot 的 ServletWebServerApplicationContext 正是通过覆盖 onRefresh() 来启动内嵌 Web 服务器的。

图4:AbstractApplicationContext.refresh() 模板方法12步骤骨架 2. **JdbcTemplate** — 数据库操作的模板方法:
// JdbcTemplate 定义了查询的骨架:获取连接 → 创建 Statement → 执行 SQL → 处理 ResultSet → 释放资源
jdbcTemplate.query("SELECT * FROM users WHERE id = ?", ps -> ps.setLong(1, userId), rs -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
return user;
});
**RestTemplate**— HTTP 请求的模板方法
业务场景:数据导出功能,不同格式(Excel、PDF、CSV)的导出流程相同,但具体实现不同:
public abstract class DataExporter<T> {
// 模板方法——定义导出骨架
public final void export(List<T> data, OutputStream outputStream) {
validate(data); // 1. 校验数据
List<T> filtered = filter(data); // 2. 过滤(钩子方法)
byte[ ] content = convert(filtered); // 3. 转换(抽象方法)
write(outputStream, content); // 4. 写入
afterExport(outputStream); // 5. 后置处理(钩子方法)
}
private void validate(List<T> data) {
if (data == null || data.isEmpty()) {
throw new IllegalArgumentException("Export data cannot be empty");
}
}
protected List<T> filter(List<T> data) { return data; } // 钩子:默认不过滤
protected abstract byte[ ] convert(List<T> data); // 子类实现
private void write(OutputStream os, byte[ ] content) { /* 写入 */ }
protected void afterExport(OutputStream os) {} // 钩子:默认空
}
模板方法 vs 策略模式:模板方法用继承(子类覆写父类方法),策略模式用组合(注入策略接口)。模板方法适合"骨架固定、步骤可变"的场景,策略模式适合"整体算法可切换"的场景。
六、责任链模式:请求的接力赛
6.1 核心概念
责任链模式让多个对象都有机会处理请求,从而避免请求发送者和接收者之间的耦合。将这些对象连成一条链,请求沿着链传递,直到有对象处理它为止。
两种变体:
- 纯责任链:要么处理请求,要么传给下一个(不处理则不传)
- 不纯责任链:每个处理器都可以对请求做部分处理,然后传给下一个

图5:Spring MVC 请求处理全链路——责任链模式
6.2 工程实践
Spring 中的责任链模式无处不在:
- Servlet Filter Chain — 最经典的责任链:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 前置处理
logRequest(request);
// 传递给下一个过滤器
chain.doFilter(request, response);
// 后置处理
logResponse(response);
}
- Spring Security Filter Chain — 多过滤器串联的安全链:
UsernamePasswordAuthenticationFilter → BasicAuthenticationFilter → ... → FilterSecurityInterceptor
- Spring MVC HandlerInterceptor — 拦截器责任链:
public class AuthInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
if (!isValidToken(token)) {
throw new UnauthorizedException();
}
return true; // 返回 true 继续传递,false 则中断
}
}
- Spring AOP 拦截器链 —
MethodInterceptor链式调用
业务场景:订单审核流程,不同级别的审批人在链上按顺序处理:
// 审批处理器抽象
public abstract class ApprovalHandler {
protected ApprovalHandler next;
public ApprovalHandler setNext(ApprovalHandler next) {
this.next = next;
return next; // 链式构建
}
public void handle(Order order) {
if (canApprove(order)) {
doApprove(order);
} else if (next != null) {
next.handle(order);
} else {
throw new RuntimeException("订单审批链已到尽头,无法处理");
}
}
protected abstract boolean canApprove(Order order);
protected abstract void doApprove(Order order);
}
// 构建链
ApprovalHandler chain = new ManagerHandler();
chain.setNext(new DirectorHandler())
.setNext(new VPHandler())
.setNext(new CEOHandler());
chain.handle(order);
关键设计要点:
- 链的构建要灵活,支持动态增减处理器
- 每个处理器职责要单一,不要一个处理器干多件事
- 注意链的长度,过长会影响性能,也可能导致请求被遗漏
- 不要在链中修改共享状态,除非你很清楚自己在做什么
七、观察者模式:一对多的自动通知
7.1 核心概念
观察者模式定义了对象之间的一对多依赖关系,当一个对象(主题/Subject)状态发生变化时,所有依赖它的对象(观察者/Observer)都会得到通知并自动更新。
核心角色:
- Subject(主题):维护观察者列表,提供注册/移除/通知方法
- Observer(观察者):定义更新接口
- ConcreteSubject(具体主题):状态变化时通知所有观察者
- ConcreteObserver(具体观察者):收到通知后执行具体逻辑

图6:Spring 事件机制——观察者模式完整流程
7.2 工程实践
Spring 事件机制是观察者模式的最佳实践:
// 1. 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
private final Long orderId;
public OrderCreatedEvent(Object source, Long orderId) {
super(source);
this.orderId = orderId;
}
public Long getOrderId() { return orderId; }
}
// 2. 发布事件
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;
@Transactional
public void createOrder(Order order) {
// 保存订单...
// 发布事件
publisher.publishEvent(new OrderCreatedEvent(this, order.getId()));
}
}
// 3. 观察者1:发送通知
@Component
public class OrderNotificationListener {
@EventListener
@Async // 异步处理,不阻塞主流程
public void handleOrderCreated(OrderCreatedEvent event) {
// 发送短信/推送通知
}
}
// 4. 观察者2:更新统计
@Component
public class OrderStatisticsListener {
@EventListener
@Order(2) // 控制执行顺序
public void handleOrderCreated(OrderCreatedEvent event) {
// 更新销量统计
}
}
Spring 事件机制的核心实现:
ApplicationEventMulticaster负责广播事件- 默认使用
SimpleApplicationEventMulticaster,同步调用所有监听器 - 可通过
@Async注解让监听器异步执行 - 事件可以用
@TransactionalEventListener绑定事务阶段(提交后、回滚后等)
观察者 vs 发布-订阅:发布-订阅模式是观察者模式的演进,增加了 Broker 中间层,实现发布者和订阅者的完全解耦。消息队列(Kafka、RocketMQ)就是发布-订阅的工程实现。
常见陷阱:
- 同步观察者会阻塞主流程——订单创建后同步发邮件,如果邮件服务慢,订单创建也慢
- 事件循环——A 事件触发 B 事件,B 事件又触发 A 事件
- 事务边界——事件发布在事务提交前还是提交后,直接影响数据一致性
八、代理模式:控制访问的替身
8.1 核心概念
代理模式为另一个对象提供一个替身或占位符,以控制对这个对象的访问。代理和被代理对象实现相同的接口,客户端无感知。
代理模式的三种类型:
- 静态代理:编译时生成代理类,需要为每个被代理类手写代理
- JDK 动态代理:运行时生成代理类,要求被代理类必须实现接口
- CGLIB 动态代理:运行时生成代理类,通过继承实现,不需要接口

图7:代理模式三种实现对比——静态代理、JDK动态代理、CGLIB代理
8.2 工程实践
Spring AOP 是代理模式的巅峰应用:
// @Transactional 注解的背后就是代理模式
@Service
public class UserService {
@Transactional
public void createUser(User user) {
userMapper.insert(user);
// 如果这里抛异常,事务自动回滚
}
}
// Spring 实际执行的是:
// 1. 创建 UserService 的代理对象
// 2. 调用 createUser() 时,代理对象先执行事务拦截器
// 3. 事务拦截器:开启事务 → 调用目标方法 → 提交/回滚事务
JDK 动态代理 vs CGLIB 代理:
| 特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 原理 | 基于接口,java.lang.reflect.Proxy |
基于继承,ASM 字节码操作 |
| 要求 | 必须实现接口 | 不需要接口,但不能代理 final 类/方法 |
| 性能 | 反射调用,创建开销小 | 字节码生成,创建开销大,调用快 |
| Spring 默认 | 有接口时默认使用 | 无接口时使用 |
MyBatis Mapper 代理是另一个经典案例:
// 你只定义了一个接口,MyBatis 通过动态代理为你生成实现
@Mapper
public interface UserMapper {
User selectById(Long id);
}
// MyBatis 内部做的事情:
// 1. 通过 JDK 动态代理创建 Mapper 接口的代理对象
// 2. 调用方法时,代理对象拦截调用
// 3. 根据方法名查找对应的 SQL(XML 或注解)
// 4. 执行 SQL,映射结果,返回
代理模式 vs 装饰器模式:代理模式强调的是"控制访问"(权限检查、延迟加载、远程调用),装饰器模式强调的是"增强功能"。AOP 中既有代理的影子(控制事务边界),也有装饰器的影子(添加日志、缓存)。
代理模式的性能影响:
- 每次代理调用都会多一层方法调用栈,但影响微乎其微(纳秒级)
- 真正影响性能的是代理逻辑本身(比如 AOP 切面中的反射、数据库查询等)
- Spring 的
@Transactional代理是单例代理,不会有额外对象创建开销
九、六种模式的关系与组合
9.1 模式之间的关联
这六种模式不是孤立的,它们在实际工程中经常组合使用:
- 工厂 + 策略:工厂创建策略对象,策略执行算法。Spring 的
InstantiationStrategy正是如此。 - 模板方法 + 策略:模板方法定义骨架,某些步骤用策略模式实现。
JdbcTemplate的StatementCallback就是策略接口。 - 责任链 + 观察者:Spring Security 的 Filter Chain 中,每个 Filter 既是责任链上的节点,也是安全事件的观察者。
- 代理 + 责任链:Spring AOP 的拦截器链——多个
MethodInterceptor组成责任链,而 AOP 本身又是通过代理实现。 - 工厂 + 代理:Spring 的
BeanPostProcessor可以在 Bean 初始化后生成代理对象,本质上是用工厂创建代理。
9.2 Spring 中的组合范例
以 Spring 处理一个 HTTP 请求的全链路为例:
请求到达 → DispatcherServlet(前端控制器)
→ HandlerMapping(策略模式:根据请求选择 Handler)
→ HandlerInterceptor 链(责任链模式:preHandle → handler → postHandle)
→ HandlerAdapter(适配器模式:调用真正的 Controller)
→ Controller 中注入的 Service(代理模式:AOP 增强)
→ Service 中的业务逻辑(策略模式/模板方法模式)
→ 返回结果
十、设计模式的选择原则
10.1 什么时候用哪种模式
| 你的问题 | 合适的模式 |
|---|---|
| 创建对象的逻辑复杂,或需要根据条件动态选择具体类 | 工厂模式 |
| 大量 if-else 或 switch 分支选择不同算法 | 策略模式 |
| 多个流程有相同的骨架,但部分步骤不同 | 模板方法 |
| 多个处理者需要依次处理同一个请求 | 责任链 |
| 一个对象的状态变化需要通知多个其他对象 | 观察者 |
| 需要对某个对象的方法调用做增强或控制 | 代理模式 |

图8:六种设计模式选择决策树
10.2 不要过度设计
设计模式是解决特定问题的手段,不是目的。几个实用原则:
- 先写简单的代码,再重构到模式:不要一开始就想着"我要用策略模式",先写 if-else,等分支超过 3 个时再重构
- 模式不是越多越好:一个类实现了 3 种模式,大概率是过度设计了
- 关注业务语义,而不是模式名称:代码评审时说"这里用责任链解耦了审批流程"比"这里用了责任链模式"更有价值
- Spring 已经帮你做了很多:先理解 Spring 用了哪些模式,再用这些模式写自己的业务代码
十一、总结
这六种设计模式是 Java 后端工程师日常工作中最常用的模式。它们不是面试的八股,而是你读 Spring 源码时的钥匙、写业务代码时的工具、做架构设计时的积木。
记住三点:
- 模式服务于业务——先有业务问题,再有模式选择
- Spring 是最好的老师——阅读 Spring 源码,理解模式在真实工程中的用法
- 组合优于继承——策略模式比模板方法更灵活,因为组合比继承更容易扩展
面试官视角提问
以下问题来自真实面试场景,建议在理解文章内容后思考,尝试用自己的话回答。
Q1:Spring 的 BeanFactory 和 ApplicationContext 分别用了什么设计模式?设计上有何不同?为什么需要两个接口?
Q2:你在项目中用策略模式替代了复杂的 if-else,但后来发现策略类数量膨胀到 30+,维护成本很高。你会怎么优化?有没有遇到过策略模式反被滥用的情况?
Q3:模板方法模式和策略模式的核心区别是什么?在什么场景下你更倾向于用模板方法而不是策略模式?为什么说"组合优于继承"在这个选择中体现得很明显?
Q4:Spring Security 的 Filter Chain 和 Spring MVC 的 HandlerInterceptor 都用了责任链模式,它们的实现有什么不同?如果让你设计一个可动态增减处理器的责任链,你会怎么设计?
Q5:Spring 事件机制中,@EventListener 默认是同步的。如果一个订单创建事件需要触发 5 个监听器,其中一个调用外部服务耗时 3 秒,会对系统产生什么影响?你会怎么解决?使用 @Async 又带来了什么新问题?
Q6:Spring AOP 什么时候用 JDK 动态代理,什么时候用 CGLIB?如果一个类实现了接口但你也想代理它没有在接口中定义的方法,会发生什么?@Transactional 自调用失效是什么原因,怎么解决?
Q7:观察者模式和发布-订阅模式有什么区别?为什么说"消息队列是实现发布-订阅的技术手段,但不是观察者模式"?在你的项目中,什么场景用 Spring 事件就够了,什么场景必须上消息队列?
Q8:请设计一个电商订单系统,要求:支持多种支付方式(微信、支付宝、银联)、订单创建后自动发通知和更新统计、不同金额的订单走不同审批流程、订单状态变更需要记录日志和执行后置操作。请说明你会用到哪些设计模式,以及为什么这么设计。
参考回答
A1:BeanFactory vs ApplicationContext
设计模式:
BeanFactory是工厂方法模式的体现——定义了获取 Bean 的接口,由DefaultListableBeanFactory等具体工厂实现ApplicationContext在工厂方法之上,额外使用了模板方法模式(refresh()方法)、观察者模式(事件发布/监听)、策略模式(ResourceLoader选择不同资源加载策略)
设计上的不同:
| 维度 | BeanFactory | ApplicationContext |
|---|---|---|
| 实例化时机 | 延迟加载(lazy-init) | 预初始化(默认 eager) |
| 功能范围 | 仅 IoC 容器基本功能 | IoC + AOP + 事件 + 国际化 + 资源加载 |
| BeanPostProcessor | 需手动注册 | 自动注册 |
| 使用场景 | 内存敏感环境(如 Applet) | 企业级应用(99% 场景) |
为什么需要两个接口?
- 单一职责原则:
BeanFactory只做 IoC 容器,接口简洁;ApplicationContext是它的超集,提供更丰富的企业级能力 - 向后兼容:
BeanFactory是最初的设计,ApplicationContext是后续扩展,保留前者保证兼容性 - 分层设计:
BeanFactory是底层"发动机",ApplicationContext是"整车"——你可以只换发动机(比如用StaticApplicationContext做单元测试),而不影响上层接口
A2:策略模式膨胀的优化
优化策略(按优先级排列):
- 策略分组 + 工厂路由:将 30+ 策略按业务领域分组(如支付策略、优惠策略、通知策略各自独立),每个领域有独立的策略工厂,避免一个巨型策略工厂
// 不要把所有策略塞进一个 Map
// 而是按领域拆分
Map<String, PaymentStrategy> paymentStrategies;
Map<String, DiscountStrategy> discountStrategies;
- 策略 + 规格模式(Specification Pattern):不是用简单的
type字符串匹配,而是让每个策略声明自己的适用条件,由路由引擎自动匹配
public interface DiscountStrategy {
boolean supports(Order order); // 策略自己声明适用条件
BigDecimal calculate(Order order);
}
// 路由时自动匹配
DiscountStrategy s = strategies.stream()
.filter(st -> st.supports(order))
.findFirst().orElse(defaultStrategy);
- 用规则引擎替代简单策略:当策略数量膨胀到 30+,且规则频繁变化,考虑引入规则引擎(Drools、EasyRules),将策略配置化而不是硬编码
- 策略模式反被滥用的情况(我面试时常问的跟进问题):
- 一个策略里只有一行代码,也要拆成独立的策略类——过度设计
- 策略之间逻辑高度相似(80% 相同),没有抽取公共抽象层——代码重复
- 策略类依赖了太多外部服务,导致单元测试困难——职责不清
- 核心判断标准:如果你删掉策略模式,用 if-else 重写,代码量减少且可读性提高,那你就是在滥用策略模式
A3:模板方法 vs 策略模式
核心区别:
| 维度 | 模板方法 | 策略模式 |
|---|---|---|
| 复用方式 | 继承(子类覆写父类方法) | 组合(注入策略接口) |
| 控制权 | 父类控制流程(好莱坞原则:"Don't call us, we'll call you") | Context 委托给策略,策略有更多自主权 |
| 扩展粒度 | 只能替换算法中的某些步骤 | 可以替换整个算法 |
| 灵活性 | 子类只能覆写父类暴露的钩子方法 | 策略可以随时替换,甚至运行时动态切换 |
| 代码复用 | 父类实现公共步骤,子类复用 | 策略之间代码复用需要额外手段(抽象类等) |
什么时候用模板方法:
- 算法骨架非常稳定,比如
JdbcTemplate的"获取连接→执行→释放资源"流程几十年不变 - 需要严格控制流程顺序,不允许子类改变调用次序
- 有大量公共代码需要复用(如
DataExporter示例中的校验、写入逻辑)
"组合优于继承"在这个选择中的体现:
- 模板方法用继承,子类与父类强耦合——父类一改,所有子类受影响
- 策略模式用组合,策略接口不变时,Context 和策略可以独立演化
- 实际工程中,优先考虑策略模式——除非你真的很确定骨架不会变
- 这就是为什么 Spring 的
JdbcTemplate把StatementCallback作为策略接口注入,而不是让用户继承JdbcTemplate来覆写方法
A4:Filter Chain vs HandlerInterceptor + 动态责任链设计
实现不同:
| 维度 | Servlet Filter Chain | Spring HandlerInterceptor |
|---|---|---|
| 标准 | Java EE / Servlet 规范 | Spring MVC 专有 |
| 作用范围 | 所有请求(包括静态资源) | 仅进入 DispatcherServlet 的请求 |
| 粒度 | 只能访问 Request/Response 对象 | 可以访问 Handler(Controller 方法) |
| 链控制 | chain.doFilter() 传递 |
preHandle() 返回 true/false |
| 执行顺序 | Filter 链 → Servlet → Filter 链返回 | preHandle → Handler → postHandle → afterCompletion |
| 中断后 | 请求直接返回,不进入 Servlet | 返回 false 后,后续拦截器和 Handler 都不执行 |
Spring Security Filter Chain 的特殊之处:
- Spring Security 的
FilterChainProxy内部维护了多个安全过滤器链(SecurityFilterChain),根据 URL 匹配选择不同的链 - 每个链包含多个 Security Filter(
UsernamePasswordAuthenticationFilter→BasicAuthenticationFilter→ ... →FilterSecurityInterceptor),这些 Filter 本身就是责任链
动态责任链设计(面试官想听的回答):
// 1. 定义处理器接口
public interface ChainHandler<T> {
void handle(T context, Chain<T> chain);
int order(); // 排序用
default boolean enabled() { return true; } // 动态开关
}
// 2. 链的实现
public class Chain<T> {
private final List<ChainHandler<T>> handlers;
private int index = 0;
public Chain(List<ChainHandler<T>> handlers) {
this.handlers = handlers;
}
public void proceed(T context) {
while (index < handlers.size()) {
ChainHandler<T> handler = handlers.get(index);
if (handler.enabled()) {
index++;
handler.handle(context, this); // handler 内部决定是否调用 chain.proceed()
return;
}
index++;
}
}
// 动态增删
public void addHandler(ChainHandler<T> handler) {
handlers.add(handler);
handlers.sort(Comparator.comparingInt(ChainHandler::order));
}
public void removeHandler(Class<?> handlerType) {
handlers.removeIf(h -> handlerType.isInstance(h));
}
}
关键设计要点:
- 用
enabled()方法支持运行时开关(通过配置中心动态启停) - 每个 handler 调用
chain.proceed()决定是否继续传递——控制权交给 handler - 用
order()决定顺序,新增 handler 时自动排序 - 考虑线程安全——如果运行时动态增删,用
CopyOnWriteArrayList或加锁
A5:Spring 事件同步阻塞与异步化
影响分析:
假设订单创建接口的 P99 目标是 200ms,5 个同步监听器各自耗时:
- 监听器1(发通知):50ms
- 监听器2(更新统计):20ms
- 监听器3(调用外部服务):3000ms ← 致命
- 监听器4(记录日志):10ms
- 监听器5(发券):30ms
总耗时 = 50 + 20 + 3000 + 10 + 30 = 3110ms,远超 200ms 目标,用户体验极差。
解决方案(按推荐顺序):
**@Async**异步化(最常用):
@EventListener
@Async
public void handleOrderCreated(OrderCreatedEvent event) { ... }
需要配置线程池:
@Bean("eventExecutor")
public Executor eventExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(500);
executor.setRejectedExecutionHandler(new CallerRunsPolicy());
return executor;
}
**@TransactionalEventListener**:对于需要"事务提交后"再执行的监听器(如发通知),使用事务事件监听器,避免事务未提交就执行- 消息队列解耦:对于耗时 3 秒的外部服务调用,发一条 MQ 消息,由消费者异步处理,彻底解耦
使用 **@Async** 带来的新问题:
- 事务一致性:异步执行时原事务可能已提交,如果监听器依赖事务内的数据,可能读到脏数据 → 用
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - 异常丢失:异步线程的异常不会抛给调用方,静默失败 → 必须配置全局
AsyncUncaughtExceptionHandler - 线程池拒绝:流量高峰时队列满,任务被拒绝 → 配置
CallerRunsPolicy或DiscardOldestPolicy,并监控线程池指标 - 上下文传递:
RequestContextHolder、SecurityContext等线程绑定的上下文在异步线程中丢失 → 使用TaskDecorator传递上下文 - 顺序性:异步后执行顺序不可控 → 如果监听器之间有依赖,用
@Order+ 同步模式,或通过 MQ 的 topic 分区保证顺序
A6:JDK 动态代理 vs CGLIB + 自调用失效
Spring 的选择逻辑(DefaultAopProxyFactory):
public AopProxy createAopProxy(AdvisedSupport config) {
if (config.isProxyTargetClass() || !hasUserSuppliedInterfaces(config)) {
// proxyTargetClass=true 或目标类没有实现接口 → CGLIB
return new ObjenesisCglibAopProxy(config);
}
// 默认:有接口 → JDK 动态代理
return new JdkDynamicAopProxy(config);
}
Spring Boot 2.x 起默认行为变化:spring.aop.proxy-target-class=true(默认用 CGLIB),因为 Spring Boot 认为大部分场景直接用 CGLIB 更简单
类实现了接口但想代理非接口方法:
- 如果使用 JDK 动态代理(默认行为),代理对象只能调用接口中定义的方法
- 如果你拿到的是 JDK 代理对象,调用
((UserService) proxy).nonInterfaceMethod()会抛出ClassCastException - 解决方案:
@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用 CGLIB
**@Transactional** 自调用失效:
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// ... 事务生效
this.updateInventory(order); // ← 自调用,事务不生效!
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateInventory(Order order) {
// 这里的事务注解不会生效
}
}
失效原因:@Transactional 是通过 AOP 代理实现的。this.updateInventory() 是直接调用目标对象的方法,绕过了代理,事务拦截器不会被触发。
解决方案:
- 注入自己(最常见):
@Autowired private OrderService self;然后用self.updateInventory(order) - 拆到另一个 Service(推荐):将
updateInventory移到InventoryService中,通过注入调用 - AopContext.currentProxy():
((OrderService) AopContext.currentProxy()).updateInventory(order),需要@EnableAspectJAutoProxy(exposeProxy = true) - 使用 AspectJ 编译期织入(不推荐,太重):不走代理,直接修改字节码
A7:观察者模式 vs 发布-订阅模式
核心区别:
| 维度 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 耦合度 | 主题知道观察者(有观察者列表) | 发布者和订阅者完全解耦,通过 Broker 通信 |
| 通信方式 | 主题直接调用观察者的 update() 方法 |
发布者发消息到 Topic,Broker 推送给订阅者 |
| 作用范围 | 同一进程内(JVM 内) | 跨进程、跨服务、跨机房 |
| 典型实现 | Spring ApplicationEvent、Java Observer |
Kafka、RocketMQ、RabbitMQ |
"消息队列是实现发布-订阅的技术手段,但不是观察者模式":
- 观察者模式是设计模式(概念层面的抽象),定义的是角色和交互关系
- 消息队列是中间件(技术层面的实现),是发布-订阅模式的一种工程落地方式
- 就像"策略模式"是概念,"Spring 的
List<DiscountStrategy>自动注入"是具体实现
场景选择:
| 场景 | Spring 事件 | 消息队列 |
|---|---|---|
| 同一服务内,两个模块之间解耦 | ✅ 最佳选择 | ❌ 杀鸡用牛刀 |
| 需要事务一致性(事务提交后执行) | ✅ @TransactionalEventListener |
❌ 需要额外设计 |
| 跨服务调用(订单服务 → 通知服务) | ❌ 做不到 | ✅ 唯一选择 |
| 需要消息持久化,保证不丢 | ❌ 重启丢失 | ✅ 消息持久化到磁盘 |
| 需要重试、死信队列 | ❌ 需自己实现 | ✅ 内置支持 |
| 流量削峰(双十一订单洪峰) | ❌ 内存扛不住 | ✅ MQ 缓冲 |
| 数据同步(Canal 监听 binlog → MQ → 多消费者) | ❌ 做不到 | ✅ 标准方案 |
面试加分点:能说出"Spring 事件 + 本地事务表 + 定时补偿"的中间方案——在不需要 MQ 的场景下,用 Spring 事件 + 数据库事务表实现"最终一致性"的轻量级方案。
A8:电商订单系统设计综合题
支付方式(工厂模式 + 策略模式):
- 用工厂模式创建支付渠道实例(
PaymentFactory.getPayment(channel)) - 用策略模式让每个支付渠道实现统一的
PaymentStrategy接口(pay()、refund()、query()) - Spring 自动注入
List<PaymentStrategy>,按渠道类型路由
订单创建后自动发通知 + 更新统计(观察者模式):
- 发布
OrderCreatedEvent事件 - 通知监听器:
@Async异步发送短信/推送 - 统计监听器:
@TransactionalEventListener(phase = AFTER_COMMIT)保证事务提交后再更新统计 - 如果需要跨服务通知,用消息队列发送
ORDER_CREATED消息
不同金额的审批流程(责任链模式):
// 审批链:金额 < 1000 → 经理审批
// 1000 ≤ 金额 < 10000 → 经理 → 总监
// 金额 ≥ 10000 → 经理 → 总监 → VP
ApprovalChain chain = new ApprovalChain();
chain.addHandler(new ManagerHandler()) // 所有订单经理先审批
.addHandler(new DirectorHandler()) // >= 1000 总监审批
.addHandler(new VPHandler()); // >= 10000 VP 审批
订单状态变更记录日志 + 后置操作(观察者模式 + 代理模式):
- 用观察者模式监听
OrderStatusChangedEvent,日志记录器、后置操作处理器各自监听 - 用代理模式(AOP)在
OrderService的状态变更方法上织入日志切面:@AfterReturning("execution(* OrderService.updateStatus(..)) && args(orderId, status)", argNames = "orderId,status")
整体架构:
订单创建请求
→ OrderController(接收请求)
→ OrderService.createOrder()
◆ 代理模式:@Transactional 事务代理
◆ 工厂模式 + 策略模式:PaymentStrategy 支付
◆ 观察者模式:发布 OrderCreatedEvent
→ @Async 通知监听器(发短信/推送)
→ @TransactionalEventListener 统计监听器(更新销量)
→ MQ 消息(跨服务通知)
◆ 责任链模式:根据金额走审批链
◆ 观察者模式:状态变更发布 OrderStatusChangedEvent
→ 日志监听器(记录操作日志)
→ 后置操作监听器(释放优惠券、更新库存等)
为什么这样设计:
- 支付方式、审批流程、通知方式都是变化点,用策略/责任链/观察者封装变化
- 每种模式只解决一个维度的问题,单一职责
- 新增支付渠道只需实现
PaymentStrategy接口,开闭原则 - 各组件通过事件/接口通信,低耦合
这是面试中常见的"系统设计 + 设计模式"综合题,面试官想看的不是你把所有模式列一遍,而是你为什么选这个模式而非另一个,以及模式之间的组合方式。