news 2026/6/9 19:44:24

JAVA 17函数式编程 + Lambda表达式实现的无侵入式设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JAVA 17函数式编程 + Lambda表达式实现的无侵入式设计

一、传统侵入式设计的弊端

1.1 真实业务场景

在零售连锁系统中,门店对配货单进行收货时存在双重收货机制:

机制1:店员手动收货

  • 门店店员在系统中点击”确认收货”按钮
  • 系统根据实际收货数量更新配货单明细
  • 更新配货单状态

机制2:系统自动收货

  • 定时任务每天中午12点扫描所有未收货的配货单
  • 如果配货单的”预计送达日期”已过,系统自动触发收货流程
  • 按照发货数量自动生成收货记录

并发冲突场景: 假设某配货单的预计送达日期是”2025-12-16”,在12月16日中午:

  • 12:00:03 - 定时任务扫描到该配货单,开始执行自动收货
  • 12:00:04 - 门店店员恰好在系统中点击”确认收货”

不加锁的后果:

  • 配货单明细的收货数量被重复更新(实际收货100件,但记录显示200件)
  • 生成两条收货记录

解决方案:使用分布式锁,同一配货单同一时刻只能执行一次收货操作(无论人工还是自动)。

1.2 传统写法示例

在传统的分布式锁实现中,我们通常会将锁逻辑直接写在业务方法内部:

@Slf4j @Service public class DeliveryReceiveService { @Autowired private RedissonClient redissonClient; @Autowired private DeliveryDocsMapper deliveryDocsMapper; @Autowired private DeliveryDocsItemMapper deliveryDocsItemMapper; /** * 店员手动收货(入口1) */ public void confirmReceive(String docsNo, List<ReceiveItem> items) { // 调用统一的收货逻辑 processReceive(docsNo, items); } /** * 系统自动收货(入口2) */ public void autoReceive(String docsNo) { // 查询配货单明细 List<DeliveryDocsItem> itemList = deliveryDocsItemMapper.selectByDocsNo(docsNo); // 按发货数量生成收货明细 List<ReceiveItem> items = itemList.stream() .map(item -> new ReceiveItem(item.getMaterialId(), item.getShippedNum())) .collect(Collectors.toList()); // 调用统一的收货逻辑 processReceive(docsNo, items); } /** * 统一的收货处理逻辑(传统侵入式写法) * 核心业务方法,所有收货操作最终都调用这里 */ private void processReceive(String docsNo, List<ReceiveItem> items) { // 拼接锁的key:按配货单号加锁 String lockKey = "delivery:receive:" + docsNo; RLock rLock = redissonClient.getLock(lockKey); try { // 尝试获取锁,获取不到直接失败(不等待) boolean locked = rLock.tryLock(0, TimeUnit.MILLISECONDS); if (!locked) { throw new RuntimeException("配货单正在收货中,请稍后再试"); } // ========== 真正的业务逻辑开始 ========== // 1. 查询配货单 DeliveryDocs docs = deliveryDocsMapper.selectByDocsNo(docsNo); if (docs == null) { throw new RuntimeException("配货单不存在"); } // 2. 校验配货单状态 if ("RECEIVED".equals(docs.getStatus())) { throw new RuntimeException("配货单已收货,请勿重复操作"); } // 3. 更新明细的收货数量 for (ReceiveItem item : items) { deliveryDocsItemMapper.updateReceiveNum( docsNo, item.getMaterialId(), item.getReceiveNum() ); } // 4. 更新配货单状态 docs.setStatus("RECEIVED"); docs.setReceiveTime(new Date()); deliveryDocsMapper.updateById(docs); // ========== 真正的业务逻辑结束 ========== } catch (RuntimeException re) { throw re; } catch (Exception e) { log.error("收货失败, docsNo:{}", docsNo, e); throw new RuntimeException("收货失败,请稍后重试"); } finally { // 释放锁 if (rLock.isHeldByCurrentThread()) { rLock.unlock(); } } } }

1.3 传统写法的三大弊端

弊端1:代码重复

每个需要加锁的方法都要重复写20+行锁相关代码(获取锁、try-catch、finally释放锁),违反DRY原则。

弊端2:业务逻辑被淹没

真正的业务代码被大量锁逻辑包裹,锁代码占比高达40-50%,可读性极差。

弊端3:维护成本高

  • 如果要修改锁的超时时间,需要改动所有方法(10个方法 = 10处修改)
  • 如果要添加锁获取失败的监控日志,需要改动所有方法
  • 新增需要加锁的方法时,必须复制粘贴20+行代码
  • 极易出现复制粘贴导致的bug(忘记修改lockKey、忘记释放锁等)

二、无侵入式设计原理

2.1 核心思想

将业务逻辑作为参数传入通用方法,通用方法负责锁的管理

2.2 实现机制

步骤1:封装通用工具类

为了让所有业务类都能复用,我们将锁逻辑封装为独立的工具类:

@Slf4j @Component @RequiredArgsConstructor public class DistributedLockUtil { private final RedissonClient redissonClient; /** * 执行带分布式锁的业务逻辑(不等待) * * @param lockKey 锁的key * @param task 要执行的业务逻辑 */ public void executeWithLock(String lockKey, Runnable task) { executeWithLock(lockKey, 0, TimeUnit.MILLISECONDS, task); } /** * 执行带分布式锁的业务逻辑(可设置等待时间) * * @param lockKey 锁的key * @param waitTime 等待锁的时间 * @param timeUnit 时间单位 * @param task 要执行的业务逻辑 */ public void executeWithLock(String lockKey, long waitTime, TimeUnit timeUnit, Runnable task) { RLock rLock = redissonClient.getLock(lockKey); try { boolean locked = rLock.tryLock(waitTime, timeUnit); if (locked) { task.run(); } else { throw new BusinessException("正在处理中,请稍后再试"); } } catch (BusinessException be) { throw be; } catch (Exception e) { log.error("执行失败, lockKey:{}", lockKey, e); throw new BusinessException("操作失败,请稍后重试"); } finally { if (rLock.isHeldByCurrentThread()) { rLock.unlock(); } } } }

步骤2:业务类注入工具类

@Service @RequiredArgsConstructor public class DeliveryReceiveService { private final DistributedLockUtil distributedLockUtil; // 注入工具类 private final DeliveryDocsMapper deliveryDocsMapper; /** * 店员手动收货 */ public void confirmReceive(String docsNo, List<ReceiveItem> items) { String lockKey = "delivery:receive:" + docsNo; // 直接调用工具类方法 distributedLockUtil.executeWithLock(lockKey, () -> processReceive(docsNo, items)); } }

好处

  • 全局复用:所有Service类都可以注入DistributedLockUtil使用
  • 统一维护:修改锁逻辑只需改一个类
  • 更加清晰:业务类不再有锁相关的代码
  • 灵活配置:支持自定义等待时间,适应不同业务场景

步骤3:业务方法保持纯净

/** * 统一的收货处理逻辑(纯业务逻辑,无锁代码) */ private void processReceive(String docsNo, List<ReceiveItem> items) { // 1. 查询配货单 DeliveryDocs docs = deliveryDocsMapper.selectByDocsNo(docsNo); if (docs == null) { throw new RuntimeException("配货单不存在"); } // 2. 校验配货单状态 if ("RECEIVED".equals(docs.getStatus())) { throw new RuntimeException("配货单已收货,请勿重复操作"); } // 3. 更新明细的收货数量 for (ReceiveItem item : items) { deliveryDocsItemMapper.updateReceiveNum( docsNo, item.getMaterialId(), item.getReceiveNum() ); } // 4. 更新配货单状态 docs.setStatus("RECEIVED"); docs.setReceiveTime(new Date()); deliveryDocsMapper.updateById(docs); }

步骤4:调用时使用Lambda表达式

/** * 店员手动收货(对外接口) */ public void confirmReceive(String docsNo, List<ReceiveItem> items) { String lockKey = "delivery:receive:" + docsNo; // 使用Lambda表达式将业务逻辑封装为Runnable传入 distributedLockUtil.executeWithLock(lockKey, () -> processReceive(docsNo, items)); }

2.3 Lambda表达式的魔法

Lambda表达式本质

() -> processReceive(docsNo, items)

等价于创建一个匿名类实例:

new Runnable() { @Override public void run() { processReceive(docsNo, items); } }

执行流程图


三、无侵入式设计的优势

对比维度传统侵入式无侵入式提升
锁逻辑复用性❌ 每个方法重复✅ 统一封装100%复用
可维护性❌ 修改影响所有方法✅ 只修改通用方法维护点从N个降为1个
可测试性❌ 难以单独测试业务逻辑✅ 业务方法独立可测测试复杂度降低

四、总结

4.1 技术要点

  1. 函数式接口:Runnable(无返回值)
  2. Lambda表达式() -> method()简化匿名类创建
  3. 高阶函数:将函数作为参数传递

4.2 适用场景

  • ✅ 分布式锁管理

4.3 核心价值

价值维度说明
代码简洁业务方法只保留业务逻辑,非业务代码统一封装
高度复用通用方法可被所有业务方法复用
易于维护修改锁逻辑只需改一处,影响范围可控
职责清晰业务逻辑与基础设施逻辑完全分离
测试友好业务方法可独立测试,无需Mock锁

通过函数式编程和Lambda表达式,我们实现了真正的无侵入式设计,让代码更加优雅、简洁、易维护。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 18:34:02

ASTM D4169测试序列设计:贴合医疗器械运输实际

一、测试序列设计的核心逻辑ASTM D4169-23E1-DC的测试项目顺序并非随机排列&#xff0c;而是深度还原医疗器械从出厂到交付用户的全流程运输场景。运输过程中&#xff0c;包装会依次经历人工/机械装卸、堆码、长途运输振动、特殊环境影响等风险&#xff0c;测试序列正是按“先基…

作者头像 李华
网站建设 2026/6/4 19:07:57

DBShadow.net之性能优化的坎坷路

一、mysql参数的成本使用BenchmarkDotNet测试1. 测试代码如下CreateParameter直接构造参数Clone预先构造参数名和类型,复制后只设置参数值/* by 01130.hk - online tools website : 01130.hk/zh/html2all.html */ private static readonly MySqlCommand _command new(); priva…

作者头像 李华
网站建设 2026/6/5 4:48:20

AI 开发稳了!Timeshift 每日 + 每周双备份,筑牢环境安全线

备份类型保留数量核心作用适配场景 / 优势每日备份5 个聚焦短期故障恢复&#xff0c;提供细粒度回滚能力应对当日依赖包安装错误、配置失误等突发问题&#xff0c;快速还原至近 5 天内的最近工作环境&#xff0c;适配日常高频操作场景每周备份4 个覆盖长期隐患&#xff0c;作为…

作者头像 李华
网站建设 2026/6/5 9:58:04

收藏!字节实习生日薪暴涨150%,普通人抓住AI大模型风口更靠谱

近期打工人的朋友圈&#xff0c;被字节跳动实习生全面涨薪的消息彻底刷屏了&#xff01;不管是学生党还是职场人&#xff0c;都被这波薪资福利狠狠吸引&#xff0c;尤其是咱们程序员和想入行技术领域的小白&#xff0c;更该从这波热度里看到行业关键信号。 据知情人士透露&…

作者头像 李华
网站建设 2026/6/5 10:08:37

领导最反感的5件事,千万别做

德鲁克曾说过&#xff1a;“管理的本质&#xff0c;不在于知&#xff0c;而在于行。”但在现实职场中&#xff0c;很多人的“行”往往跑偏了方向&#xff0c;不是能力不够&#xff0c;而是不懂得权力真谛和人性的复杂。K哥在职场20多年&#xff0c;从被别人管到管理几百人的团队…

作者头像 李华
网站建设 2026/6/5 9:00:37

必收藏!从行业兴衰看风口:AI大模型岗位带你弯道超车

刷到一位网友的真实分享&#xff1a;身边好友入手了一套单价9万的房产&#xff0c;如今房价直接下跌30%&#xff0c;每月还要背负3.5万的房贷&#xff0c;算下来每天一睁眼&#xff0c;就相当于凭空亏损1000元&#xff0c;压力大到喘不过气。 评论区里满是唏嘘与共鸣&#xff0…

作者头像 李华