Это 12-й день моего участия в Gengwen Challenge, Подробную информацию о мероприятии см.:Обновить вызов
Введение
Эта статья в основном знакомит с конечным автоматом и некоторыми связанными с ним понятиями. В сочетании с простым процессом статуса заказа, пример интеграции в SpringbootSpring-statemachine.
Конечный автомат
Конечный автомат (англ. Finite-State Machine, аббревиатура: FSM), называемый конечным автоматом, представляет собой математическую модель, которая представляет ограниченное состояние, а также переход и действие между этими состояниями..
Применение модели FSM может помочь управлять порядком состояний в жизненном цикле объекта и событиями, которые приводят к изменению состояния..Извлечение управления состоянием и событиями из if else различных методов бизнес-службы. FSM имеет широкий спектр приложений и может использоваться в сценариях со сложными потоками состояний и высокими требованиями к масштабируемости.
Ниже приведены четыре элемента модели конечного автомата, а именно текущее состояние, условие, действие и следующее состояние.
- Текущее состояние: относится к текущему состоянию.
- Условия: также известны как «события». При миграции условия выполнено, он будет вызвать действие или выполнить состояние.
- Действие: действие для выполнения после выполнения состояния. После выполнения действия его можно мигрировать в новое состояние, или он может оставаться в исходном состоянии. Действия не требуются. Когда условия выполняются, вы можете напрямую мигрировать в новое состояние без выполнения каких-либо действий..
- Подсостояние: новое состояние для перехода при выполнении условия. "Вторичное состояние" относится к "текущему состоянию". Как только "вторичное состояние" будет активировано, оно превратится в новое "текущее состояние"..
В конечном автомате каждое состояние имеет соответствующее поведение, и состояние переключается по мере срабатывания поведения. Один из способов — использовать двумерный массив для реализации механизма конечного автомата, где абсцисса представляет поведение, ордината — состояние, а конкретное значение — текущее состояние.
Мы разрабатываем конечный автомат со сценарием входа в систему.
Разработайте таблицу конечного автомата.
Горизонтальная ось — действие, вертикальная ось — состояние
На данный момент это 2D-массив следующим образом
- Кроме того, мы также можем реализовать конечный автомат с помощью шаблона состояния.Шаблон состояния инкапсулирует каждое состояние в независимый класс, и конкретное поведение будет меняться в зависимости от внутреннего состояния. Режим состояния использует классы для представления состояний, поэтому мы можем легко изменять состояние объектов, переключая классы, избегая длинных операторов условного перехода,
- Сделать систему более гибкой и расширяемой. Теперь определим перечисление состояний, которое включает 4 состояния: не подключен, подключен, зарегистрирован, зарегистрирован.
Определите класс среды, который является объектом, фактически владеющим состоянием.
Шаблон состояния использует классы для представления состояний, так что вы можете легко изменить состояние объекта, переключая классы. Мы определяем несколько классов состояния.
Обратите внимание, что мы можем сгенерировать исключение RuntimeException, если действие не вызывает изменение состояния. Кроме того, при вызове переключение состояния контролируется классом среды, как показано ниже.
Spring StateMachine делает структуру конечного автомата более иерархической, что может помочь разработчикам упростить процесс разработки конечных автоматов. Теперь давайте модернизируем Spring StateMachine. Измените файл pom, чтобы добавить зависимости Maven/gradle.
dependencies {
compile 'org.springframework.statemachine:spring-statemachine-core:1.2.7.RELEASE'
}
Определите перечисление состояний, которое включает 4 состояния: не подключен, подключен, зарегистрирован и зарегистрирован.
public enum RegStatusEnum {
// 未连接
UNCONNECTED,
// 已连接
CONNECTED,
// 正在登录
LOGINING,
// 登录进系统
LOGIN_INTO_SYSTEM;
}
Определите перечисление событий, появление события вызывает переход состояния
public enum RegEventEnum {
// 连接
CONNECT,
// 开始登录
BEGIN_TO_LOGIN,
// 登录成功
LOGIN_SUCCESS,
// 登录失败
LOGIN_FAILURE,
// 注销登录
LOGOUT;
}
Настройте конечный автомат и включите функцию конечного автомата с помощью аннотаций. Класс конфигурации обычно наследует класс EnumStateMachineConfigurerAdapter и переопределяет некоторые методы конфигурации для настройки начального состояния конечного автомата и связи между событиями и переходами состояний.
import static com.qyz.dp.state.events.RegEventEnum.BEGIN_TO_LOGIN;
import static com.qyz.dp.state.events.RegEventEnum.CONNECT;
import static com.qyz.dp.state.events.RegEventEnum.LOGIN_FAILURE;
import static com.qyz.dp.state.events.RegEventEnum.LOGIN_SUCCESS;
import static com.qyz.dp.state.events.RegEventEnum.LOGOUT;
import static com.qyz.dp.state.state.RegStatusEnum.CONNECTED;
import static com.qyz.dp.state.state.RegStatusEnum.LOGINING;
import static com.qyz.dp.state.state.RegStatusEnum.LOGIN_INTO_SYSTEM;
import static com.qyz.dp.state.state.RegStatusEnum.UNCONNECTED;
import java.util.EnumSet;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import com.qyz.dp.state.events.RegEventEnum;
import com.qyz.dp.state.state.RegStatusEnum;
@Configuration
@EnableStateMachine // 开启状态机配置
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter{
/**
* 配置状态机状态
*/
@Override
public void configure(StateMachineStateConfigurer states) throws Exception {
states.withStates()
// 初始化状态机状态
.initial(RegStatusEnum.UNCONNECTED)
// 指定状态机的所有状态
.states(EnumSet.allOf(RegStatusEnum.class));
}
/**
* 配置状态机状态转换
*/
@Override
public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
// 1. connect UNCONNECTED -> CONNECTED
transitions.withExternal()
.source(UNCONNECTED)
.target(CONNECTED)
.event(CONNECT)
// 2. beginToLogin CONNECTED -> LOGINING
.and().withExternal()
.source(CONNECTED)
.target(LOGINING)
.event(BEGIN_TO_LOGIN)
// 3. login failure LOGINING -> UNCONNECTED
.and().withExternal()
.source(LOGINING)
.target(UNCONNECTED)
.event(LOGIN_FAILURE)
// 4. login success LOGINING -> LOGIN_INTO_SYSTEM
.and().withExternal()
.source(LOGINING)
.target(LOGIN_INTO_SYSTEM)
.event(LOGIN_SUCCESS)
// 5. logout LOGIN_INTO_SYSTEM -> UNCONNECTED
.and().withExternal()
.source(LOGIN_INTO_SYSTEM)
.target(UNCONNECTED)
.event(LOGOUT);
}
}
Spring StateMachine предоставляет метод реализации конфигурации аннотаций.Все события, определенные в интерфейсе StateMachineListener, можно настроить и реализовать с помощью аннотаций. Возьмем в качестве примера событие подключения. В @OnTransition источник указывает исходное состояние, а цель указывает целевое состояние. Когда событие инициируется, оно будет отслеживаться, и будет вызываться метод connect().
При запуске springboot вам нужно ввести состояние конечного автомата и конфигурацию событий. В основном это касается следующих двух категорий:
-
StateMachineStateConfigurer Настройте набор состояний и начальное состояние, общий параметр S представляет состояние, а E представляет событие.
-
StateMachineTransitionConfigurer настраивает переход потока состояний и может определять события, принимаемые переходом состояний.
Настройте прослушиватели событий, действия, которые будут запускаться при возникновении события
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
@Configuration
@WithStateMachine
public class StateMachineEventConfig {
@OnTransition(source = "UNCONNECTED", target = "CONNECTED")
public void connect() {
System.out.println("Switch state from UNCONNECTED to CONNECTED: connect");
}
@OnTransition(source = "CONNECTED", target = "LOGINING")
public void beginToLogin() {
System.out.println("Switch state from CONNECTED to LOGINING: beginToLogin");
}
@OnTransition(source = "LOGINING", target = "LOGIN_INTO_SYSTEM")
public void loginSuccess() {
System.out.println("Switch state from LOGINING to LOGIN_INTO_SYSTEM: loginSuccess");
}
@OnTransition(source = "LOGINING", target = "UNCONNECTED")
public void loginFailure() {
System.out.println("Switch state from LOGINING to UNCONNECTED: loginFailure");
}
@OnTransition(source = "LOGIN_INTO_SYSTEM", target = "UNCONNECTED")
public void logout()
{
System.out.println("Switch state from LOGIN_INTO_SYSTEM to UNCONNECTED: logout");
}
}
Автосвязывание конечного автомата с помощью аннотаций Здесь написан остаточный интерфейс, чтобы инициировать изменения конечного автомата.
@RestController
public class WebApi {
@Autowired
private StateMachine stateMachine;
@GetMapping(value = "/testStateMachine")
public void testStateMachine()
{
stateMachine.start();
stateMachine.sendEvent(RegEventEnum.CONNECT);
stateMachine.sendEvent(RegEventEnum.BEGIN_TO_LOGIN);
stateMachine.sendEvent(RegEventEnum.LOGIN_FAILURE);
stateMachine.sendEvent(RegEventEnum.LOGOUT);
}
}
Switch state from UNCONNECTED to CONNECTED: connect
Switch state from CONNECTED to LOGINING: beginToLogin
Switch state from LOGINING to UNCONNECTED: loginFailure
- Как видно из вывода, хоть и отправляются 4 события, выходов всего три. Причина в том, что когда происходит последнее событие LOGOUT, конечный автомат находится в состоянии UNCONNECTED, и с событием LOGOUT не связан переход между состояниями, поэтому он не работает.
- С помощью конечного автомата, реализованного Spring, всеми отношениями между классами управляет IOC-контейнер, реализующий развязку в истинном смысле. Конечно же, Весенний Дафа – хороший.
Spring StateMachine делает структуру конечного автомата более иерархической. Давайте рассмотрим следующие основные шаги:
-
Первым шагом является определение государственного перечисления.
-
Второй шаг - определить перечисление событий.
-
Третий шаг — определить конфигурацию конечного автомата, установить начальное состояние и взаимосвязь между состоянием и событием.
-
Четвертый шаг — определить прослушиватель состояния, который запускает метод при изменении состояния.
слушатель перехода состояния
В процессе передачи состояния некоторые задачи, такие как сохраняемость или бизнес-мониторинг, могут выполняться слушателями. В сценариях, требующих постоянства, постоянная обработка может быть добавлена к прослушивателю в шаблоне конечного автомата.
который в основном включает
Прослушатель событий StatemachineListener (реализован через механизм событий весны).
-
Прослушивание stateEntered (вход в состояние), stateExited (выход из состояния), eventNotAccepted (события не отвечают), transition (преобразование), transitionStarted (начало преобразования), transitionEnded (конец преобразования), stateMachineStarted (запуск конечного автомата), stateMachineStopped (конечный автомат выключен), stateMachineError (аномальный конечный автомат) и другие события, при этом слушатель может отслеживать статус передачи.
-
Интерфейс перехватчика StateChangeInterceptor, отличный от Listener. Это может изменить изменения в цепочке перехода состояний. В основном в preEvent (предварительная обработка события), preStateChange (предварительная обработка изменения состояния), postStateChange (постобработка изменения состояния), preTransition (предварительная обработка преобразования), postTransition (постобработка преобразования), stateMachineError (аномальная обработка) и другие точки выполнения.
-
Экземпляр конечного автомата StateMachine. Конечный автомат Spring поддерживает создание одноэлементного и фабричного режимов. Каждый конечный автомат имеет уникальный machineId для идентификации экземпляра машины; следует отметить, что экземпляр конечного автомата хранит связанные с контекстом свойства, такие как текущий конечный автомат. , поэтому этот экземпляр не может использоваться совместно несколькими потоками.
Чтобы облегчить расширение большего количества прослушивателей и управлять прослушивателями и перехватчиками. Вы можете определить обработчик на основе экземпляра конечного автомата: PersistStateMachineHandler и прослушивателя постоянных сущностей OrderPersistStateChangeListener следующим образом:
Обработчик и интерфейс слушателя определяют PersistStateMachineHandler:
public class PersistStateMachineHandler extends LifecycleObjectSupport {
private final StateMachine<OrderStatus, OrderStatusChangeEvent> stateMachine;
private final PersistingStateChangeInterceptor interceptor = new
PersistingStateChangeInterceptor();
private final CompositePersistStateChangeListener listeners = new
CompositePersistStateChangeListener();
/**
* 实例化一个新的持久化状态机Handler
*
* @param stateMachine 状态机实例
*/
public PersistStateMachineHandler(StateMachine<OrderStatus, OrderStatusChangeEvent>
stateMachine) {
Assert.notNull(stateMachine, "State machine must be set");
this.stateMachine = stateMachine;
}
@Override
protected void onInit() throws Exception {
stateMachine.getStateMachineAccessor().doWithAllRegions(function ->
function.addStateMachineInterceptor(interceptor));
}
/**
* 处理entity的事件
*
* @param event
* @param state
* @return 如果事件被接受处理,返回true
*/
public boolean handleEventWithState(Message<OrderStatusChangeEvent> event, OrderStatus
state) {
stateMachine.stop();
List<StateMachineAccess<OrderStatus, OrderStatusChangeEvent>> withAllRegions =
stateMachine.getStateMachineAccessor()
.withAllRegions();
for (StateMachineAccess<OrderStatus, OrderStatusChangeEvent> a : withAllRegions) {
a.resetStateMachine(new DefaultStateMachineContext<>(state, null, null, null));
}
stateMachine.start();
return stateMachine.sendEvent(event);
}
/**
* 添加listener
*
* @param listener the listener
*/
public void addPersistStateChangeListener(PersistStateChangeListener listener) {
listeners.register(listener);
}
/**
* 可以通过 addPersistStateChangeListener,增加当前Handler的PersistStateChangeListener。
* 在状态变化的持久化触发时,会调用相应的实现了PersistStateChangeListener的Listener实例。
*/
public interface PersistStateChangeListener {
/**
* 当状态被持久化,调用此方法
*
* @param state
* @param message
* @param transition
* @param stateMachine 状态机实例
*/
void onPersist(State<OrderStatus, OrderStatusChangeEvent> state,
Message<OrderStatusChangeEvent> message, Transition<OrderStatus,
OrderStatusChangeEvent> transition,
StateMachine<OrderStatus, OrderStatusChangeEvent> stateMachine);
}
private class PersistingStateChangeInterceptor extends
StateMachineInterceptorAdapter<OrderStatus, OrderStatusChangeEvent> {
// 状态预处理的拦截器方法
@Override
public void preStateChange(State<OrderStatus, OrderStatusChangeEvent> state,
Message<OrderStatusChangeEvent> message,
Transition<OrderStatus, OrderStatusChangeEvent> transition,
StateMachine<OrderStatus,
OrderStatusChangeEvent> stateMachine) {
listeners.onPersist(state, message, transition, stateMachine);
}
}
private class CompositePersistStateChangeListener extends
AbstractCompositeListener<PersistStateChangeListener> implements
PersistStateChangeListener {
@Override
public void onPersist(State<OrderStatus, OrderStatusChangeEvent> state,
Message<OrderStatusChangeEvent> message,
Transition<OrderStatus, OrderStatusChangeEvent> transition,
StateMachine<OrderStatus,
OrderStatusChangeEvent> stateMachine) {
for (Iterator<PersistStateChangeListener> iterator = getListeners().reverse();
iterator.hasNext(); ) {
PersistStateChangeListener listener = iterator.next();
listener.onPersist(state, message, transition, stateMachine);
}
}
}
}
Класс реализации слушателя OrderPersistStateChangeListener для объекта заказа, постоянное состояние которого изменилось:
public class OrderPersistStateChangeListener implements
PersistStateMachineHandler.PersistStateChangeListener {
@Autowired
private OrderRepo repo;
@Override
public void onPersist(State<OrderStatus, OrderStatusChangeEvent> state,
Message<OrderStatusChangeEvent> message,
Transition<OrderStatus, OrderStatusChangeEvent> transition,
StateMachine<OrderStatus, OrderStatusChangeEvent> stateMachine) {
if (message != null && message.getHeaders().containsKey("order")) {
Integer order = message.getHeaders().get("order", Integer.class);
Order o = repo.findByOrderId(order);
OrderStatus status = state.getId();
o.setStatus(status);
repo.save(o);
}
}
}
Springboot внедряет класс Configuration компонентов Handler и Listener, OrderPersistHandlerConfig.
@Configuration
public class OrderPersistHandlerConfig {
@Autowired
private StateMachine<OrderStatus, OrderStatusChangeEvent> stateMachine;
@Bean
public OrderStateService persist() {
PersistStateMachineHandler handler = persistStateMachineHandler();
handler.addPersistStateChangeListener(persistStateChangeListener());
return new OrderStateService(handler);
}
@Bean
public PersistStateMachineHandler persistStateMachineHandler() {
return new PersistStateMachineHandler(stateMachine);
}
@Bean
public OrderPersistStateChangeListener persistStateChangeListener(){
return new OrderPersistStateChangeListener();
}
}
Controller&Service пример службы заказа
В примере представлены два простых интерфейса: один предназначен для просмотра списка всех заказов, а другой — для изменения статуса заказа.
Контроллер выглядит следующим образом: OrderController:
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderStateService orderStateService;
/**
* 列出所有的订单列表
*
* @return
*/
@RequestMapping(method = {RequestMethod.GET})
public ResponseEntity orders() {
String orders = orderStateService.listDbEntries();
return new ResponseEntity(orders, HttpStatus.OK);
}
/**
* 通过触发一个事件,改变一个订单的状态
* @param orderId
* @param event
* @return
*/
@RequestMapping(value = "/{orderId}", method = {RequestMethod.POST})
public ResponseEntity processOrderState(@PathVariable("orderId") Integer orderId, @RequestParam("event") OrderStatusChangeEvent event) {
Boolean result = orderStateService.change(orderId, event);
return new ResponseEntity(result, HttpStatus.OK);
}
}
Класс обслуживания заказа OrderStateService:
@Component
public class OrderStateService {
private PersistStateMachineHandler handler;
public OrderStateService(PersistStateMachineHandler handler) {
this.handler = handler;
}
@Autowired
private OrderRepo repo;
public String listDbEntries() {
List<Order> orders = repo.findAll();
StringJoiner sj = new StringJoiner(",");
for (Order order : orders) {
sj.add(order.toString());
}
return sj.toString();
}
public boolean change(int order, OrderStatusChangeEvent event) {
Order o = repo.findByOrderId(order);
return handler.handleEventWithState(MessageBuilder.withPayload(event).setHeader("order", order).build(), o.getStatus());
}
}