服务器的状态机制,服务端指南 | 状态机设计

本文介绍了如何使用状态机模式来处理登录场景,通过定义状态枚举和行为枚举,创建了不同的状态类来实现状态转换。然后利用SpringStateMachine进行状态机配置,定义了状态与事件的关系,并通过监听器监听状态变化。这种方法简化了状态机的开发,提高了代码的可读性和可维护性。
摘要由CSDN通过智能技术生成

状态机中,每个状态有着相应的行为,随着行为的触发来切换状态。其中一种做法是使用二维数组实现状态机机制,其中横坐标表示行为,纵坐标表示状态,具体的数值则表示当前的状态。

我们以登录场景设计一个状态机。

1460000012835623

这时,我们设计一张状态机表。

1460000012835624

那么,此时它的二维数组,如下所示。

1460000012835625

此外,我们也可以通过状态模式实现一个状态机。状态模式将每一个状态封装成独立的类,具体行为会随着内部状态而改变。状态模式用类表示状态,这样我们就能通过切换类来方便地改变对象的状态,避免了冗长的条件分支语句,让系统具有更好的灵活性和可扩展性。

现在,我们定义一个状态枚举,其中包括未连接、已连接、注册中、已注册 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 依赖。

org.springframework.statemachine

spring-statemachine-core

1.2.0.RELEASE

定义一个状态枚举,其中包括未连接、已连接、注册中、已注册 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 {

}

我们需要初始化状态机的状态。其中,initial(RegStatusEnum.UNCONNECTED) 定义了初始状态是未连接状态。states(EnumSet.allOf(RegStatusEnum.class)) 定义了状态机中存在的所有状态。

@Override

public void configure(StateMachineStateConfigurer states) throws Exception {

states.withStates()

// 定义初始状态

.initial(RegStatusEnum.UNCONNECTED)

// 定义状态机状态

.states(EnumSet.allOf(RegStatusEnum.class));

}

我们需要初始化当前状态机有哪些状态事件。其中, source 指定原始状态,target 指定目标状态,event 指定触发事件。

@Override

public void configure(StateMachineTransitionConfigurer 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 让状态机结构更加层次化,我们来回顾下几个核心步骤:第一步,定义状态枚举。第二步,定义事件枚举。第三步,定义状态机配置,设置初始状态,以及状态与事件之间的关系。第四步,定义状态监听器,当状态变更时,触发方法。

(完)

更多精彩文章,尽在「服务端思维」微信公众号!

1460000012835626?w=737&h=352

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值