在实际示例中使用Spring状态机

在ROSEU协议示例中使用Spring状态机


本文通过一个根据ROSEU技术建立连接的示例来描述Spring状态机的使用。 在两个EDO运营商之间以点对点模式或通过漫游中心建立了连接。 它描述了如何管理状态,按事件切换状态以及在更改状态时执行指定的操作。 如果有兴趣的话,我要一只猫。

图片

此处详细描述了ROSEU协议。

对于一篇文章,我们只需要了解在两个EDI客户端之间建立连接的原理。 他们每个人都发送邀请建立连接。 邀请的类型为“请求”或“中断”。

为了建立连接,工作流程中的两个参与者都必须发送“请求”类型的邀请。 此后,将视为已建立连接。

如果参与者之一发送的邀请类型为“中断”,则连接断开。

在我们的系统中,我们为EDI参与者设置了七个可能的状态。

  1. 连接失败-NO_CONNECTION
  2. 我们的客户已发出漫游邀请。 但是我们还没有交付。 通过应用程序的机构及其发送到漫游中心的异步性可以证明这一点。 -INVITATION_SAVED
  3. 连接成功建立-ARE_CONNECTED
  4. 参与者之一主动断开连接-CONNECTION_BROKEN
  5. 收到外部邀请,我们的客户之前没有发送任何内容-INVITATION_RECEIVED
  6. 漫游中心接受了客户的邀请-INVITATION_SEND
  7. 连接错误-CONNECTION_ERROR

可能的事件:

  1. 我们的客户向“请求”发送了邀请。 -OUTCOME_INVITATION
  2. 我们的客户发送了邀请函“ Break”-OUTCOME_REJECT
  3. 外部客户发送了“请求”邀请-INCOME_INVITATION
  4. 外部客户发送了“中断”邀请-INCOME_REJECT
  5. 漫游中心已成功接受邀请-RC_SUCCESS
  6. 漫游中心未接受邀请-RC_ERROR

状态切换表。 第一行是初始状态。 第一列是一个事件。 在十字路口-一种新状态。

图片

可以通过if-else开关对这种状态切换进行编码。 但是在我看来,通过状态机,这将更加方便。

逻辑如下-如果有人断开连接,则任何人都可以重新建立它。 如果建立连接时发生错误,则无法进行其他操作,需要手动纠正问题。

有关Spring状态机的详细文档来自此处

我们将通过建造者制造汽车

StateMachineBuilder.Builder<ClientsState, ContractorEvent> builder = StateMachineBuilder.builder(); 

接下来,我们设置所有可能的状态。 初始状态设置为客户的当前状态。

 builder.configureStates() .withStates() .initial(initialState) .states(EnumSet.allOf(ClientsState.class)); 

我们配置汽车的自动启动。 否则,您将必须手动启动

 builder.configureConfiguration() .withConfiguration() .autoStartup(true); 

接下来,我们规定付款。 -初始状态, 目标 -最终状态, 事件 -发生状态切换的事件, 操作 -更新客户端状态。

 builder.configureTransitions() .withExternal() .source(NO_CONNECTION) .target(INVITATION_RECEIVED) .event(INCOME_INVITATION) .action(updateStateAction) .and() .withExternal() .source(NO_CONNECTION) .target(CONNECTION_BROKEN) .event(INCOME_REJECT) .action(updateStateAction) 

创建状态机后,我们将事件传递到其输入。 但是我们实际上需要其他信息来更新客户的状态。 因此,我们将事件包装在message中 ,并将必要的数据放在header中

 StateMachine<ClientsState, ContractorEvent> sm = builder.build(); Map<String, Object> clients = new HashMap<>(); clients.put("client1", "client11"); clients.put("client2", "client22"); MessageHeaders headers = new MessageHeaders(clients); Message<ContractorEvent> message = new GenericMessage<>(event, headers); sm.sendEvent(message); sm.stop(); 

进一步地,该数据被提取并使用。

 @Service public class UpdateStateAction implements Action<ClientsState, ContractorEvent> { @Override public void execute(StateContext<ClientsState, ContractorEvent> context) { System.out.println("Source state: " + context.getSource()); System.out.println("Target state: " + context.getTarget()); System.out.println("Event: " + context.getEvent()); MessageHeaders headers = context.getMessageHeaders(); System.out.println(headers.get("client1")); System.out.println(headers.get("client2")); } } 

您也可以使用防护来防止状态更改,但是在我们这种情况下,这不是必需的。

基本上就是这样。 该示例的源代码可以从链接中获取

在本文中,我们研究了如何使用Spring状态机对状态转换表进行编码。

这不是其全部功能,而只是最基本的功能。 我希望本文对您有所帮助,并鼓励您使用此框架。

如果您有使用它的个人经验,请发表评论。

UPD

在使用过程中,发现了一个有趣的功能-在默认操作之一发生异常的情况下,不会引发错误,而只是记录错误。 在这种情况下,整个状态机的执行不会停止。 甚至hasStateMachineError方法也返回false。
作为决策,我们进行了一个AbstractAction,在其中捕获异常并将其置于状态机上下文中。 之后,在发生异常的情况下,hasStateMachineError返回true,然后我们对其进行进一步处理。

 @Override public void execute(StateContext<StatesEnum, OperationsEnum> context) { try { prepareContext(context); executeInternal(context); } catch (Exception e) { logger.error("  state machine", e); context.getStateMachine().setStateMachineError(e); } } 

Source: https://habr.com/ru/post/zh-CN440788/


All Articles