模板方法模式在 JDK 和 Spring 中有哪些典型应用?
简化版
模板方法模式在框架中很常见,因为框架通常负责控制主流程,业务代码只实现扩展点。典型例子包括 Spring 的 JdbcTemplate、RestTemplate 部分调用流程、Servlet 的 service/doGet/doPost 思想,以及测试框架中的生命周期方法。
详细版
模板方法模式非常适合框架设计。框架先定义稳定流程,再把变化点留给用户代码。
常见例子:
JdbcTemplate:封装获取连接、创建语句、执行 SQL、处理异常、释放资源等流程,调用方只关注 SQL 和结果映射。- Servlet:容器调用
service(),再根据 HTTP 方法分发到doGet()、doPost()等方法。 - 测试框架:统一执行测试生命周期,用户通过 setup、test、teardown 等扩展点补充逻辑。
- Spring Bean 生命周期:容器控制创建、依赖注入、初始化、销毁流程,业务对象通过回调参与部分阶段。
这些例子的共同点是:框架控制流程,业务代码填补变化点。
完整版教学
一、为什么框架特别喜欢模板方法
框架的职责通常是控制复杂流程,屏蔽通用细节。
比如数据库访问有很多固定工作:
- 获取连接;
- 创建语句;
- 执行 SQL;
- 处理结果;
- 处理异常;
- 释放资源。
如果每个业务方法都手写这些逻辑,重复又容易出错。框架用模板方法把公共流程固定下来,让业务代码只关注真正变化的部分。
二、JdbcTemplate 是经典例子
JdbcTemplate 的思想就是模板方法风格:它封装 JDBC 样板代码,让调用方只提供 SQL、参数和结果映射。
调用方常写:
jdbcTemplate.query(sql, rowMapper);
背后流程大致是:
获取连接 -> 创建 PreparedStatement -> 设置参数 -> 执行查询 -> 遍历 ResultSet -> 调用 RowMapper -> 释放资源
其中资源管理、异常转换、连接处理由模板负责;每一行怎么映射成对象,由调用方提供。
严格说,JdbcTemplate 也结合了回调思想,但整体上体现了“模板控制流程,用户提供变化点”的模板方法思想。
三、Servlet 的 service/doGet/doPost 也很好理解
Servlet 容器接收请求后,会调用 Servlet 的 service() 方法。
service() 根据 HTTP 方法分发到:
doGet()doPost()doPut()doDelete()
开发者通常重写 doGet() 或 doPost() 来实现业务逻辑。
这体现了模板方法思想:容器和父类控制请求处理主流程,子类实现具体请求方法处理。
四、Spring 生命周期也有模板思想
Spring 容器创建 Bean 的流程是稳定的:
实例化 -> 属性填充 -> 初始化前处理 -> 初始化 -> 初始化后处理 -> 使用 -> 销毁
业务代码可以通过接口、注解或后置处理器参与某些阶段。例如初始化回调、销毁回调、BeanPostProcessor。
虽然这不是最传统的继承版模板方法,但体现了框架控制流程、业务扩展步骤的同类思想。
五、面试回答要避免说得太绝对
有些框架源码不是纯粹的 GoF 模板方法模式,而是模板方法、回调、策略、工厂等模式组合使用。
例如 JdbcTemplate 里既有模板流程,也大量使用回调接口。面试中更稳妥的表述是:它体现了模板方法的思想,而不是强行说它只用了模板方法模式。
这会显得更准确。
六、框架中的模板与回调变体
JdbcTemplate 固定资源获取、执行、异常转换、释放 4 类步骤,调用者通常只提供 SQL 和回调;一次异常仍由模板保证连接释放。
JdbcTemplate.execute -> acquire -> callback.doInConnection -> translate -> release
这里真正需要保护的是父类掌握的流程不变量:客户端只能进入模板入口,子类只能改写约定好的步骤。评审时应逐步标出哪些行为固定、哪些行为必须实现、哪些行为只是可选钩子,避免父类和子类互相猜测。
七、继承边界与流程契约验证
| 检查维度 | 应确认的内容 |
|---|---|
| 机制正确性 | Servlet 的 service 分派和 Spring Template 类体现不同程度的骨架控制,要说清实际扩展点。 |
| 适用边界 | 框架可能用回调对象而非子类覆写实现变化点,这属于模板思想与回调组合,回答时不要硬说所有例子都严格对应 GoF 类图。 |
| 测试证据 | 用框架真实入口触发模板,验证回调时机、异常转换和资源清理,不能只直接调用扩展方法 |
| 工程代价 | 重点评估父类改动会影响多少子类、protected 扩展面有多大,以及新增变化是否迫使无关子类一起修改 |
易错点:Servlet 的 service 分派和 Spring Template 类体现不同程度的骨架控制,要说清实际扩展点。
八、常见误区与追问
- 误区:JdbcTemplate 要求业务类继承它。 常见用法是组合 JdbcTemplate 并传入回调,模板思想存在但扩展机制不必是业务子类继承。
- 误区:模板方法要求所有步骤都由子类覆写。 稳定步骤应在父类直接实现;只有必要变化点才做抽象步骤,可选变化点才做有默认实现的钩子。
- 追问:HttpServlet 的模板入口是什么? 容器调用 service,HttpServlet 再根据 HTTP 方法分派到 doGet、doPost 等受保护扩展点。
- 追问:模板方法为什么常声明为 final? 为了防止子类改写算法骨架和步骤顺序;若框架有意允许重写,则必须明确不变量和扩展契约。
- 追问:模板方法最关键的回归测试是什么? 是父类契约测试:所有子类都必须满足固定步骤顺序、公共前后置行为和异常清理规则。
九、加强记忆
框架中的模板方法思想可以记成“框架定流程,业务填空”。JDK、Servlet、Spring、JdbcTemplate 都能看到类似影子:通用流程由框架掌控,变化点通过继承、回调或接口留给业务代码。