尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
你写 JdbcTemplate 的 callback 写了三年——这就是模板方法,但你从没把它当设计模式
# 你写 JdbcTemplate 的 callback 写了三年——这就是模板方法但你从没把它当设计模式 大多数 Java 程序员对模板方法模式的认知停留在定义一个抽象方法让子类实现。这个认知没错但只覆盖了模板方法 20% 的用法——剩下的 80% 藏在各种 Spring 组件里你天天在用却从来没把它跟设计模式挂钩。 ## Spring JdbcTemplate模板方法的集大成者 写一段你最熟悉的代码 java jdbcTemplate.query(SELECT * FROM orders WHERE status ?, ps - ps.setString(1, PAID), // 步骤1设置参数 (rs, rowNum) - { // 步骤2处理每一行结果 Order order new Order(); order.setId(rs.getLong(id)); order.setAmount(rs.getBigDecimal(amount)); return order; } ); 这段代码里query() 方法里面发生了什么 1. 获取连接 2. 创建 PreparedStatement 3. 设置参数你传进去的 lambda 4. 执行查询 5. 遍历 ResultSet每行调你的 RowMapper 6. 关闭 ResultSet 7. 关闭 Statement 8. 归还连接到连接池 总共 8 个步骤你只关心第 3 步和第 5 步。剩下的 6 个步骤是 JdbcTemplate 帮你写好的——这就是模板方法模式的本质**固定流程 可变步骤**。 把 JdbcTemplate.query() 简化后是这样的 java public List query(String sql, PreparedStatementSetter pss, RowMapper rowMapper) { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { // 固定步骤1-2获取连接、创建 Statement conn dataSource.getConnection(); ps conn.prepareStatement(sql); // 可变步骤3设置参数——你来决定 pss.setValues(ps); // 固定步骤4执行查询 rs ps.executeQuery(); List results new ArrayList(); int rowNum 0; while (rs.next()) { // 可变步骤5映射每一行——你来决定 results.add(rowMapper.mapRow(rs, rowNum)); } return results; } catch (SQLException e) { throw new DataAccessException(e); } finally { // 固定步骤6-8关闭资源 closeQuietly(rs); closeQuietly(ps); closeQuietly(conn); } } 这就是模板方法的 callback 变体。GoF 原版是用继承实现——父类定义模板方法子类重写抽象方法。Spring 升级成了组合——把可变步骤抽成 callback 接口通过参数传进来。好处是你不用为了每种查询都写一个子类直接传 lambda。 ## ApplicationContext 的 refresh() 是 Spring 的启动模板 Spring 容器的启动流程是模板方法模式的典型应用 java // AbstractApplicationContext.refresh() —— 模板方法 public void refresh() { prepareRefresh(); // 1. 准备刷新 ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory(); // 2. 获取 BeanFactory prepareBeanFactory(beanFactory); // 3. 准备 BeanFactory postProcessBeanFactory(beanFactory); // 4. 后处理模板方法——子类可重写 invokeBeanFactoryPostProcessors(beanFactory); // 5. 执行 BeanFactoryPostProcessor registerBeanPostProcessors(beanFactory); // 6. 注册 BeanPostProcessor initMessageSource(); // 7. 初始化消息源 initApplicationEventMulticaster(); // 8. 初始化事件广播器 onRefresh(); // 9. 模板方法——留给子类的钩子 registerListeners(); // 10. 注册监听器 finishBeanFactoryInitialization(beanFactory); // 11. 实例化所有单例 Bean finishRefresh(); // 12. 完成刷新 } 注意第 4 步和第 9 步——postProcessBeanFactory() 和 onRefresh() 都是空的 protected 方法专门留给子类扩展。AbstractRefreshableWebApplicationContext 在 postProcessBeanFactory() 里注册了 request/session scopeSpringApplication 的嵌入式 Web 容器在 onRefresh() 里启动了 Tomcat。 框架负责固定流程启动顺序、异常处理、生命周期管理你只关心可变部分注册额外 scope、启动内嵌容器。这就是模板方法的威力——不是因为算法多复杂而是因为它把一个容易写乱的流程用固定的框架组织起来了。 ## 但模板方法的继承版本有两个致命问题 GoF 原版的模板方法是基于继承的 java public abstract class DataExporter { // 模板方法——final 防止子类改流程 public final void export() { List data fetchData(); String formatted formatData(data); // 可变步骤1 validate(formatted); // 可变步骤2 write(formatted); // 可变步骤3 } protected abstract List fetchData(); protected abstract String formatData(List data); protected abstract void validate(String content); protected abstract void write(String content); } public class ExcelExporter extends DataExporter { // 实现四个抽象方法 } public class PdfExporter extends DataExporter { // 实现四个抽象方法 } 问题一**一个子类只能重写一个模板方法的行为。** 如果 Excel 导出有带有表头和不带表头两种变体你怎么办再写两个 ExcelExporter 的子类这就是类爆炸。 问题二**所有子类必须实现所有抽象方法。** 哪怕你的 PdfExporter 不需要 validate() 这一步你也得写个空方法。Java 8 的 default 方法可以缓解但模板方法的核心痛点是当你需要**组合**可变行为而非**继承**时继承是错的。 这就是为什么 Spring 用了 callback 版本——把可变步骤抽成接口用组合替代继承 java public class DataExporter { public void export(DataFetcher fetcher, DataFormatter formatter, DataWriter writer) { List data fetcher.fetch(); String formatted formatter.format(data); writer.write(formatted); } } // 使用时 exporter.export( () - jdbcTemplate.query(SELECT ..., rowMapper), // fetch data - jsonMapper.writeValueAsString(data), // format content - Files.write(path, content.getBytes()) // write ); 模板方法变成了策略模式的变体。但核心思路没变**固定骨架不动可变细节外挂。** ## 什么时候用模板方法、什么时候用策略模式 这两个经常被搞混因为看起来都是在替换算法。区别在这 - **模板方法**流程固定步骤可变。你控制不了顺序比如必须先 open 再 execute 最后 close只能替换某一步的行为。 - **策略模式**整个算法可以整体替换。你可以选冒泡排序也可以选快排顺序是你自己决定的。 判断标准很简单看调用者是不是你自己写的。JdbcTemplate 的模板是你写的还是 Spring 写的Spring 写的你只填空。策略模式里的排序算法是你选的你调用的——步骤和顺序都是你掌控的。 我在做一个用卡皮巴拉讲设计模式的小程序「爪爪代码冒险记」模板方法这章用做菜来讲——固定步骤是洗菜→切菜→炒→装盘但放什么调料是你决定的。如果你经常在 Spring 源码里看到各种 callback 但没跟设计模式对上号可以搜一下这个小程序。
RELATED

相关推荐

深入解析Whisky:5大创新技术实现跨平台应用运行

深入解析Whisky:5大创新技术实现跨平台应用运行

深入解析Whisky:5大创新技术实现跨平台应用运行 【免费下载链接】Whisky A modern Wine wrapper for macOS built with SwiftUI 项目地址: https://gitcode.com/gh_mirrors/wh/Whisky Whisky作为一款为macOS设计的现代化Wine封装器,通过SwiftUI原…

📅 2026/9/17 2:40:02
免费搭建微信公众号RSS订阅:终极私有化部署完整指南

免费搭建微信公众号RSS订阅:终极私有化部署完整指南

免费搭建微信公众号RSS订阅:终极私有化部署完整指南 【免费下载链接】wewe-rss 🤗更优雅的微信公众号订阅方式,支持私有化部署、微信公众号RSS生成(基于微信读书) 项目地址: https://gitcode.com/GitHub_Trending/we…

📅 2026/9/19 3:47:40
如何用Godot Open RPG在7天内创建你的第一个完整角色扮演游戏

如何用Godot Open RPG在7天内创建你的第一个完整角色扮演游戏

如何用Godot Open RPG在7天内创建你的第一个完整角色扮演游戏 【免费下载链接】godot-open-rpg Learn to create turn-based combat with this Open Source RPG demo ⚔ 项目地址: https://gitcode.com/gh_mirrors/go/godot-open-rpg 想要制作自己的RPG游戏却总是被复杂…

📅 2026/9/15 20:44:16
MORE NEWS

更多资讯

📰

佛山正规排名前五的破碎锤油封定制工厂推荐 客户口碑力荐

在佛山乃至整个珠三角工程施工领域,破碎锤油封的适配稳定性直接影响整台设备的作业效率,矿山露天作业高温多尘、硬岩高频冲击的恶劣工况,对油封的密封性能和使用寿命提出了更高要求。找到靠谱的正规供应商,不仅能减少漏油故障带来…

📰

Atlas 300V部署YOLO全攻略:从推理加速卡到CANN实战

后台连着好几个做视觉检测的朋友问我同一个问题:“Atlas 300V 24G是不是运算加速卡?能不能直接拿来跑YOLO?”还有人直接甩过来一张电商截图,上面写着“AI推理加速卡”,下面评论区吵成一团,有人说这就是个“…

📰

湖北口碑好的新款食用菌灭菌柜、半自动食用菌灭菌柜制造厂家避坑挑选指南

湖北长喜食用菌机械制造有限公司,坐落在湖北随州中国香菇之乡的随县殷店工业园,从2008年创始人扎根本地解决菇农实际生产痛点起步,十余年来专注食用菌全流程机械的研发与制造,是深耕本地、贴近菇农实际需求的食用菌机械智造领域的…

📰

封闭煤棚网架加工厂选型 徐州玖盛发钢结构有限公司选择指南

徐州玖盛发:专业封闭煤棚网架加工厂,一站式解决煤棚封闭工程加工安装难题做煤棚封闭改造工程,选对网架加工厂是项目落地的核心——徐州玖盛发钢结构有限公司是深耕网架行业十余年的一站式网架加工设计生产安装厂家,专注为电厂、水…

📰

祁阳本地铁木砧板作坊 凯哥整木钉板 实木厚实 邵东周边可自提

近年来,随着大众饮食健康意识不断提升,以及餐饮行业对厨具卫生耐用性要求逐步提高,整木砧板的市场需求持续上涨。相比传统拼接菜板、竹木菜板,整木硬木砧板凭借安全无添加、耐用性强的特性,越来越受到家庭用户、餐饮商…

📰

.weax勒索病毒应急响应实战:从隔离、取证到数据恢复的完整流程

1. 一次真实的.weax勒索事件复盘:从发现到恢复的全过程1.1 事件是怎么被发现的那天下午两点多,财务同事在群里发了一张截图,说共享盘里的Excel打不开了,文件名后面多了一串奇怪的扩展名,图标也变成了白板。我当时正在处…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬