文章

Spring状态机-基本概念使用

Spring状态机

状态机基本概念

State 状态

状态机是一个状态模型,比如一个键盘,一般左侧有普通键,右侧有数字键盘,numlock可以让数字键盘处于两种不同的状态,如果它不处于活动状态,按下数字键盘键会使用箭头导航等。如果数字键盘处于活动状态,按下这些键会输入数字。本质上,键盘的数字键盘部分可以处于两种不同的状态。

Pseudo States 伪状态

伪状态是具有特殊含义的状态,比如初始状态、结束状态、条件状态、历史状态、分支状态、合并状态、入口点、出口点状态、

Guard Conditions 守卫条件

守卫条件是动态选择运行特定的动作或转换

Events 活动

事件是驱动状态机最常用的触发行为,事件也被称为“信号”。

Transitions 转换

转换是源状态和目标状态之间的关系。状态机从一种状态到另一种状态是由触发器引起的状态转换。

Triggers 触发器

触发器引发状态转换。触发器可以由事件或计时器驱动。

Actions 行为

在状态转换前中后定义一些自定义操作,动作通常可以访问状态机上下文

以订单状态为例,使用spring状态机完成订单中状态流转。
状态流转图

+---------------------------------------------------------------+
|                            SM                                 |
+---------------------------------------------------------------+
|                                                               |
|      +----------------+           +----------------+          |
|  *-->|     PLACED     |           |   PROCESSING   |          |
|      +----------------+  PROCESS  +----------------+ SEND     |
|      |                |---------->|                |-----+    |
|      +----------------+           +----------------+     |    |
|                                                          |    |
|                                                          |    |
|      +----------------+           +----------------+     |    |
|      |      SENT      |           |   DELIVERED    |     |    |
|      +----------------+  DELIVER  +----------------+     |    |
|      |                |<----------|                |<----+    |
|      +----------------+           +----------------+          |
|                                                               |
+---------------------------------------------------------------+

依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.1.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-starter</artifactId>
        </dependency>
<!-- https://mvnrepository.com/artifact/org.hsqldb/hsqldb -->
<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.7.1</version>
    <scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.statemachine/spring-statemachine-recipes-common -->
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-recipes-common</artifactId>
    <version>4.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.statemachine/spring-statemachine-core -->
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>4.0.0</version>
</dependency>

订单实体类

public class Order {
    int id;
    String state;

    public Order(int id, String state) {
        this.id = id;
        this.state = state;
    }

    @Override
    public String toString() {
        return "Order [id=" + id + ", state=" + state + "]";
    }

}

状态机配置

@Configuration
@EnableStateMachine
public class StateMachineConfig
        extends StateMachineConfigurerAdapter<String, String> {

    @Override
    public void configure(StateMachineStateConfigurer<String, String> states)
            throws Exception {
        states
                .withStates()
                .initial("PLACED",actionInit())

                .state("PROCESSING",entryPro(),exitPro())
                .state("SENT")
                .state("DELIVERED");
    }

    private Action<String, String> exitPro() {
        return context -> System.out.println("进入pro状态");
    }

    private Action<String, String> entryPro() {
        return context -> System.out.println("退出pro状态");
    }

    private Action<String, String> actionInit() {
        return context -> System.out.println("初始化状态");
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<String, String> transitions)
            throws Exception {
        transitions
                .withExternal()
                .source("PLACED").target("PROCESSING")
                .event("PROCESS")
                .and()
                .withExternal()
                .source("PROCESSING").target("SENT")
                .event("SEND")
                .and()
                .withExternal()
                .source("SENT").target("DELIVERED")
                .event("DELIVER");
    }

}

状态机持久化类

public class Persist {

	private final PersistStateMachineHandler handler;

//tag::snippetA[]
	@Autowired
	private JdbcTemplate jdbcTemplate;
//end::snippetA[]

	private final PersistStateChangeListener listener = new LocalPersistStateChangeListener();

	public Persist(PersistStateMachineHandler handler) {
		this.handler = handler;
		this.handler.addPersistStateChangeListener(listener);
	}

	public String listDbEntries() {
		List<Order> orders = jdbcTemplate.query(
		        "select id, state from orders",
		        new RowMapper<Order>() {
		            public Order mapRow(ResultSet rs, int rowNum) throws SQLException {
		            	return new Order(rs.getInt("id"), rs.getString("state"));
		            }
		        });
		StringBuilder buf = new StringBuilder();
		for (Order order : orders) {
			buf.append(order);
			buf.append("\n");
		}
		return buf.toString();
	}

//tag::snippetB[]
	public void change(int order, String event) {
		Order o = jdbcTemplate.queryForObject("select id, state from orders where id = ?",
				new RowMapper<Order>() {
					public Order mapRow(ResultSet rs, int rowNum) throws SQLException {
						return new Order(rs.getInt("id"), rs.getString("state"));
					}
				}, new Object[] { order });
		handler.handleEventWithStateReactively(MessageBuilder
				.withPayload(event).setHeader("order", order).build(), o.state)
			.subscribe();
	}

	//end::snippetB[]

//tag::snippetC[]
	private class LocalPersistStateChangeListener implements PersistStateChangeListener {

		@Override
		public void onPersist(State<String, String> state, Message<String> message,
				Transition<String, String> transition, StateMachine<String, String> stateMachine) {
			System.out.println("dizhi"+stateMachine);
			System.out.println("uuid"+stateMachine.getUuid());
			if (message != null && message.getHeaders().containsKey("order")) {
				Integer order = message.getHeaders().get("order", Integer.class);
				jdbcTemplate.update("update orders set state = ? where id = ?", state.getId(), order);
			}
		}
	}
//end::snippetC[]

}

状态机持久化配置

@Configuration
public class PersistHandlerConfig {

    @Autowired
    private StateMachine<String, String> stateMachine;

    @Bean
    public Persist persist() {
        return new Persist(persistStateMachineHandler());
    }

    @Bean
    public PersistStateMachineHandler persistStateMachineHandler() {
        return new PersistStateMachineHandler(stateMachine);
    }

}

状态转换监听

@WithStateMachine
public class OrderStateListenerImpl {

    @OnTransition(source = "PLACED", target = "PROCESSING")
    public void fromS1ToS2(Message<String> message) {
        System.out.println("--------------"+message.toString()+"-------------");
        System.out.println("订单1 从PLACED ---------------  PROCESSING");
        
    }
}

application.properties

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=false

spring.datasource.username=sa
spring.datasource.password=

data.sql

insert into orders (id, state) values (1, 'PLACED');
insert into orders (id, state) values (2, 'PROCESSING');
insert into orders (id, state) values (3, 'SENT');
insert into orders (id, state) values (4, 'DELIVERED');

schema.sql

create table orders (
  id int,
  state varchar(256)
);

controller

@RestController
public class PersistController  {

	@Autowired
	private Persist persist;

	@GetMapping("db")
	public String listDbEntries() {
		return persist.listDbEntries();
	}


	@GetMapping("process")
	public void process(@RequestParam int order) {
		persist.change(order, "PROCESS");
	}


	@GetMapping("send")
	public void send(@RequestParam int order) {
		persist.change(order, "SEND");
	}


	@GetMapping("deliver")
	public void deliver(@RequestParam int order) {
		persist.change(order, "DELIVER");
	}

}
License:  CC BY 4.0