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

Function + 异常策略链:构建可组合的异常封装工具类

在这里插入图片描述

引言:异常到底该谁处理?

在日常 Java 业务开发中,我们常常写出这样的代码:

String result = someRemoteCall();
if (result == null) {throw new RuntimeException("接口返回为空");
}

看起来没问题,但一旦这种模式重复几十次,并在不同的团队成员手中以不同的形式出现(Optionalif-null-throw、try-catch 包装、日志输出混杂),你就会发现:异常的处理并不统一,也很难组合。

有没有办法像处理数据一样“组合”异常策略?答案是:有 —— 结合 Function<T, R> + 策略链思想,我们可以优雅地构建一套“异常装饰器”工具类。


核心思想:Function 的包装链 + 异常策略分离

我们想要实现的目标是:

  • 可以对任何 Function 进行统一的异常封装(比如异常转换、日志打点、返回默认值等)
  • 保持函数式接口的组合能力(支持 .andThen() / .compose()
  • 封装后的 Function 可被透明使用,不需要改业务代码

本质上是一种责任链模式 + 装饰器模式 + 函数式编程的融合。


工具类设计:FunctionChainWrapper

@FunctionalInterface
public interface CheckedFunction<T, R> {R apply(T t) throws Exception;
}

我们定义了一个能抛异常的函数接口,然后封装一个 FunctionWrapper 工具:

public class FunctionChainWrapper<T, R> {private final CheckedFunction<T, R> originalFunction;private final List<Function<Throwable, R>> exceptionHandlers = new ArrayList<>();private Function<Exception, RuntimeException> exceptionTranslator = RuntimeException::new;private Consumer<Throwable> exceptionLogger = e -> {}; // 默认无操作private R fallbackValue = null;private boolean hasFallback = false;public FunctionChainWrapper(CheckedFunction<T, R> originalFunction) {this.originalFunction = originalFunction;}public FunctionChainWrapper<T, R> onException(Function<Throwable, R> handler) {this.exceptionHandlers.add(handler);return this;}public FunctionChainWrapper<T, R> logException(Consumer<Throwable> logger) {this.exceptionLogger = logger;return this;}public FunctionChainWrapper<T, R> translateException(Function<Exception, RuntimeException> translator) {this.exceptionTranslator = translator;return this;}public FunctionChainWrapper<T, R> fallback(R value) {this.fallbackValue = value;this.hasFallback = true;return this;}public Function<T, R> build() {return input -> {try {return originalFunction.apply(input);} catch (Throwable ex) {exceptionLogger.accept(ex);for (Function<Throwable, R> handler : exceptionHandlers) {try {return handler.apply(ex);} catch (Throwable ignore) {}}if (hasFallback) return fallbackValue;if (ex instanceof RuntimeException) throw (RuntimeException) ex;throw exceptionTranslator.apply(new Exception(ex));}};}
}

使用示例:封装远程调用函数

FunctionChainWrapper<String, String> wrapper = new FunctionChainWrapper<>(input -> {if ("bad".equals(input)) throw new IOException("网络失败");return "Result: " + input;
});Function<String, String> safeFunc = wrapper.logException(e -> log.error("调用失败: {}", e.getMessage())).fallback("默认值").build();String val = safeFunc.apply("bad"); // 输出:默认值

可以继续组合使用:

Function<String, String> func = safeFunc.andThen(res -> res.toUpperCase()).andThen(res -> res + " ✅");String result = func.apply("bad"); // "默认值 ✅"

与 BiFunction 的扩展

只需简单改造:

@FunctionalInterface
public interface CheckedBiFunction<T, U, R> {R apply(T t, U u) throws Exception;
}public class BiFunctionChainWrapper<T, U, R> {// 与 FunctionChainWrapper 类似,只是换成 BiFunction
}

你就可以封装两个参数的函数,如数据库查询、组合业务等:

BiFunctionChainWrapper<String, Integer, String> wrapper = new BiFunctionChainWrapper<>((name, age) -> {if (age < 0) throw new IllegalArgumentException("非法年龄");return name + " is " + age + " years old.";}
);BiFunction<String, Integer, String> safeFunc = wrapper.translateException(e -> new BusinessException("年龄不合法", e)).fallback("默认用户").build();

单元测试建议(Function 单测思路)

  1. 输入正常、输出正常
  2. 输入异常(会触发 fallback / log)
  3. 异常链中的处理函数也抛异常(验证链的容错)
  4. 验证 log 是否触发(可 mock logger
  5. 组合测试(andThen)是否正常传递
@Test
void testFallbackTriggered() {FunctionChainWrapper<String, String> wrapper = new FunctionChainWrapper<>(s -> {throw new RuntimeException("fail");}).fallback("safe");Function<String, String> safeFunc = wrapper.build();Assertions.assertEquals("safe", safeFunc.apply("anything"));
}

避坑提示

  • 不要用 try-catch 把所有逻辑包死,应该聚焦于“异常感知点”来封装(I/O、反序列化、远程调用等)
  • 日志与 fallback 分离,不要耦合:打日志是一种副作用,fallback 是行为策略
  • 组合时注意状态性函数(如带缓存)之间的顺序

延伸思考:函数式异常封装的更大价值

  • 能让你的业务逻辑表达更加清晰(只关注 happy path)
  • 异常处理逻辑被拆分并复用(通用 fallback、打点策略可组合)
  • 可以在策略链中插入熔断器、缓存装饰器等 —— 用法无限扩展

总结

Function + 异常策略链 是一个十分实用且优雅的模式。它不仅解决了代码冗余和异常处理不一致的问题,还让我们重新认识了函数式接口的扩展能力。

推荐阅读文章

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 什么是 Cookie?简单介绍与使用方法

  • 什么是 Session?如何应用?

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • 如何理解应用 Java 多线程与并发编程?

  • 把握Java泛型的艺术:协变、逆变与不可变性一网打尽

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 如何理解线程安全这个概念?

  • 理解 Java 桥接方法

  • Spring 整合嵌入式 Tomcat 容器

  • Tomcat 如何加载 SpringMVC 组件

  • “在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”

  • “避免序列化灾难:掌握实现 Serializable 的真相!(二)”

  • 如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)

  • 解密 Redis:如何通过 IO 多路复用征服高并发挑战!

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • “打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”

  • Java 中消除 If-else 技巧总结

  • 线程池的核心参数配置(仅供参考)

  • 【人工智能】聊聊Transformer,深度学习的一股清流(13)

  • Java 枚举的几个常用技巧,你可以试着用用

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • 探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)

  • 为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)

http://www.xdnf.cn/news/17229.html

相关文章:

  • 智慧社区(十)——声明式日志记录与小区地图功能实现
  • Go通道操作全解析:从基础到高并发模式
  • 智能厨具机器人的革命性升级:Deepoc具身模型外拓板技术解析
  • 第六章第四节 PWM驱动LED呼吸灯 PWM驱动舵机 PWM驱动直流电机
  • Kotlin反射
  • 暴力解决MySQL连接失败
  • 从0配置yolo实例分割(ubuntu)
  • springBoot集成minio并实现文件的上传下载
  • OpenAI 开源模型 GPT-OSS MCP服务器深度解密:从工具集成到系统提示全自动化,浏览器+Python无缝协同的底层逻辑
  • 轻松实现浏览器自动化——AI浏览器自动化框架Stagehand
  • 【R语言】重新绘制高清MaxEnt的单因素响应曲线图像
  • 写Rust GPU内核驱动:GPU驱动工作原理简述
  • 告别Cursor!最强AI编程辅助Claude Code安装到使用全流程讲解
  • Beelzebub靶机
  • 第二十七天(数据结构:图)
  • Linux线程学习
  • Flutter 局部刷新方案对比:ValueListenableBuilder vs. GetBuilder vs. Obx
  • 力扣经典算法篇-46-阶乘后的零(正向步长遍历,逆向步长遍历)
  • 了解大型语言模型:力量与潜力
  • 什么是键值缓存?让 LLM 闪电般快速
  • 每日五个pyecharts可视化图表-bars(6)
  • 关于Android studio调试功能使用
  • 2025年主流开源音视频播放项目深度解析
  • MCU中的USB
  • 聚众识别场景误报率↓76%:陌讯动态密度估计算法实战解析
  • 【C语言】深入理解编译与链接过程
  • 前后端加密传数据实现方案
  • OpenCV入门:图像处理基础教程
  • [优选算法专题一双指针——两数之和](双指针和哈希表)
  • Qwen-Image开源模型实战