您的位置:首页 > 编程语言 > Java开发

springboot干货——(十九)Spring StateMachine框架实现状态机

2018-03-16 10:54 1186 查看
简介:

Spring StateMachine框架的主要功能是帮助开发者简化状态机的开发过程,让状态机结构更加层次化。

1.老规矩,先上项目结构图





2.新建项目springboot-statemachine,pom.xml如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>

<groupId>com.gwd</groupId>
<artifactId>springboot-statemachine</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>springboot-statemachine</name>
<description>Demo project for springboot-statemachine</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-statemachine.version>2.0.0.RELEASE</spring-statemachine.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-bom</artifactId>
<version>${spring-statemachine.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

3.定义场景状态和枚举事件具体如下
状态:

package com.gwd.pojo;
/**
* @FileName States.java
* @Description:TODO
* @author JackHisen(gu.weidong)
* @version V1.0
* @createtime 2018年3月15日 下午7:31:25
* 修改历史:
* 时间 作者 版本 描述
*====================================================
*
*/
public enum States {
UNPAID, // 待支付
WAITING_FOR_RECEIVE, // 待收货
DONE // 结束
}

事件:
package com.gwd.pojo;
/**
* @FileName Events.java
* @Description:TODO
* @author JackHisen(gu.weidong)
* @version V1.0
* @createtime 2018年3月15日 下午7:39:55
* 修改历史:
* 时间           作者          版本        描述
*====================================================
*
*/
public enum Events {
PAY,        // 支付
RECEIVE     // 收货
}


其中共有三个状态(待支付、待收货、结束)以及两个引起状态迁移的事件(支付、收货),其中支付事件PAY会触发状态从待支付UNPAID状态到待收货WAITING_FOR_RECEIVE状态的迁移,而收货事件RECEIVE会触发状态从待收货WAITING_FOR_RECEIVE状态到结束DONE状态的迁移。

4.创建状态机的配置类

package com.gwd.config;

import java.util.EnumSet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.transition.Transition;
import com.gwd.pojo.Events;
import com.gwd.pojo.States;

/**
* @FileName StateMachineConfig.java
* @Description:TODO
* @author JackHisen(gu.weidong)
* @version V1.0
* @createtime 2018年3月15日 下午7:47:32
* 修改历史:
* 时间 作者 版本 描述
*====================================================
*
*/
@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events>{

@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.UNPAID)
.states(EnumSet.allOf(States.class));
}

@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.UNPAID).target(States.WAITING_FOR_RECEIVE)
.event(Events.PAY)
.and()
.withExternal()
.source(States.WAITING_FOR_RECEIVE).target(States.DONE)
.event(Events.RECEIVE);
}

@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
throws Exception {
config
.withConfiguration()
.listener(listener());
}

@Bean
public StateMachineListener<States, Events> listener() {
return new StateMachineListenerAdapter<States, Events>() {

@Override
public void transition(Transition<States, Events> transition) {
if(transition.getTarget().getId() == States.UNPAID) {
System.out.println("订单创建,待支付");
return;
}

if(transition.getSource().getId() == States.UNPAID
&& transition.getTarget().getId() == States.WAITING_FOR_RECEIVE) {
System.out.println("用户完成支付,待收货");
return;
}

if(transition.getSource().getId() == States.WAITING_FOR_RECEIVE
&& transition.getTarget().getId() == States.DONE) {
System.out.println("用户已收货,订单完成");
return;
}
}

};
}
}

在该类中定义了较多配置内容,下面对这些内容一一说明:

@EnableStateMachine
注解用来启用Spring
StateMachine状态机功能

configure(StateMachineStateConfigurer<States,
Events> states)
方法用来初始化当前状态机拥有哪些状态,其中
initial(States.UNPAID)
定义了初始状态为
UNPAID
states(EnumSet.allOf(States.class))
则指定了使用上一步中定义的所有状态作为该状态机的状态定义。

configure(StateMachineTransitionConfigurer<States,
Events> transitions)
方法用来初始化当前状态机有哪些状态迁移动作,其中命名中我们很容易理解每一个迁移动作,都有来源状态
source
,目标状态
target
以及触发事件
event


configure(StateMachineConfigurationConfigurer<States,
Events> config)
方法为当前的状态机指定了状态监听器,其中
listener()
则是调用了下一个内容创建的监听器实例,用来处理各个各个发生的状态迁移事件。

StateMachineListener<States,
Events> listener()
方法用来创建
StateMachineListener
状态监听器的实例,在该实例中会定义具体的状态迁移处理逻辑,上面的实现中只是做了一些输出,实际业务场景会会有更负责的逻辑,所以通常情况下,我们可以将该实例的定义放到独立的类定义中,并用注入的方式加载进来。

5.controller

package com.gwd.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.gwd.pojo.Events;
import com.gwd.pojo.States;

/**
* @FileName TestController.java
* @Description:TODO
* @author JackHisen(gu.weidong)
* @version V1.0
* @createtime 2018年3月15日 下午8:11:24
* 修改历史:
* 时间 作者 版本 描述
*====================================================
*
*/
@RestController
public class TestController {

@Autowired
private StateMachine<States, Events> stateMachine;

@RequestMapping("/testMachine")
@ResponseBody
public void testMachine() {
stateMachine.start();
stateMachine.sendEvent(Events.PAY);
stateMachine.sendEvent(Events.RECEIVE);
}
}
run
函数中,我们定义了整个流程的处理过程,其中
start()
就是创建这个订单流程,根据之前的定义,该订单会处于待支付状态,然后通过调用
sendEvent(Events.PAY)
执行支付操作,最后通过掉用
sendEvent(Events.RECEIVE)
来完成收货操作。在运行了上述程序之后,我们可以在控制台中获得类似下面的输出内容:
订单创建,待支付
2018-03-16 10:28:57.799 INFO 15368 --- [nio-8080-exec-1] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@7616252a
2018-03-16 10:28:57.800 INFO 15368 --- [nio-8080-exec-1] o.s.s.support.LifecycleObjectSupport : started UNPAID DONE WAITING_FOR_RECEIVE / UNPAID / uuid=a57c0a4b-98b5-4155-b4de-a9d847d96399 / id=null
用户完成支付,待收货
用户已收货,订单完成

其中包括了状态监听器中对各个状态迁移做出的处理。

通过上面的例子,我们可以对如何使用Spring StateMachine做如下小结:

定义状态和事件枚举

为状态机定义使用的所有状态以及初始状态

为状态机定义状态的迁移动作

为状态机指定监听处理器
状态监听器

通过上面的入门示例以及最后的小结,我们可以看到使用Spring
StateMachine来实现状态机的时候,代码逻辑变得非常简单并且具有层次化。整个状态的调度逻辑主要依靠配置方式的定义,而所有的业务逻辑操作都被定义在了状态监听器中,其实状态监听器可以实现的功能远不止上面我们所述的内容,它还有更多的事件捕获,我们可以通过查看
StateMachineListener
接口来了解它所有的事件定义:

/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0 *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.statemachine.listener;

import org.springframework.messaging.Message;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateContext.Stage;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.Transition;

/**
* {@code StateMachineListener} for various state machine events.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public interface StateMachineListener<S,E> {

/**
* Notified when state is changed.
*
* @param from the source state
* @param to the target state
*/
void stateChanged(State<S,E> from, State<S,E> to);

/**
* Notified when state is entered.
*
* @param state the state
*/
void stateEntered(State<S,E> state);

/**
* Notified when state is exited.
*
* @param state the state
*/
void stateExited(State<S,E> state);

/**
* Notified when event was not accepted.
*
* @param event the event
*/
void eventNotAccepted(Message<E> event);

/**
* Notified when transition happened.
*
* @param transition the transition
*/
void transition(Transition<S, E> transition);

/**
* Notified when transition started.
*
* @param transition the transition
*/
void transitionStarted(Transition<S, E> transition);

/**
* Notified when transition ended.
*
* @param transition the transition
*/
void transitionEnded(Transition<S, E> transition);

/**
* Notified when statemachine starts
*
* @param stateMachine the statemachine
*/
void stateMachineStarted(StateMachine<S, E> stateMachine);

/**
* Notified when statemachine stops
*
* @param stateMachine the statemachine
*/
void stateMachineStopped(StateMachine<S, E> stateMachine);

/**
* Notified when statemachine enters error it can't recover from.
*
* @param stateMachine the state machine
* @param exception the exception
*/
void stateMachineError(StateMachine<S, E> stateMachine, Exception exception);

/**
* Notified when extended state variable is either added, modified or removed.
*
* @param key the variable key
* @param value the variable value
*/
void extendedStateChanged(Object key, Object value);

/**
* Notified on various {@link Stage}s about a {@link StateContext}.
*
* @param stateContext the state context
*/
void stateContext(StateContext<S, E> stateContext);

}
注解监听器
对于状态监听器,Spring
StateMachine还提供了优雅的注解配置实现方式,所有
StateMachineListener
接口中定义的事件都能通过注解的方式来进行配置实现。比如,我们可以将之前实现的状态监听器用注解配置来做进一步的简化:

package com.gwd.config;

import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;

/**
* @FileName EventConfig.java
* @Description:TODO
* @author JackHisen(gu.weidong)
* @version V1.0
* @createtime 2018年3月16日 上午10:41:20
* 修改历史:
* 时间 作者 版本 描述
*====================================================
*
*/
@WithStateMachine
public class EventConfig {

@OnTransition(target = "UNPAID")
public void create() {
System.out.println("-------订单创建,待支付");
}

@OnTransition(source = "UNPAID", target = "WAITING_FOR_RECEIVE")
public void pay() {
System.out.println("---------用户完成支付,待收货");
}

@OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")
public void receive() {
System.out.println("---------用户已收货,订单完成");
}
}上述代码实现了与快速入门中定义的
listener()
方法创建的监听器相同的功能,但是由于通过注解的方式配置,省去了原来事件监听器中各种if的判断,使得代码显得更为简洁,拥有了更好的可读性。

根据实际测试,注解的方式在非注解方式之前运行

2018-03-16 10:50:22.571 INFO 9032 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 16 ms
-------订单创建,待支付
订单创建,待支付
2018-03-16 10:50:22.617 INFO 9032 --- [nio-8080-exec-1] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@37f160cf
2018-03-16 10:50:22.618 INFO 9032 --- [nio-8080-exec-1] o.s.s.support.LifecycleObjectSupport : started DONE WAITING_FOR_RECEIVE UNPAID / UNPAID / uuid=0c1d6a4f-bfd6-4e22-b84e-358e1b6cefb7 / id=null
---------用户完成支付,待收货
用户完成支付,待收货
---------用户已收货,订单完成
用户已收货,订单完成
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: