在状态机(Ref#1)上有很多文章和开源项目,可以在Google或GitHub上进行搜索。Spring团队本身提供了一个状态机框架(Ref#2)。但是,我发现这些框架不容易自定义。此外,在我需要的地方添加日志并引发自定义异常并不容易。因此,我创建了状态机的简单实现,可以轻松地将其集成到Spring Boot应用程序中。
状态机的想法是定义一组状态转换,其中每个转换都受事件影响。例如,维基百科举例说明了具有锁定和解锁状态的旋转门,该状态受事件“硬币”和“推送”的影响。当发生“硬币”事件时,旋转栅门的状态从“已锁定”转变为“未锁定”。当发生“推送”事件时,它会从“解锁”转换为“锁定”。状态机强制执行以下操作:当旋转闸门处于“锁定”状态时,“推送”事件无效。同样,当旋转门处于“解锁”状态时,“硬币”事件无效。
本文提出的状态机框架基于状态转换表表示,如下所示:
拟议的框架包括以下核心组成部分:
2.- ProcessEvent 将上述状态转换配置为Java Enum类。
StateTransitionsManager —响应事件。如果事件是事件前,则将处理转发到处理器,或者如果事件是事件后,则将状态更改为最终状态。如果需要,它还会设置默认状态。
Processor —执行相应流程状态转换步骤的业务规则。
下面列出了相应的Java组件:
//Enum implements this marker interface public interface ProcessState { } //Enum implements this interface public interface ProcessEvent { public abstract Class<? extends Processor> nextStepProcessor(ProcessEvent event); public abstract ProcessState nextState(ProcessEvent event); } //events handler public interface StateTransitionsManager { public ProcessData processEvent(ProcessData data) throws ProcessException; } //enforces initialization of the state where needed public abstract class AbstractStateTransitionsManager implements StateTransitionsManager { protected abstract ProcessData initializeState(ProcessData data) throws ProcessException; protected abstract ProcessData processStateTransition(ProcessData data) throws ProcessException; @Override public ProcessData processEvent(ProcessData data) throws ProcessException { initializeState(data); return processStateTransition(data); } } //executes the business rules //needed for this state transition step public interface Processor { public ProcessData process(ProcessData data) throws ProcessException; } // public interface ProcessData { public ProcessEvent getEvent(); } // public class ProcessException extends Exception { private static final long serialVersionUID = 1L; public ProcessException(String message) { super(message); } public ProcessException(String message, Throwable e) { super(message, e); } }
用于在线订单处理应用程序的上述状态机框架的示例实现涉及以下类:
/** * DEFAULT - submit -> orderProcessor() -> orderCreated -> PMTPENDING * PMTPENDING - pay -> paymentProcessor() -> paymentError -> PMTPENDING * PMTPENDING - pay -> paymentProcessor() -> paymentSuccess -> COMPLETED */ public enum OrderState implements ProcessState { Default, PaymentPending, Completed; } /** * DEFAULT - submit -> orderProcessor() -> orderCreated -> PMTPENDING * PMTPENDING - pay -> paymentProcessor() -> paymentError -> PMTPENDING * PMTPENDING - pay -> paymentProcessor() -> paymentSuccess -> COMPLETED */ public enum OrderEvent implements ProcessEvent { submit { @Override public Class<? extends Processor> nextStepProcessor(ProcessEvent event) { return OrderProcessor.class; } /** * This event has no effect on state so return current state */ @Override public ProcessState nextState(ProcessEvent event) { return OrderState.Default; } }, orderCreated { /** * This event does not trigger any process * So return null */ @Override public Class<? extends Processor> nextStepProcessor(ProcessEvent event) { return null; } @Override public ProcessState nextState(ProcessEvent event) { return OrderState.PaymentPending; } }, pay { @Override public Class<? extends Processor> nextStepProcessor(ProcessEvent event) { return PaymentProcessor.class; } /** * This event has no effect on state so return current state */ @Override public ProcessState nextState(ProcessEvent event) { return OrderState.PaymentPending; } }, paymentSuccess { /** * This event does not trigger any process * So return null */ @Override public Class<? extends Processor> nextStepProcessor(ProcessEvent event) { return null; } @Override public ProcessState nextState(ProcessEvent event) { return OrderState.Completed; } }, paymentError { /** * This event does not trigger any process * So return null */ @Override public Class<? extends Processor> nextStepProcessor(ProcessEvent event) { return null; } @Override public ProcessState nextState(ProcessEvent event) { return OrderState.PaymentPending; } }; } /** * This class manages various state transitions * based on the event * The superclass AbstractStateTransitionsManager * calls the two methods initializeState and * processStateTransition in that order */ @RequiredArgsConstructor @Slf4j @Service public class OrderStateTransitionsManager extends AbstractStateTransitionsManager { private final ApplicationContext context; private final OrderDbService dbService; @Override protected ProcessData processStateTransition(ProcessData sdata) throws ProcessException { OrderData data = (OrderData) sdata; try { log.info("Pre-event: " + data.getEvent().toString()); data = (OrderData) this.context.getBean(data.getEvent().nextStepProcessor(data.getEvent())).process(data); log.info("Post-event: " + data.getEvent().toString()); dbService.getStates().put(data.getOrderId(), (OrderState)data.getEvent().nextState(data.getEvent())); log.info("Final state: " + dbService.getStates().get(data.getOrderId()).name()); log.info("??*************************************"); } catch (OrderException e) { log.info("Post-event: " + ((OrderEvent) data.getEvent()).name()); dbService.getStates().put(data.getOrderId(), (OrderState)data.getEvent().nextState(data.getEvent())); log.info("Final state: " + dbService.getStates().get(data.getOrderId()).name()); log.info("??*************************************"); throw new OrderException(((OrderEvent) data.getEvent()).name(), e); } return data; } private OrderData checkStateForReturningCustomers(OrderData data) throws OrderException { // returning customers must have a state if (data.getOrderId() != null) { if (this.dbService.getStates().get(data.getOrderId()) == null) { throw new OrderException("No state exists for orderId=" + data.getOrderId()); } else if (this.dbService.getStates().get(data.getOrderId()) == OrderState.Completed) { throw new OrderException("Order is completed for orderId=" + data.getOrderId()); } else { log.info("Initial state: " + dbService.getStates().get(data.getOrderId()).name()); } } return data; } @Override protected ProcessData initializeState(ProcessData sdata) throws OrderException { OrderData data = (OrderData) sdata; if (data.getOrderId() != null) { return checkStateForReturningCustomers(data); } UUID orderId = UUID.randomUUID(); data.setOrderId(orderId); dbService.getStates().put(orderId, (OrderState) OrderState.Default); log.info("Initial state: " + dbService.getStates().get(data.getOrderId()).name()); return data; } public ConcurrentHashMap<UUID, OrderState> getStates() { return dbService.getStates(); } } //persists state of the data //here we are using HashMap for illustration purposes @Service public class OrderDbService { private final ConcurrentHashMap<UUID, OrderState> states; public OrderDbService() { this.states = new ConcurrentHashMap<UUID, OrderState>(); } public ConcurrentHashMap<UUID, OrderState> getStates() { return states; } } @NoArgsConstructor @AllArgsConstructor @Setter @Getter @Builder public class OrderData implements ProcessData { private double payment; private ProcessEvent event; private UUID orderId; @Override public ProcessEvent getEvent() { return this.event; } } @Service public class OrderProcessor implements Processor { @Override public ProcessData process(ProcessData data) throws ProcessException{ ((OrderData)data).setEvent(OrderEvent.orderCreated); return data; } } @Service public class PaymentProcessor implements Processor { @Override public ProcessData process(ProcessData data) throws ProcessException { if(((OrderData)data).getPayment() < 1.00) { ((OrderData)data).setEvent(OrderEvent.paymentError); throw new PaymentException(OrderEvent.paymentError.name()); } else { ((OrderData)data).setEvent(OrderEvent.paymentSuccess); } return data; } } @RequiredArgsConstructor @RestController public class OrderController { private final OrderStateTransitionsManager stateTrasitionsManager; @GetMapping("/order/cart") public String handleOrderPayment( @RequestParam double payment, @RequestParam UUID orderId) throws Exception { OrderData data = new OrderData(); data.setPayment(payment); data.setOrderId(orderId); data.setEvent(OrderEvent.pay); data = (OrderData)stateTrasitionsManager.processEvent(data); return ((OrderEvent)data.getEvent()).name(); } @ExceptionHandler(value=OrderException.class) public String handleOrderException(OrderException e) { return e.getMessage(); } @GetMapping("/order") public String handleOrderSubmit() throws ProcessException { OrderData data = new OrderData(); data.setEvent(OrderEvent.submit); data = (OrderData)stateTrasitionsManager.processEvent(data); return ((OrderEvent)data.getEvent()).name() + ", orderId = " + data.getOrderId(); } } @SpringBootApplication public class StateMachineApplication { public static void main(String[] args) { SpringApplication.run(StateMachineApplication.class, args); } }
完整的源代码也可以在GitHub上找到。
请注意,实现此框架的第一步是创建状态转换表。要运行此示例,请将源 导入到STS之类的IDE中,并作为Spring Boot应用程序运行。可以执行以下两个API来测试上述实现:
GET http://localhost:8080/order
GET http://localhost:8080/order/cart?payment=123&orderId=123
为了在浏览器中进行快速测试,两个API均实现为GET。
测试#1:当 /order 调用API时,将返回诸如“创建订单,orderId = 123”的响应,并显示控制台日志:
Initial state: Default Pre-event: submit Post-event: orderCreated Final state: PaymentPending
Initial state: Default
Pre-event: submit
Post-event: orderCreated
Final state: PaymentPending
测试2:如果 /order/cart 调用API时出现付款错误,例如: /order/cart?payment=0&orderId=123 调用了API,则返回响应:“ paymentError”,并且控制台日志显示: Initial state: PaymentPending Pre-event: pay Post-event: paymentError Final state: PaymentPending
/order/cart?payment=0&orderId=123
Initial state: PaymentPending
Pre-event: pay
Post-event: paymentError
测试#3:当 /order/cart?payment=123&orderId=123 API调用,就像一个回应:“paymentSuccess”返回和控制台日志显示: Initial state: PaymentPending Pre-event: pay Post-event: paymentSuccess Final state: Completed
Post-event: paymentSuccess
Final state: Completed
测试#4:付款成功处理后,如果 /order/cart?payment=123&orderId=123 再次调用,则返回响应:“ orderId = 123的订单已完成”,状态保持不变。
结论 呈现的状态机应易于定制,并集成到面向流程/工作流的Spring Boot项目中。希望你喜欢!
原文链接:https://codingdict.com/