函数式接口在 Spring Boot 中使用(模板方法)
·
目的
了解java8 函数式接口,了解其在企业中的实践
一、企业级 Service 层的痛点
在企业级 Spring Boot 项目中,Service 层往往承担了过多非业务职责:
- 参数校验
- 权限控制
- 日志记录(这边用的)
- 异常包装
- 耗时统计 (这边用的)
- 重试与容错(这边用的)
- 核心业务执行
典型代码如下:
public void doBusiness() {
long start = System.currentTimeMillis();
try {
checkParam();
checkPermission();
doBusinessLogic();
} catch (Exception e) {
log.error("error", e);
throw e;
} finally {
log.info("cost {} ms", System.currentTimeMillis() - start);
}
}
你会发现,真正的业务逻辑可能只有一行,却被大量重复模板逻辑包裹。
这并不是开发能力的问题,而是Service 层缺少统一执行模型的必然结果。
二、Java 8 的革命性能力:行为参数化
Java 8 之前:
- 方法只能接收「数据」
Java 8 之后:
- 方法可以接收「行为」
Runnable action = () -> doBusinessLogic();
Supplier<Order> supplier = () -> queryOrder();
这意味着:
- 可变部分 = 业务逻辑
- 稳定部分 = 执行流程
这正是模板方法模式的理想落地形式。
三、函数式接口在企业项目中的角色
在企业 Service/Domain 层,高频使用的函数式接口非常有限:
| 接口 | 含义 |
|---|---|
Runnable |
无返回值执行 |
Supplier<T> |
返回结果执行 |
Consumer<T> |
带参数执行 |
Function<T,R> |
转换型执行 |
Predicate<T> |
条件判断 |
它们的本质非常简单:
() -> void
() -> T
(T) -> void
(T) -> R
(T) -> boolean
简单 = 企业级可复用设计的前提
四、函数式接口 = 轻量级策略模式
传统策略模式需要:
- 接口 + 多实现类
- Spring 注入
- 包结构膨胀
对于一次性或少量使用的策略,成本过高。
而函数式接口可以直接用 Lambda 即写即用:
execute("确认订单", () -> orderService.confirm(orderId));
- 无额外类
- 无配置成本
- 即写即用
本质上是轻量级策略模式。
五、模板方法核心思想
模板方法的核心:
固定执行流程 + 可插入业务钩子
最简单的形式:
public final class ExecTemplate {
public static void run(String name, Runnable action) {
long start = System.currentTimeMillis();
try {
before(name);
action.run();
after(name);
} catch (Exception e) {
onError(name, e);
throw e;
} finally {
finallyDo(name, System.currentTimeMillis() - start);
}
}
private static void before(String name) { log.info("开始执行:{}", name); }
private static void after(String name) { log.info("执行成功:{}", name); }
private static void onError(String name, Exception e) { log.error("执行失败:{}", name, e); }
private static void finallyDo(String name, long cost) { log.info("{} 耗时 {} ms", name, cost); }
}
使用方式:
ExecTemplate.run("确认订单", () -> orderService.confirm(orderId));
Service 层完全不再关注重复逻辑,只关心业务“做什么”。
六、企业级“大而全”模板方法示例(Spring Retry 集成)
启用 Spring Retry:
@EnableRetry
@Configuration
public class RetryConfig {}
模板方法:
@Slf4j
@Component
public class ExecTemplate {
@Retryable(retryFor = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 500))
public <T> T execute(String name, Supplier<T> supplier) {
long start = System.currentTimeMillis();
before(name);
try {
T result = supplier.get();
after(name);
return result;
} catch (Exception e) {
onError(name, e);
throw e;
} finally {
finallyDo(name, System.currentTimeMillis() - start);
}
}
@Retryable(retryFor = Exception.class, maxAttempts = 3)
public void run(String name, Runnable action) {
execute(name, () -> { action.run(); return null; });
}
protected void before(String name) { log.info("▶ 开始执行:{}", name); }
protected void after(String name) { log.info("✔ 执行成功:{}", name); }
protected void onError(String name, Exception e) { log.error("✘ 执行失败:{}", name, e); }
protected void finallyDo(String name, long cost) { log.info("⏱ {} 耗时 {} ms", name, cost); }
@Recover
public <T> T recover(Exception e, String name, Supplier<T> supplier) {
log.error("‼ {} 重试失败,进入兜底处理", name, e);
throw e;
}
}
七、企业项目中模板方法的典型应用场景
1️⃣ 统一 Service 执行模板
- 统一日志、异常、耗时统计
- Service 方法只关心“做什么”
execTemplate.execute("创建订单", () -> orderService.create(cmd));
2️⃣ 远程调用保护模板
- RPC/HTTP/MQ 异常统一处理
- 内置重试、熔断、降级
OrderDTO dto = execTemplate.execute("调用订单中心", () -> orderClient.query(orderId));
3️⃣ 缓存加载模板(Cache-Aside)
- 避免重复代码
- 自动处理缓存穿透、空值缓存
Order order = cacheTemplate.getOrLoad("order:" + id, () -> orderRepository.findById(id));
4️⃣ 幂等执行模板
- 防止重复点击、MQ 重复消费
- 幂等标识 + 并发控制
idempotentTemplate.execute("pay:" + orderId, () -> paymentService.pay(orderId));
5️⃣ 状态流转校验模板
- 状态机前置校验
- 提高业务执行安全性
stateTemplate.execute(order.getStatus(), s -> s == CREATED, () -> order.pay());
6️⃣ 事务包裹模板
- 多步操作统一事务管理
- 避免每个 Service 写
@Transactional
transactionTemplate.execute(() -> {
orderRepo.save(order);
inventoryRepo.lock(order);
return order;
});
7️⃣ 数据权限过滤模板
- 查询自动带用户权限
- 避免重复拼接 SQL 或 QueryWrapper
List<Order> orders = permissionTemplate.query(() -> orderRepo.findAll());
8️⃣ 规则执行模板
- 统一规则执行框架
- 保证执行流程稳定
rulesTemplate.executeRules(rules, context -> apply(context));
八、为什么模板方法优于 AOP
| 维度 | AOP | 模板方法 |
|---|---|---|
| 可读性 | 隐式 | 显式 |
| 调试 | 困难 | 直观 |
| 粒度 | 方法级 | 代码块级 |
| 业务编排 | 弱 | 强 |
| 重试/降级 | 复杂 | 自然 |
AOP 适合横切关注点
模板方法适合业务执行流程治理
九、设计收益总结
- ✔ Service 层代码高度聚焦业务
- ✔ 执行流程统一可治理
- ✔ 重试、日志、监控集中管理
- ✔ 极低学习成本
- ✔ 易扩展,多业务场景复用
十、一句话总结
函数式接口不是 Stream 的附属品,它是企业级 Service 层模板方法模式的最佳实现载体
更多推荐




所有评论(0)