状态机中,每个状态有着相应的行为,随着行为的触发来切换状态。其中一种做法是使用二维数组实现状态机机制,其中横坐标表示行为,纵坐标表示状态,具体的数值则表示当前的状态。
我们以登录场景设计一个状态机。
这时,我们设计一张状态机表。
那么,此时它的二维数组,如下所示。
此外,我们也可以通过状态模式实现一个状态机。状态模式将每一个状态封装成独立的类,具体行为会随着内部状态而改变。状态模式用类表示状态,这样我们就能通过切换类来方便地改变对象的状态,避免了冗长的条件分支语句,让系统具有更好的灵活性和可扩展性。
现在,我们定义一个状态枚举,其中包括未连接、已连接、注册中、已注册 4 种状态。
-
public enum StateEnum {
-
// 未连接
-
UNCONNECT(1, "UNCONNECT"),
-
// 已连接
-
CONNECT(2, "CONNECT"),
-
// 注册中
-
REGISTING(3, "REGISTING"),
-
// 已注册
-
REGISTED(4, "REGISTED");
-
-
private int key;
-
private String value;
-
-
StateEnum(int key, String value) {
-
this.key = key;
-
this.value = value;
-
}
-
public int getKey() {return key;}
-
public String getValue() {return value;}
-
}
定义一个环境类,它是实际上是真正拥有状态的对象。
-
public class Context {
-
private State state;
-
public void connect(){
-
state.connect(this);
-
System.out.println("STATE : " + state.getCurState());
-
}
-
public void register(){
-
state.register(this);
-
System.out.println("STATE : " + state.getCurState());
-
}
-
public void registerSuccess(){
-
state.registerSuccess(this);
-
System.out.println("STATE : " + state.getCurState());
-
}
-
public void registerFailed(){
-
state.registerFailed(this);
-
System.out.println("STATE : " + state.getCurState());
-
}
-
public void unRegister(){
-
state.unRegister(this);
-
System.out.println("STATE : " + state.getCurState());
-
}
-
public State getState() {
-
return state;
-
}
-
public void setState(State state) {
-
this.state = state;
-
}
-
}
状态模式用类表示状态,这样我们就能通过切换类来方便地改变对象的状态。现在,我们定义几个状态类。
-
public interface State {
-
void connect(Context c);
-
void register(Context c);
-
void registerSuccess(Context c);
-
void registerFailed(Context c);
-
void unRegister(Context c);
-
String getCurState();
-
}
-
-
public class UnconnectState implements State {
-
@Override
-
public void connect(Context c) {
-
c.setState(new ConnectState());
-
}
-
@Override
-
public void register(Context c) {
-
throw new RuntimeException("INVALID_OPERATE_ERROR");
-
}
-
@Override
-
public void registerSuccess(Context c) {
-
throw new RuntimeException("INVALID_OPERATE_ERROR");
-
}
-
@Override
-
public void registerFailed(Context c) {
-
throw new RuntimeException("INVALID_OPERATE_ERROR");
-
}
-
@Override
-
public void unRegister(Context c) {
-
throw new RuntimeException("INVALID_OPERATE_ERROR");
-
}
-
@Override
-
public String getCurState() {
-
return StateEnum.UNCONNECT.toString();
-
}
-
}
-
-
public class ConnectState implements State {
-
@Override
-
public void connect(Context c) {
-
c.setState(new ConnectState());
-
}
-
@Override
-
public void register(Context c) {
-
c.setState(new RegistingState());
-
}
-
@Override
-
public void registerSuccess(Context c) {
-
throw new RuntimeException("INVALID_OPERATE_ERROR");
-
}
-
@Override
-
public void registerFailed(Context c) {
-
throw new RuntimeException("INVALID_OPERATE_ERROR");
-
}
-
@Override
-
public void unRegister(Context c) {
-
c.setState(new UnconnectState());
-
}
-
@Override
-
public String getCurState() {
-
return StateEnum.CONNECT.toString();
-
}
-
}
-
-
public class RegistingState implements State {
-
@Override
-
public void connect(Context c) {
-
throw new RuntimeException("INVALID_OPERATE_ERROR");
-
}
-
@Override
-
public void register(Context c) {
-
c.setState(new RegistingState());
-
}
-
@Override
-
public void registerSuccess(Context c) {
-
c.setState(new RegistedState());
-
}
-
@Override
-
public void registerFailed(Context c) {
-
c.setState(new UnconnectState());
-
}
-
@Override
-
public void unRegister(Context c) {
-
c.setState(new UnconnectState());
-
}
-
@Override
-
public String getCurState() {
-
return StateEnum.REGISTING.toString();
-
}
-
}
-
-
public class RegistedState implements State {
-
@Override
-
public void connect(Context c) {
-
throw new RuntimeException("INVALID_OPERATE_ERROR");
-
}
-
@Override
-
public void register(Context c) {
-
throw new RuntimeException("INVALID_OPERATE_ERROR");
-
}
-
@Override
-
public void registerSuccess(Context c) {
-
c.setState(new RegistedState());
-
}
-
@Override
-
public void registerFailed(Context c) {
-
throw new RuntimeException("INVALID_OPERATE_ERROR");
-
}
-
@Override
-
public void unRegister(Context c) {
-
c.setState(new UnconnectState());
-
}
-
@Override
-
public String getCurState() {
-
return StateEnum.REGISTED.toString();
-
}
-
}
注意的是,如果某个行为不会触发状态的变化,我们可以抛出一个 RuntimeException 异常。此外,调用时,通过环境类控制状态的切换,如下所示。
-
public class Client {
-
public static void main(String[] args) {
-
Context context = new Context();
-
context.connect();
-
context.register();
-
}
-
}
Spring StateMachine 让状态机结构更加层次化,可以帮助开发者简化状态机的开发过程。现在,我们来用 Spring StateMachine 进行改造。修改 pom 文件,添加 Maven 依赖。
-
<dependency>
-
<groupId>org.springframework.statemachine</groupId>
-
<artifactId>spring-statemachine-core</artifactId>
-
<version>1.2.0.RELEASE</version>
-
</dependency>
定义一个状态枚举,其中包括未连接、已连接、注册中、已注册 4 种状态。
-
public enum RegStatusEnum {
-
// 未连接
-
UNCONNECTED,
-
// 已连接
-
CONNECTED,
-
// 注册中
-
REGISTERING,
-
// 已注册
-
REGISTERED;
-
}
定义一个行为枚举,其中包括连接、注册、注册成功、注册失败、注销 5 种行为事件。
-
public enum RegEventEnum {
-
// 连接
-
CONNECT,
-
// 注册
-
REGISTER,
-
// 注册成功
-
REGISTER_SUCCESS,
-
// 注册失败
-
REGISTER_FAILED,
-
// 注销
-
UN_REGISTER;
-
}
接着,我们需要进行状态机配置,其中 @EnableStateMachine 注解,标识启用 Spring StateMachine 状态机功能。
-
@Configuration
-
@EnableStateMachine
-
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<RegStatusEnum, RegEventEnum> {
-
}
我们需要初始化状态机的状态。其中,initial(RegStatusEnum.UNCONNECTED) 定义了初始状态是未连接状态。states(EnumSet.allOf(RegStatusEnum.class)) 定义了状态机中存在的所有状态。
-
@Override
-
public void configure(StateMachineStateConfigurer<RegStatusEnum, RegEventEnum> states) throws Exception {
-
states.withStates()
-
// 定义初始状态
-
.initial(RegStatusEnum.UNCONNECTED)
-
// 定义状态机状态
-
.states(EnumSet.allOf(RegStatusEnum.class));
-
}
我们需要初始化当前状态机有哪些状态事件。其中, source 指定原始状态,target 指定目标状态,event 指定触发事件。
-
@Override
-
public void configure(StateMachineTransitionConfigurer<RegStatusEnum, RegEventEnum> transitions)
-
throws Exception {
-
// 1.连接事件
-
// 未连接 -> 已连接
-
.withExternal()
-
.source(RegStatusEnum.UNCONNECTED)
-
.target(RegStatusEnum.CONNECTED)
-
.event(RegEventEnum.CONNECT)
-
.and()
-
.withExternal()
-
.source(RegStatusEnum.CONNECTED)
-
.target(RegStatusEnum.CONNECTED)
-
.event(RegEventEnum.CONNECT)
-
.and()
-
-
// 2.注册事件
-
// 已连接 -> 注册中
-
.withExternal()
-
.source(RegStatusEnum.CONNECTED)
-
.target(RegStatusEnum.REGISTERING)
-
.event(RegEventEnum.REGISTER)
-
.and()
-
.withExternal()
-
.source(RegStatusEnum.REGISTERING)
-
.target(RegStatusEnum.REGISTERING)
-
.event(RegEventEnum.REGISTER)
-
.and()
-
-
// 3.注册成功事件
-
// 注册中 -> 已注册
-
.withExternal()
-
.source(RegStatusEnum.REGISTERING)
-
.target(RegStatusEnum.REGISTERED)
-
.event(RegEventEnum.REGISTER_SUCCESS)
-
.and()
-
.withExternal()
-
.source(RegStatusEnum.REGISTERED)
-
.target(RegStatusEnum.REGISTERED)
-
.event(RegEventEnum.REGISTER_SUCCESS)
-
.and()
-
-
// 4.注册失败事件
-
// 注册中 -> 未连接
-
.withExternal()
-
.source(RegStatusEnum.REGISTERING)
-
.target(RegStatusEnum.UNCONNECTED)
-
.event(RegEventEnum.REGISTER_FAILED)
-
.and()
-
-
// 5.注销事件
-
// 已连接 -> 未连接
-
.withExternal()
-
.source(RegStatusEnum.CONNECTED)
-
.target(RegStatusEnum.UNCONNECTED)
-
.event(RegEventEnum.UN_REGISTER)
-
.and()
-
// 注册中 -> 未连接
-
.withExternal()
-
.source(RegStatusEnum.REGISTERING)
-
.target(RegStatusEnum.UNCONNECTED)
-
.event(RegEventEnum.UN_REGISTER)
-
.and()
-
// 已注册 -> 未连接
-
.withExternal()
-
.source(RegStatusEnum.REGISTERED)
-
.target(RegStatusEnum.UNCONNECTED)
-
.event(RegEventEnum.UN_REGISTER)
-
;
-
}
Spring StateMachine 提供了注解配置实现方式,所有 StateMachineListener 接口中定义的事件都能通过注解的方式来进行配置实现。这里以连接事件为案例,@OnTransition 中 source 指定原始状态,target 指定目标状态,当事件触发时将会被监听到从而调用 connect() 方法。
-
@WithStateMachine
-
public class StateMachineEventConfig {
-
-
@OnTransition(source = "UNCONNECTED", target = "CONNECTED")
-
public void connect() {
-
System.out.println("///");
-
System.out.println("连接事件, 未连接 -> 已连接");
-
System.out.println("///");
-
}
-
-
@OnTransition(source = "CONNECTED", target = "REGISTERING")
-
public void register() {
-
System.out.println("///");
-
System.out.println("注册事件, 已连接 -> 注册中");
-
System.out.println("///");
-
}
-
-
@OnTransition(source = "REGISTERING", target = "REGISTERED")
-
public void registerSuccess() {
-
System.out.println("///");
-
System.out.println("注册成功事件, 注册中 -> 已注册");
-
System.out.println("///");
-
}
-
-
@OnTransition(source = "REGISTERED", target = "UNCONNECTED")
-
public void unRegister() {
-
System.out.println("///");
-
System.out.println("注销事件, 已注册 -> 未连接");
-
System.out.println("///");
-
}
-
}
Spring StateMachine 让状态机结构更加层次化,我们来回顾下几个核心步骤:第一步,定义状态枚举。第二步,定义事件枚举。第三步,定义状态机配置,设置初始状态,以及状态与事件之间的关系。第四步,定义状态监听器,当状态变更时,触发方法。