欢迎光临
我们一直在努力

你写 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 但没跟设计模式对上号,可以搜一下这个小程序。

赞(0)
未经允许不得转载:171主机测评 » 你写 JdbcTemplate 的 callback 写了三年——这就是模板方法,但你从没把它当设计模式
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址