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 技术要点
4.2 适用场景
- ✅ 分布式锁管理
4.3 核心价值
| 价值维度 | 说明 |
|---|---|
| 代码简洁 | 业务方法只保留业务逻辑,非业务代码统一封装 |
| 高度复用 | 通用方法可被所有业务方法复用 |
| 易于维护 | 修改锁逻辑只需改一处,影响范围可控 |
| 职责清晰 | 业务逻辑与基础设施逻辑完全分离 |
| 测试友好 | 业务方法可独立测试,无需Mock锁 |
通过函数式编程和Lambda表达式,我们实现了真正的无侵入式设计,让代码更加优雅、简洁、易维护。
更多推荐




所有评论(0)