当前位置: 首页 > news >正文

你写 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 scope,`SpringApplication` 的嵌入式 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 但没跟设计模式对上号,可以搜一下这个小程序。
http://www.jsqmd.com/news/1037207/

相关文章:

  • 自定义AI代理开发模板
  • 从创意到原型:如何用Pencil Project高效设计用户界面
  • 指纹浏览器 vs 云手机:核心区别、优缺点及场景选择指南
  • 海口旧金金条回收攻略,持证实体安全交易 - 开心测评
  • 专业AI音频分离工具Ultimate Vocal Remover:高效提取人声与伴奏的完整实践指南
  • 衡水内外墙涂料生产厂家科普|衡水袁氏新型建材有限公司(梦仕利)选材测评 - 百航
  • 嵌入式开发链接器原理与MCUez Linker实战配置指南
  • 推开窗是汤逊湖,走出去是光谷:湖北民办大学中的‘宝藏选手’与实力梯队 - 商业观察
  • 2026成都本地名表回收保值梯队划分,你的表属于第几档? - 逸程
  • ERPNext开源ERP终极指南:中小企业数字化转型的免费解决方案
  • Django毕业设计-基于 Django+Vue 的智慧农业管理系统的设计与实现 基于 Django+Vue 的现代化农业管理平台的设计与实现(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • 26执业兽医考试最后阶段,用什么题库刷高频题和真题? - 优学考证上岸
  • Android GIF圆角特效:3分钟让你的动画更优雅
  • Path of Building PoE2:流放之路2角色构建的终极规划工具
  • 同城黄金回收口碑排行第一名,实时金价结算不扣损耗秒速回款 - 奢品小当家
  • 终极Windows激活解决方案:CMWTAT_Digital_Edition让Win10/11数字许可证获取如此简单
  • 2026宁波黄金回收权威龙头|TOP高阶变现实测指南 - 奢侈品回收测评
  • 深度解析:WordLlama的架构设计与高性能Token嵌入技术实现
  • 2026年 沈阳304不锈钢板正规供货商口碑榜单:专业实力与稳定品质的优选推荐 - 品牌发掘
  • 2026年半导体设备展与材料展怎么选?五家展会制造端资源密度实地考察报告 - 深度智识库
  • 2026龙川二手奢侈品回收指南:龙川源奢汇领衔,5家正规机构实测对比 - 行走在冷风中。
  • Drupal核心SQL注入漏洞CVE-2026-9082深度剖析与防御实战
  • Maupassant Hugo主题国际化支持:多语言博客搭建完整教程
  • 10分钟永久保存微信聊天记录:留痕工具完全指南
  • 黄金变现必看!上海本地人都去的黄金回收门店-收的顶行业标杆持证鉴定 - 奢侈品回收评测
  • GitHub Desktop汉化终极指南:3分钟打造中文版Git客户端
  • 2026年临汾全屋整装与毛坯房装修公司排行:5大品牌深度横评 - 精选优质企业推荐官
  • 3步打造你的专属AI助理:Hermes Agent智能助手全攻略
  • MuleSoft企业级AI编排实战:打通数据孤岛与大模型的中枢架构
  • 天津钻石回收门店排行榜|禹竞名奢汇稳居榜首,本地变现首选靠谱商家 - 名奢变现站