欢迎你来读这篇博客,这篇博客主要是关于命令模式。 其中包括命令模式的核心思想、适用场景、优缺点、命令队列、撤销操作、异步执行、与策略模式/责任链模式/事件模式的区别,以及 Java 后端开发中订单操作命令总线的完整案例。
序言 在 Java 后端开发中,我们经常会遇到这样的问题:
一个调用者想触发某个操作,但它不应该直接知道这个操作具体由谁执行、怎么执行、什么时候执行。
比如后台管理系统中有很多订单操作:
创建订单;
取消订单;
支付订单;
发货订单;
关闭订单;
退款订单;
审核订单;
批量导出订单。
最直接的写法是:
1 2 3 4 5 orderService.createOrder(command); orderService.cancelOrder(orderId); orderService.payOrder(orderId); orderService.shipOrder(orderId); orderService.refundOrder(orderId);
这当然可以。
但如果后面出现这些需求:
操作要排队执行;
操作要异步执行;
操作要记录操作日志;
操作失败后要重试;
操作需要支持撤销;
操作要统一鉴权;
操作要统一落库审计;
操作要支持批量组合;
操作要可以延迟执行;
操作要从前端配置或 MQ 消息中动态触发。
这时如果调用者直接依赖具体 Service 方法,就会越来越难扩展。
命令模式就是为了解决这类问题:
把一个请求封装成一个对象,从而让请求可以被参数化、排队、记录、撤销、重试、异步执行。
简单说:
命令模式就是把“我要做什么”封装成一个对象。
这个对象本身就是一条命令。
调用者不需要知道命令怎么执行,只需要把命令交给执行器。
这就像你在餐厅点菜,不需要冲进后厨告诉厨师“先热锅、再下油、再放菜”,你只需要下单。菜单就是命令,服务员是调用者,厨师是接收者,后厨系统是命令执行器。
当然,如果你点的是“随便”,那不是命令模式,那是产品经理需求不明确。
正文 chapter 1:什么是命令模式 命令模式,英文是 Command Pattern ,属于行为型设计模式。
它的定义是:
将一个请求封装为一个对象,从而使你可以用不同的请求对客户端进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
这句话很长,但核心只有一个:
把请求封装成对象。
在命令模式中,一个操作不再只是一个方法调用,而是一个对象。
例如:
1 2 3 Command command = new CancelOrderCommand (orderId); command.execute();
这个 CancelOrderCommand 对象就代表“取消订单”这个请求。
它可以被:
立即执行;
放进队列;
保存到数据库;
发送到 MQ;
延迟执行;
批量执行;
失败重试;
记录日志;
执行撤销。
一旦请求变成对象,它就可以像普通对象一样被传递、组合、存储和调度。
这就是命令模式最重要的价值。
chapter 2:命令模式的核心角色 命令模式一般包含五个角色:
Command 抽象命令 :定义命令执行接口。
ConcreteCommand 具体命令 :封装具体请求,调用接收者完成操作。
Receiver 接收者 :真正执行业务逻辑的对象。
Invoker 调用者 :负责触发命令执行。
Client 客户端 :创建命令对象,并设置接收者。
结构如下:
1 2 3 4 5 6 7 8 9 10 Client │ ▼ Invoker │ ▼ Command ▲ │ ConcreteCommand ─────► Receiver
用订单系统来理解:
命令模式角色
订单系统示例
Command
OrderCommand
ConcreteCommand
CreateOrderCommand、CancelOrderCommand
Receiver
OrderService、PaymentService、InventoryService
Invoker
CommandInvoker、CommandBus
Client
Controller、MQ Listener、定时任务
命令对象并不一定自己完成所有业务逻辑。
它通常会调用 Receiver。
例如:
1 2 3 4 5 6 7 8 9 10 11 public class CancelOrderCommand implements OrderCommand { private final OrderService orderService; private final Long orderId; @Override public void execute () { orderService.cancelOrder(orderId); } }
真正取消订单的是 OrderService。
命令对象负责封装这次请求。
chapter 3:为什么需要命令模式 命令模式解决的核心问题是:
调用者和执行者之间耦合太强。
如果 Controller 直接调用具体 Service:
1 orderService.cancelOrder(orderId);
Controller 就必须知道:
调哪个 Service;
调哪个方法;
传哪些参数;
什么时候执行;
失败如何处理;
是否要重试;
是否要记录操作日志。
如果操作越来越多,调用方就会越来越复杂。
命令模式把这些操作封装成对象:
1 2 3 OrderCommand command = new CancelOrderCommand (orderService, orderId); commandBus.dispatch(command);
调用方只负责创建命令和提交命令。
执行细节交给命令对象和命令总线。
这样可以带来几个好处:
调用者和执行者解耦;
操作可以被统一调度;
操作可以排队;
操作可以异步;
操作可以记录日志;
操作可以撤销;
操作可以组合;
操作可以延迟执行。
chapter 4:命令模式适合哪些问题 命令模式适合的不是普通方法调用,而是“请求需要被对象化”的场景。
例如:
1. 请求需要排队 例如:
1 2 3 4 订单导出任务 批量短信发送任务 报表生成任务 大文件处理任务
命令对象可以放进队列,后续由执行器慢慢消费。
2. 请求需要异步执行 例如:
1 commandExecutor.submit(command);
命令对象可以提交给线程池、MQ、任务调度器。
3. 请求需要记录日志 命令对象可以包含操作人、操作时间、操作参数。
适合审计日志。
4. 请求需要撤销 如果命令对象支持 undo(),就可以实现撤销操作。
例如编辑器撤销、订单操作回滚、配置变更回滚。
5. 请求需要统一增强 例如统一鉴权、幂等、重试、限流、监控。
命令总线可以在执行命令前后统一处理。
6. 请求需要组合 多个命令可以组成一个宏命令。
例如:
1 创建订单 -> 锁定库存 -> 创建支付单 -> 发送通知
chapter 5:最简单的命令模式示例 先看一个遥控器控制电灯的经典例子。
电灯是接收者:
1 2 3 4 5 6 7 8 9 10 public class Light { public void on () { System.out.println("打开电灯" ); } public void off () { System.out.println("关闭电灯" ); } }
命令接口:
1 2 3 4 public interface Command { void execute () ; }
开灯命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class LightOnCommand implements Command { private final Light light; public LightOnCommand (Light light) { this .light = light; } @Override public void execute () { light.on(); } }
关灯命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class LightOffCommand implements Command { private final Light light; public LightOffCommand (Light light) { this .light = light; } @Override public void execute () { light.off(); } }
遥控器是调用者:
1 2 3 4 5 6 7 8 9 10 11 12 public class RemoteController { private Command command; public void setCommand (Command command) { this .command = command; } public void pressButton () { command.execute(); } }
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class CommandDemo { public static void main (String[] args) { Light light = new Light (); Command lightOnCommand = new LightOnCommand (light); RemoteController controller = new RemoteController (); controller.setCommand(lightOnCommand); controller.pressButton(); } }
这个例子虽然简单,但它体现了命令模式的核心:
遥控器不直接依赖电灯,而是依赖命令接口。
遥控器只知道执行命令。
至于命令背后是开灯、关灯、开空调,遥控器不关心。
chapter 6:案例背景:订单操作命令总线 下面用 Java 后端更真实的案例来讲命令模式。
假设我们正在开发一个订单系统。
订单有多种操作:
创建订单;
取消订单;
支付订单;
发货订单;
关闭订单。
我们希望实现:
每个操作封装成一个命令对象;
调用方只提交命令,不直接调用具体 Service;
命令执行前后可以统一打印日志;
命令可以放入队列;
命令可以支持撤销;
Spring Boot 中可以根据命令类型路由到对应处理器。
这个案例会分几步实现:
普通命令模式;
支持撤销;
支持命令队列;
Spring Boot 命令总线。
chapter 7:定义订单模型 Order 先定义一个简单订单对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Order { private final Long orderId; private String status; public Order (Long orderId, String status) { this .orderId = orderId; this .status = status; } public Long getOrderId () { return orderId; } public String getStatus () { return status; } public void changeStatus (String status) { this .status = status; } }
这里为了演示,只保留订单 ID 和订单状态。
真实项目中,订单会复杂得多。
chapter 8:定义接收者 OrderService 接收者是真正执行业务逻辑的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.atomic.AtomicLong;public class OrderService { private final AtomicLong idGenerator = new AtomicLong (10000 ); private final Map<Long, Order> orderStore = new ConcurrentHashMap <>(); public Long createOrder (Long userId, Long productId, Integer quantity) { Long orderId = idGenerator.incrementAndGet(); Order order = new Order (orderId, "CREATED" ); orderStore.put(orderId, order); System.out.println("创建订单,orderId = " + orderId + ",userId = " + userId + ",productId = " + productId + ",quantity = " + quantity); return orderId; } public void cancelOrder (Long orderId) { Order order = getOrder(orderId); order.changeStatus("CANCELLED" ); System.out.println("取消订单,orderId = " + orderId); } public void payOrder (Long orderId) { Order order = getOrder(orderId); order.changeStatus("PAID" ); System.out.println("支付订单,orderId = " + orderId); } public void shipOrder (Long orderId) { Order order = getOrder(orderId); order.changeStatus("SHIPPED" ); System.out.println("发货订单,orderId = " + orderId); } public void changeStatus (Long orderId, String status) { Order order = getOrder(orderId); order.changeStatus(status); System.out.println("修改订单状态,orderId = " + orderId + ",status = " + status); } public Order getOrder (Long orderId) { Order order = orderStore.get(orderId); if (order == null ) { throw new IllegalArgumentException ("order not found, orderId = " + orderId); } return order; } }
OrderService 是命令模式中的 Receiver。
它不关心命令对象,也不关心谁调用它。
它只负责订单业务能力。
chapter 9:定义命令接口 OrderCommand 1 2 3 4 5 6 public interface OrderCommand { String name () ; void execute () ; }
每个命令都需要有:
命令名称可以用于日志、监控、审计。
chapter 10:定义创建订单命令 创建订单命令封装了创建订单所需的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class CreateOrderCommand implements OrderCommand { private final OrderService orderService; private final Long userId; private final Long productId; private final Integer quantity; private Long createdOrderId; public CreateOrderCommand (OrderService orderService, Long userId, Long productId, Integer quantity) { this .orderService = orderService; this .userId = userId; this .productId = productId; this .quantity = quantity; } @Override public String name () { return "CreateOrderCommand" ; } @Override public void execute () { this .createdOrderId = orderService.createOrder(userId, productId, quantity); } public Long getCreatedOrderId () { return createdOrderId; } }
这个命令对象中保存了:
接收者 OrderService;
用户 ID;
商品 ID;
数量;
创建后的订单 ID。
调用者可以通过命令对象拿到执行结果。
chapter 11:定义取消订单命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class CancelOrderCommand implements OrderCommand { private final OrderService orderService; private final Long orderId; public CancelOrderCommand (OrderService orderService, Long orderId) { this .orderService = orderService; this .orderId = orderId; } @Override public String name () { return "CancelOrderCommand" ; } @Override public void execute () { orderService.cancelOrder(orderId); } }
取消订单命令只关心取消订单所需信息。
chapter 12:定义支付订单命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class PayOrderCommand implements OrderCommand { private final OrderService orderService; private final Long orderId; public PayOrderCommand (OrderService orderService, Long orderId) { this .orderService = orderService; this .orderId = orderId; } @Override public String name () { return "PayOrderCommand" ; } @Override public void execute () { orderService.payOrder(orderId); } }
chapter 13:定义发货订单命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ShipOrderCommand implements OrderCommand { private final OrderService orderService; private final Long orderId; public ShipOrderCommand (OrderService orderService, Long orderId) { this .orderService = orderService; this .orderId = orderId; } @Override public String name () { return "ShipOrderCommand" ; } @Override public void execute () { orderService.shipOrder(orderId); } }
chapter 14:定义调用者 CommandInvoker 调用者负责触发命令执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class CommandInvoker { public void invoke (OrderCommand command) { if (command == null ) { throw new IllegalArgumentException ("command can not be null" ); } long start = System.currentTimeMillis(); System.out.println("[Invoker] 开始执行命令:" + command.name()); try { command.execute(); System.out.println("[Invoker] 命令执行成功:" + command.name()); } finally { long cost = System.currentTimeMillis() - start; System.out.println("[Invoker] 命令耗时:" + cost + " ms" ); } } }
这里就可以统一加:
日志;
耗时统计;
异常处理;
监控埋点;
审计记录。
命令对象只关心业务请求。
调用者负责命令执行过程管理。
chapter 15:客户端使用命令模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class CommandPatternDemo { public static void main (String[] args) { OrderService orderService = new OrderService (); CommandInvoker invoker = new CommandInvoker (); CreateOrderCommand createCommand = new CreateOrderCommand ( orderService, 1L , 100L , 2 ); invoker.invoke(createCommand); Long orderId = createCommand.getCreatedOrderId(); invoker.invoke(new PayOrderCommand (orderService, orderId)); invoker.invoke(new ShipOrderCommand (orderService, orderId)); } }
输出类似:
1 2 3 4 5 6 7 8 9 10 11 12 [Invoker] 开始执行命令:CreateOrderCommand 创建订单,orderId = 10001,userId = 1,productId = 100,quantity = 2 [Invoker] 命令执行成功:CreateOrderCommand [Invoker] 命令耗时:2 ms [Invoker] 开始执行命令:PayOrderCommand 支付订单,orderId = 10001 [Invoker] 命令执行成功:PayOrderCommand [Invoker] 命令耗时:1 ms [Invoker] 开始执行命令:ShipOrderCommand 发货订单,orderId = 10001 [Invoker] 命令执行成功:ShipOrderCommand [Invoker] 命令耗时:1 ms
可以看到,客户端没有直接调用:
1 orderService.payOrder(orderId);
而是创建命令并交给 Invoker 执行。
这就是命令模式的基本形式。
chapter 16:支持撤销的命令 命令模式有一个很经典的能力:撤销。
要支持撤销,可以定义一个新的接口。
1 2 3 4 public interface UndoableCommand extends OrderCommand { void undo () ; }
撤销的关键是:
命令在执行前要保存足够的旧状态,撤销时才能恢复。
比如取消订单命令。
取消前订单可能是 CREATED。
执行取消后变成 CANCELLED。
撤销时要恢复成原来的状态。
chapter 17:可撤销取消订单命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class UndoableCancelOrderCommand implements UndoableCommand { private final OrderService orderService; private final Long orderId; private String oldStatus; public UndoableCancelOrderCommand (OrderService orderService, Long orderId) { this .orderService = orderService; this .orderId = orderId; } @Override public String name () { return "UndoableCancelOrderCommand" ; } @Override public void execute () { Order order = orderService.getOrder(orderId); this .oldStatus = order.getStatus(); orderService.cancelOrder(orderId); } @Override public void undo () { if (oldStatus == null ) { throw new IllegalStateException ("command has not been executed" ); } orderService.changeStatus(orderId, oldStatus); System.out.println("撤销取消订单,orderId = " + orderId + ",恢复状态:" + oldStatus); } }
这个命令在执行前保存旧状态:
1 this .oldStatus = order.getStatus();
撤销时恢复:
1 orderService.changeStatus(orderId, oldStatus);
chapter 18:支持撤销的 Invoker 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import java.util.ArrayDeque;import java.util.Deque;public class UndoableCommandInvoker { private final Deque<UndoableCommand> history = new ArrayDeque <>(); public void invoke (UndoableCommand command) { if (command == null ) { throw new IllegalArgumentException ("command can not be null" ); } System.out.println("[UndoableInvoker] 执行命令:" + command.name()); command.execute(); history.push(command); } public void undoLast () { if (history.isEmpty()) { System.out.println("[UndoableInvoker] 没有可撤销命令" ); return ; } UndoableCommand command = history.pop(); System.out.println("[UndoableInvoker] 撤销命令:" + command.name()); command.undo(); } }
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class UndoCommandDemo { public static void main (String[] args) { OrderService orderService = new OrderService (); CreateOrderCommand createCommand = new CreateOrderCommand (orderService, 1L , 100L , 2 ); createCommand.execute(); Long orderId = createCommand.getCreatedOrderId(); UndoableCommandInvoker invoker = new UndoableCommandInvoker (); UndoableCommand cancelCommand = new UndoableCancelOrderCommand (orderService, orderId); invoker.invoke(cancelCommand); invoker.undoLast(); } }
撤销功能非常适合:
文本编辑器;
图形编辑器;
配置变更;
工作流操作;
后台管理操作;
游戏操作;
一些可补偿业务动作。
但是要注意:
不是所有业务都能简单撤销。
比如支付成功、短信已发送、库存已真实扣减,这些动作可能不能简单 undo()。
真实业务里更多使用“补偿命令”,而不是简单回滚。
chapter 19:命令队列 命令模式的另一个常见能力是队列化。
因为命令是对象,所以可以放进队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java.util.LinkedList;import java.util.Queue;public class CommandQueue { private final Queue<OrderCommand> queue = new LinkedList <>(); public void add (OrderCommand command) { queue.offer(command); } public void executeAll () { while (!queue.isEmpty()) { OrderCommand command = queue.poll(); System.out.println("[CommandQueue] 执行命令:" + command.name()); command.execute(); } } }
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class CommandQueueDemo { public static void main (String[] args) { OrderService orderService = new OrderService (); CommandQueue queue = new CommandQueue (); CreateOrderCommand createCommand1 = new CreateOrderCommand (orderService, 1L , 100L , 2 ); CreateOrderCommand createCommand2 = new CreateOrderCommand (orderService, 2L , 200L , 1 ); queue.add(createCommand1); queue.add(createCommand2); queue.executeAll(); } }
命令队列适合:
异步任务;
批量操作;
延迟执行;
削峰填谷;
后台任务调度。
如果队列换成 MQ,本质也是类似思想:
把请求封装成消息,异步交给消费者执行。
chapter 20:异步命令执行 命令也可以提交到线程池异步执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class AsyncCommandInvoker { private final ExecutorService executorService = Executors.newFixedThreadPool(4 ); public void submit (OrderCommand command) { executorService.submit(() -> { System.out.println("[AsyncInvoker] 异步执行命令:" + command.name()); command.execute(); }); } public void shutdown () { executorService.shutdown(); } }
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class AsyncCommandDemo { public static void main (String[] args) { OrderService orderService = new OrderService (); AsyncCommandInvoker invoker = new AsyncCommandInvoker (); invoker.submit(new CreateOrderCommand (orderService, 1L , 100L , 2 )); invoker.submit(new CreateOrderCommand (orderService, 2L , 200L , 1 )); invoker.shutdown(); } }
真实项目中,异步命令可以结合:
线程池;
MQ;
延迟队列;
定时任务;
Disruptor;
Job 调度框架;
任务表。
不过要注意:
一旦异步执行,就要考虑幂等、重试、失败补偿、上下文传递、TraceId、事务边界。
异步不是魔法,是把复杂度从当前线程搬到了另一个地方。搬家不是消失,箱子还得拆。
chapter 21:宏命令 宏命令就是把多个命令组合成一个命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import java.util.List;public class MacroOrderCommand implements OrderCommand { private final String name; private final List<OrderCommand> commands; public MacroOrderCommand (String name, List<OrderCommand> commands) { this .name = name; this .commands = List.copyOf(commands); } @Override public String name () { return name; } @Override public void execute () { for (OrderCommand command : commands) { command.execute(); } } }
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class MacroCommandDemo { public static void main (String[] args) { OrderService orderService = new OrderService (); CreateOrderCommand createCommand = new CreateOrderCommand (orderService, 1L , 100L , 2 ); createCommand.execute(); Long orderId = createCommand.getCreatedOrderId(); MacroOrderCommand macroCommand = new MacroOrderCommand ( "PayAndShipMacroCommand" , List.of( new PayOrderCommand (orderService, orderId), new ShipOrderCommand (orderService, orderId) ) ); macroCommand.execute(); } }
宏命令适合批量操作。
例如:
批量开关设备;
批量修改配置;
批量审批;
批量执行后台任务;
多步骤业务动作。
chapter 22:Spring Boot 中的命令模式落地 在 Spring Boot 项目中,更常见的写法不是让命令对象直接持有 Service,而是:
命令对象只保存参数,命令处理器负责执行业务。
这更接近 CQRS、Command Bus 的风格。
例如:
1 2 3 CreateOrderCommandDTO CreateOrderCommandHandler CommandBus
这种方式更适合企业级后端项目。
chapter 23:定义命令标记接口 1 2 public interface Command { }
命令对象只是数据载体。
例如创建订单命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class CreateOrderCommand implements Command { private final Long userId; private final Long productId; private final Integer quantity; public CreateOrderCommand (Long userId, Long productId, Integer quantity) { this .userId = userId; this .productId = productId; this .quantity = quantity; } public Long getUserId () { return userId; } public Long getProductId () { return productId; } public Integer getQuantity () { return quantity; } }
取消订单命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class CancelOrderCommand implements Command { private final Long orderId; private final String reason; public CancelOrderCommand (Long orderId, String reason) { this .orderId = orderId; this .reason = reason; } public Long getOrderId () { return orderId; } public String getReason () { return reason; } }
chapter 24:定义命令处理器接口 1 2 3 4 5 6 public interface CommandHandler <C extends Command , R> { Class<C> commandType () ; R handle (C command) ; }
每个处理器声明自己支持的命令类型。
例如:
命令总线可以根据类型找到对应处理器。
chapter 25:创建订单命令处理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import org.springframework.stereotype.Component;@Component public class CreateOrderCommandHandler implements CommandHandler <CreateOrderCommand, Long> { private final OrderApplicationService orderApplicationService; public CreateOrderCommandHandler (OrderApplicationService orderApplicationService) { this .orderApplicationService = orderApplicationService; } @Override public Class<CreateOrderCommand> commandType () { return CreateOrderCommand.class; } @Override public Long handle (CreateOrderCommand command) { return orderApplicationService.createOrder( command.getUserId(), command.getProductId(), command.getQuantity() ); } }
这里的 Receiver 可以是应用服务:
命令处理器负责把命令交给应用服务执行。
chapter 26:取消订单命令处理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import org.springframework.stereotype.Component;@Component public class CancelOrderCommandHandler implements CommandHandler <CancelOrderCommand, Void> { private final OrderApplicationService orderApplicationService; public CancelOrderCommandHandler (OrderApplicationService orderApplicationService) { this .orderApplicationService = orderApplicationService; } @Override public Class<CancelOrderCommand> commandType () { return CancelOrderCommand.class; } @Override public Void handle (CancelOrderCommand command) { orderApplicationService.cancelOrder( command.getOrderId(), command.getReason() ); return null ; } }
chapter 27:定义命令总线 CommandBus 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import org.springframework.stereotype.Component;import java.util.List;import java.util.Map;import java.util.stream.Collectors;@Component public class CommandBus { private final Map<Class<?>, CommandHandler<?, ?>> handlerMap; public CommandBus (List<CommandHandler<?, ?>> handlers) { this .handlerMap = handlers.stream() .collect(Collectors.toUnmodifiableMap(CommandHandler::commandType, handler -> handler)); } @SuppressWarnings("unchecked") public <C extends Command , R> R dispatch (C command) { if (command == null ) { throw new IllegalArgumentException ("command can not be null" ); } CommandHandler<C, R> handler = (CommandHandler<C, R>) handlerMap.get(command.getClass()); if (handler == null ) { throw new IllegalArgumentException ("No command handler found for " + command.getClass().getName()); } long start = System.currentTimeMillis(); System.out.println("[CommandBus] 开始分发命令:" + command.getClass().getSimpleName()); try { R result = handler.handle(command); System.out.println("[CommandBus] 命令执行成功:" + command.getClass().getSimpleName()); return result; } finally { long cost = System.currentTimeMillis() - start; System.out.println("[CommandBus] 命令耗时:" + cost + " ms" ); } } }
这里做了几件事:
启动时收集所有 CommandHandler;
按命令类型建立映射;
接收到命令后查找对应 Handler;
调用 Handler 执行命令;
统一记录日志和耗时。
这就是后端项目里非常常见的命令总线。
chapter 28:Controller 使用 CommandBus 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import org.springframework.web.bind.annotation.*;@RestController @RequestMapping("/orders") public class OrderController { private final CommandBus commandBus; public OrderController (CommandBus commandBus) { this .commandBus = commandBus; } @PostMapping public Long createOrder (@RequestBody CreateOrderRequest request) { CreateOrderCommand command = new CreateOrderCommand ( request.getUserId(), request.getProductId(), request.getQuantity() ); return commandBus.dispatch(command); } @PostMapping("/{orderId}/cancel") public void cancelOrder (@PathVariable Long orderId, @RequestBody CancelOrderRequest request) { CancelOrderCommand command = new CancelOrderCommand ( orderId, request.getReason() ); commandBus.dispatch(command); } }
请求 DTO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class CreateOrderRequest { private Long userId; private Long productId; private Integer quantity; public Long getUserId () { return userId; } public Long getProductId () { return productId; } public Integer getQuantity () { return quantity; } }
1 2 3 4 5 6 7 8 public class CancelOrderRequest { private String reason; public String getReason () { return reason; } }
这种写法的好处是:
Controller 很薄;
命令对象表达业务意图;
命令处理器职责清晰;
新增命令只需要新增 Command + Handler;
CommandBus 可以统一做横切增强。
chapter 29:CommandBus 可以做哪些统一增强 命令总线是一个很好的统一入口。
它可以做很多事情:
1. 日志 1 2 3 4 执行了什么命令 谁执行的 执行参数是什么 执行结果是什么
2. 审计 例如后台操作审计:
1 管理员 A 在 2026-04-01 取消了订单 10001
3. 权限校验 可以根据命令类型检查权限:
1 2 CreateOrderCommand -> order:create CancelOrderCommand -> order:cancel
4. 幂等 可以通过命令中的 requestId 做幂等控制。
5. 事务 可以在 Handler 或应用服务层控制事务。
6. 监控 可以统计每类命令的执行次数、耗时、失败率。
7. 异步分发 某些命令可以进入异步队列。
8. 重试 对可重试命令做统一失败重试。
不过要注意:
CommandBus 可以做横切增强,但不要把业务规则全部塞进去。
CommandBus 是调度中心,不是业务垃圾桶。
chapter 30:命令模式和 CQRS 命令模式和 CQRS 经常一起出现。
CQRS 是 Command Query Responsibility Segregation,命令查询职责分离。
它强调:
Command 负责改变系统状态;
Query 负责查询系统状态。
例如:
1 2 3 CreateOrderCommand CancelOrderCommand PayOrderCommand
这些是命令,会修改状态。
1 2 GetOrderDetailQuery ListUserOrdersQuery
这些是查询,不应该修改状态。
命令模式可以作为 CQRS 中 Command 部分的实现方式。
例如:
1 2 commandBus.dispatch(new CreateOrderCommand (...)); queryBus.query(new GetOrderDetailQuery (...));
这不是必须的,但在复杂业务系统中很常见。
chapter 31:命令模式和事件模式的区别 命令模式和事件模式很容易混。
它们都可以是一个对象,都可以放进 MQ。
但语义完全不同。
对比项
命令 Command
事件 Event
语义
希望系统去做某件事
表示某件事已经发生
命名
CreateOrderCommand
OrderCreatedEvent
时间方向
将来要执行
过去已经发生
接收者数量
通常一个明确处理者
可以有多个订阅者
是否期望结果
通常期望结果
通常不直接期望结果
示例
取消订单
订单已取消
命令是:
事件是:
这两个语义千万不要混。
如果用户点击“取消订单”,发送的是命令:
订单取消成功后发布的是事件:
命令是意图。
事件是事实。
chapter 32:命令模式和策略模式的区别 命令模式和策略模式都可以封装行为。
但关注点不同。
对比项
命令模式
策略模式
核心目的
将请求封装成对象
封装可替换算法
是否保存请求参数
通常会保存
通常不保存具体请求
是否支持排队/撤销/日志
适合
不是重点
调用方式
command.execute()
strategy.calculate(context)
示例
CancelOrderCommand
DiscountStrategy
策略模式解决的是:
多种算法如何替换。
命令模式解决的是:
一个请求如何对象化、调度、记录、撤销。
例如优惠计算:
1 discountStrategy.calculate(order);
这是策略模式。
取消订单:
1 commandBus.dispatch(new CancelOrderCommand (orderId));
这是命令模式。
chapter 33:命令模式和责任链模式的区别 命令模式和责任链模式也常常一起出现。
对比项
命令模式
责任链模式
核心目的
把请求封装成对象
让请求沿处理链传递
关注点
请求对象化
多个处理器协作
是否多个处理器
不一定
通常多个
典型场景
命令总线、任务队列、撤销
校验链、过滤器链、审批链
示例
CreateOrderCommand
OrderValidateHandlerChain
它们可以组合使用。
例如:
1 2 3 4 5 6 7 CreateOrderCommand -> CommandBus -> CreateOrderCommandHandler -> OrderCreateValidateChain -> ParamValidateHandler -> StockValidateHandler -> RiskValidateHandler
命令模式负责表达“创建订单”这个请求。
责任链模式负责处理“创建订单前的校验流程”。
chapter 34:命令模式和模板方法模式的区别
对比项
命令模式
模板方法模式
核心目的
请求对象化
固定算法骨架
扩展方式
新增命令类
子类重写步骤
是否适合排队
适合
不适合
是否适合撤销
适合
不是重点
示例
ExportReportCommand
AbstractImportTemplate
模板方法适合固定流程。
命令模式适合把操作封装成独立对象。
chapter 35:命令模式的优点 1. 解耦调用者和执行者 调用者只依赖命令接口或命令总线,不直接依赖具体执行逻辑。
2. 请求可以对象化 一旦请求变成对象,就可以传递、保存、排队、重试、撤销。
3. 易于扩展新命令 新增命令时,新增 Command 和 Handler 即可。
4. 支持日志和审计 命令对象天然适合记录操作信息。
5. 支持撤销和重做 命令可以保存旧状态,实现 undo()。
6. 支持队列和异步 命令可以进入队列,由后台线程或 MQ 消费。
7. 支持宏命令 多个命令可以组合成一个大命令。
chapter 36:命令模式的缺点 1. 类数量增加 每个操作都可能对应一个命令类和处理器类。
2. 简单场景会显得啰嗦 如果只是一个普通方法调用,命令模式可能过度设计。
3. 命令对象设计需要谨慎 命令里放什么参数、是否可序列化、是否可重试,都需要设计。
4. 撤销并不总是容易 真实业务中的撤销往往不是简单恢复旧状态,而是需要补偿流程。
5. 异步命令会引入更多复杂度 包括幂等、重试、事务、消息丢失、顺序一致性等。
chapter 37:适用场景 命令模式适合以下场景。
1. 请求需要被参数化 例如按钮绑定不同命令。
1 button.setCommand(new SaveCommand ());
2. 请求需要排队或异步执行 例如任务队列、后台任务、MQ 消息。
3. 请求需要日志和审计 例如后台管理操作日志。
4. 请求需要撤销和重做 例如编辑器、配置变更、工作流操作。
5. 调用者和执行者需要解耦 例如 Controller 不直接依赖复杂业务执行逻辑。
6. 需要统一命令分发 例如 CommandBus。
7. 需要把操作保存下来之后再执行 例如延迟任务、定时任务、失败重试任务。
chapter 38:不适合使用的场景 以下场景不建议使用命令模式。
1. 普通 CRUD 过于简单 如果只是:
1 userService.getById(id);
没必要包装成:
不然项目会很快变成类文件批发市场。
2. 操作不需要排队、撤销、异步、日志 如果没有这些需求,直接方法调用更简单。
3. 团队不熟悉命令总线 命令总线会引入间接调用,新人可能需要理解成本。
4. 命令数量失控 如果所有操作都命令化,命令类会非常多。
需要分包、命名、规范管理。
5. 命令和事件语义混乱 不要用 OrderCreatedEvent 去表示“创建订单请求”。
也不要用 CreateOrderCommand 表示“订单已创建事实”。
语义错了,架构后面会非常别扭。
chapter 39:真实项目中的实践建议 1. 命令命名要表达动作意图 推荐:
1 2 3 4 5 CreateOrderCommand CancelOrderCommand PayOrderCommand RefundOrderCommand ExportOrderCommand
不推荐:
1 2 3 4 OrderCommand OrderRequest OrderAction DoOrderCommand
名字越泛,职责越危险。
2. 命令对象尽量只保存请求参数 不要把复杂业务逻辑塞进命令 DTO。
在 Spring Boot 项目中,更推荐:
1 Command + CommandHandler
而不是命令对象自己持有一堆 Service。
3. 命令处理器要有明确边界 一个 Handler 处理一个命令。
不要一个 Handler 处理几十种命令。
4. CommandBus 只做调度和横切增强 适合做:
日志;
监控;
权限;
幂等;
事务入口;
Handler 路由。
不适合做:
订单核心业务;
价格计算;
库存规则;
支付状态机。
5. 异步命令必须考虑幂等 只要进入 MQ 或任务队列,就必须假设命令可能重复执行。
因此要设计:
requestId;
业务唯一键;
去重表;
幂等状态机;
分布式锁;
唯一索引。
6. 撤销操作优先考虑补偿命令 真实业务中更推荐:
1 2 3 CancelPaymentCommand ReleaseStockCommand RollbackCouponCommand
而不是简单 undo()。
因为很多业务动作不可逆,只能补偿。
7. 命令入库要考虑序列化 如果命令要持久化到任务表,需要考虑:
命令类型;
命令参数 JSON;
状态;
重试次数;
下一次执行时间;
错误信息;
创建时间;
更新时间。
8. 命令和事件分清楚 命令是:
事件是:
这个边界非常重要。
9. 简单项目不要过度命令化 命令模式很强,但不要把所有方法都包成命令。
设计模式是工具,不是 KPI。
chapter 40:完整案例代码汇总 订单对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Order { private final Long orderId; private String status; public Order (Long orderId, String status) { this .orderId = orderId; this .status = status; } public Long getOrderId () { return orderId; } public String getStatus () { return status; } public void changeStatus (String status) { this .status = status; } }
接收者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.atomic.AtomicLong;public class OrderService { private final AtomicLong idGenerator = new AtomicLong (10000 ); private final Map<Long, Order> orderStore = new ConcurrentHashMap <>(); public Long createOrder (Long userId, Long productId, Integer quantity) { Long orderId = idGenerator.incrementAndGet(); Order order = new Order (orderId, "CREATED" ); orderStore.put(orderId, order); System.out.println("创建订单,orderId = " + orderId + ",userId = " + userId + ",productId = " + productId + ",quantity = " + quantity); return orderId; } public void cancelOrder (Long orderId) { Order order = getOrder(orderId); order.changeStatus("CANCELLED" ); System.out.println("取消订单,orderId = " + orderId); } public void payOrder (Long orderId) { Order order = getOrder(orderId); order.changeStatus("PAID" ); System.out.println("支付订单,orderId = " + orderId); } public void shipOrder (Long orderId) { Order order = getOrder(orderId); order.changeStatus("SHIPPED" ); System.out.println("发货订单,orderId = " + orderId); } public void changeStatus (Long orderId, String status) { Order order = getOrder(orderId); order.changeStatus(status); System.out.println("修改订单状态,orderId = " + orderId + ",status = " + status); } public Order getOrder (Long orderId) { Order order = orderStore.get(orderId); if (order == null ) { throw new IllegalArgumentException ("order not found, orderId = " + orderId); } return order; } }
命令接口 1 2 3 4 5 6 public interface OrderCommand { String name () ; void execute () ; }
创建订单命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class CreateOrderCommand implements OrderCommand { private final OrderService orderService; private final Long userId; private final Long productId; private final Integer quantity; private Long createdOrderId; public CreateOrderCommand (OrderService orderService, Long userId, Long productId, Integer quantity) { this .orderService = orderService; this .userId = userId; this .productId = productId; this .quantity = quantity; } @Override public String name () { return "CreateOrderCommand" ; } @Override public void execute () { this .createdOrderId = orderService.createOrder(userId, productId, quantity); } public Long getCreatedOrderId () { return createdOrderId; } }
支付订单命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class PayOrderCommand implements OrderCommand { private final OrderService orderService; private final Long orderId; public PayOrderCommand (OrderService orderService, Long orderId) { this .orderService = orderService; this .orderId = orderId; } @Override public String name () { return "PayOrderCommand" ; } @Override public void execute () { orderService.payOrder(orderId); } }
发货订单命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ShipOrderCommand implements OrderCommand { private final OrderService orderService; private final Long orderId; public ShipOrderCommand (OrderService orderService, Long orderId) { this .orderService = orderService; this .orderId = orderId; } @Override public String name () { return "ShipOrderCommand" ; } @Override public void execute () { orderService.shipOrder(orderId); } }
调用者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class CommandInvoker { public void invoke (OrderCommand command) { if (command == null ) { throw new IllegalArgumentException ("command can not be null" ); } long start = System.currentTimeMillis(); System.out.println("[Invoker] 开始执行命令:" + command.name()); try { command.execute(); System.out.println("[Invoker] 命令执行成功:" + command.name()); } finally { long cost = System.currentTimeMillis() - start; System.out.println("[Invoker] 命令耗时:" + cost + " ms" ); } } }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class CommandPatternDemo { public static void main (String[] args) { OrderService orderService = new OrderService (); CommandInvoker invoker = new CommandInvoker (); CreateOrderCommand createCommand = new CreateOrderCommand ( orderService, 1L , 100L , 2 ); invoker.invoke(createCommand); Long orderId = createCommand.getCreatedOrderId(); invoker.invoke(new PayOrderCommand (orderService, orderId)); invoker.invoke(new ShipOrderCommand (orderService, orderId)); } }
chapter 41:一句话总结 命令模式的本质是:
把请求封装成对象,让请求可以被传递、存储、排队、记录、撤销、重试和异步执行。
它特别适合:
操作需要排队;
操作需要异步;
操作需要审计;
操作需要撤销;
操作需要重试;
操作需要统一调度;
调用者和执行者需要解耦;
后端系统需要 CommandBus。
命令模式不是为了把每个方法都包装成类。
它真正适合的是那些“操作本身有生命周期”的场景。
一个普通方法调用,直接调用就好。
一个需要被记录、调度、重试、撤销、异步执行的请求,就值得被封装成命令。
好的命令模式像一张清晰的工单:谁发起、做什么、谁处理、结果如何,都有记录。
坏的命令模式像一堆小纸条:类很多,语义乱,最后没人知道哪张纸条该交给谁。
参考资料
Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software .
Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices .
Martin Fowler. Patterns of Enterprise Application Architecture .
Eric Evans. Domain-Driven Design: Tackling Complexity in the Heart of Software .
Spring Framework Documentation: Core Technologies - The IoC Container.
Spring Framework Documentation: Task Execution and Scheduling.
Refactoring Guru: Command Pattern.
SourceMaking: Command Design Pattern.
启示录 富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。