需求背景与状态机
在实习过程中, 有一种ETL任务是这样的, 将sql查询出来的结果集导出到某种数据库中, 其中涉及到了sql版本管理与上下线的需求. 用户可以编辑sql, 保存不同的sql版本, 可以上线或者下线某个的sql版本, 上线时还要进行审批. 那么这个sql版本就要在上线, 下线, 审批中, 审批不通过等等状态中进行流转.
如果上线没有审批时, 状态转换就是这样的.
如果上线需要经过审批, 那么状态转换就是这样的.
机智如我, 立刻就想到了一个设计模式: 状态模式. 紧接着, 就涉及到了另一个概念: 有限状态机.
不多bb, 状态机的概念自行百度. 下面来说状态机的4个重要组成部分
- State,状态: 草稿, 已上线, 已下线这就是sql版本的状态
- Event,事件: 事件就是执行某个操作的触发条件. 比如, 从草稿到审批中需要一个上线申请这样一个事件
- Action,动作: 事件发生以后要执行动作. 上线申请这个事件需要做这么几件事, 发起bpm审批流申请上线, 修改sql版本的状态为审批中, 这个就是动作.
- Transition,变换: 从一个状态变化为另一个状态. 比如从操作变到审批中就是一个状态变换.
接下来, 用2种方式来实现一下状态机. 分别是状态模式, 状态集合实现状态机. 然后是spring状态机的使用.
状态模式实现状态机
定义状态枚举
public enum StateEnum {
TEMP(0, new TempState()),
ONLINE(1, new OnlineState()),
PROCESSING(2, new ProcessingState()),
REJECTED(3, new RejectedState()),
OFFLINE(4, new OfflineState()),
;
private final int code;
private final State state;
StateEnum(int code, State state) {
this.code = code;
this.state = state;
}
public int getCode() {
return code;
}
public State getState() {
return state;
}
@Override
public String toString() {
return "StateEnum{" +
"state=" + state +
'}';
}
}
定义事件枚举
public enum Event {
ONLINE_WITHOUT_APPLY, // 从 草稿/已下线 变成已上线
ONLINE_APPLY, // 从 草稿/已上线 变成审批中
PUBLISH, // 从审批中变成已上线
REJECT, // 从审批中变成审批不通过
CANCEL_PUBLISH; // 从已上线变为已下线
}
定义抽象状态父类
public abstract class State {
// 状态机
private StateMachine stateMachine;
public StateMachine getStateMachine() {
return stateMachine;
}
public void setStateMachine(StateMachine stateMachine) {
this.stateMachine = stateMachine;
}
// 上线不经过审批
public abstract void onlineWithoutApply();
// 上线审批
public abstract void onlineApply();
// 审批不通过
public abstract void reject();
// 发布 (审批中->已上线)
public abstract void publish();
// 取消发布 (已上线->已下线)
public abstract void cancelPublish();
}
定义具体的状态类
// 草稿
public class TempState extends State {
@Override
public void onlineWithoutApply() {
getStateMachine().online();
}
@Override
public void onlineApply() {
getStateMachine().apply();
}
@Override
public void reject() {
throw new RuntimeException("in valid: TempState -> reject");
}
@Override
public void publish() {
throw new RuntimeException("in valid: TempState -> publish");
}
@Override
public void cancelPublish() {
throw new RuntimeException("in valid: TempState -> cancelPublish");
}
}
// 已上线
public class OnlineState extends State {
@Override
public void onlineWithoutApply() {
throw new RuntimeException("in valid: OnlineState -> onlineWithoutApply");
}
@Override
public void onlineApply() {
throw new RuntimeException("in valid: OnlineState -> onlineApply");
}
@Override
public void reject() {
throw new RuntimeException("in valid: OnlineState -> reject");
}
@Override
public void publish() {
throw new RuntimeException("in valid: OnlineState -> publish");
}
@Override
public void cancelPublish() {
getStateMachine().offline();
}
}
// 审批中
public class ProcessingState extends State {
@Override
public void onlineWithoutApply() {
throw new RuntimeException("in valid: ProcessingState -> onlineWithoutApply");
}
@Override
public void onlineApply() {
throw new RuntimeException("in valid: ProcessingState -> onlineApply");
}
@Override
public void reject() {
getStateMachine().reject();
}
@Override
public void publish() {
getStateMachine().online();
}
@Override
public void cancelPublish() {
throw new RuntimeException("in valid: ProcessingState -> cancelPublish");
}
}
// 已下线
public class OfflineState extends State {
@Override
public void onlineWithoutApply() {
getStateMachine().online();
}
@Override
public void onlineApply() {
getStateMachine().apply();
}
@Override
public void reject() {
throw new RuntimeException("in valid: OfflineState -> reject");
}
@Override
public void publish() {
throw new RuntimeException("in valid: OfflineState -> publish");
}
@Override
public void cancelPublish() {
throw new RuntimeException("in valid: OfflineState -> cancelPublish");
}
}
// 审批不通过
public class RejectedState extends State {
@Override
public void onlineWithoutApply() {
throw new RuntimeException("in valid: RejectedState -> onlineWithoutApply");
}
@Override
public void onlineApply() {
throw new RuntimeException("in valid: RejectedState -> onlineApply");
}
@Override
public void reject() {
throw new RuntimeException("in valid: RejectedState -> reject");
}
@Override
public void publish() {
throw new RuntimeException("in valid: RejectedState -> publish");
}
@Override
public void cancelPublish() {
throw new RuntimeException("in valid: RejectedState -> cancelPublish");
}
}
定义状态机类
public class StateMachine {
// 当前状态
private State curState;
public StateMachine(State curState) {
setCurState(curState);
}
// 传入不同的事件, 执行不同状态的动作
public void execute(Event event) {
switch (event) {
case ONLINE_WITHOUT_APPLY:
getCurState().onlineWithoutApply();
break;
case ONLINE_APPLY:
getCurState().onlineApply();
break;
case PUBLISH:
getCurState().publish();
break;
case REJECT:
getCurState().reject();
break;
case CANCEL_PUBLISH:
getCurState().cancelPublish();
break;
default:
throw new RuntimeException("in valid event: " + event);
}
}
// 以下的4个属于action
public void online() {
System.out.println("执行sql版本上线相关的操作");
System.out.println("修改sql版本为已上线");
setCurState(StateEnum.ONLINE.getState());
}
public void apply() {
System.out.println("发起上线审批, 执行sql版本审批相关的操作");
System.out.println("修改状态为审批中");
setCurState(StateEnum.PROCESSING.getState());
}
public void reject() {
System.out.println("执行sql版本审批不通过相关的操作");
System.out.println("设置sql版本状态为审批不通过");
setCurState(StateEnum.REJECTED.getState());
}
public void offline() {
System.out.println("执行sql版本已下线相关的操作");
System.out.println("修改sql版本状态为已下线");
setCurState(StateEnum.OFFLINE.getState());
}
public State getCurState() {
return curState;
}
public void setCurState(State curState) {
this.curState = curState;
curState.setStateMachine(this);
}
}
简单测试使用
@Test
void test() {
StateMachine stateMachine = new StateMachine(StateEnum.TEMP.getState());
stateMachine.execute(Event.ONLINE_APPLY);
stateMachine.execute(Event.PUBLISH);
stateMachine.execute(Event.CANCEL_PUBLISH);
stateMachine.execute(Event.ONLINE_WITHOUT_APPLY);
}
状态模式实现状态机并不太好, 虽然去掉了大量switch case或者if else的状态, 但是不完全符合开闭原则, 新增或者删除状态需要修改状态机代码, 新增事件也需要修改很多代码.
状态集合实现状态机
定义状态枚举
public enum State {
TEMP(0, "草稿"),
ONLINE(1, "已上线"),
PROCESSING(2, "审批中"),
REJECTED(3, "审批不通过"),
OFFLINE(4, "已下线");
private final int code;
private final String desc;
State(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "State{" +
"code=" + code +
", desc='" + desc + '\'' +
'}';
}
}
定义事件
public enum Event {
ONLINE_WITHOUT_APPLY, // 从 草稿/已上线 变成已上线
ONLINE_APPLY, // 从 草稿/已上线 变成审批中
PUBLISH, // 从审批中变成已上线
REJECT, // 从审批中变成审批不通过
CANCEL_PUBLISH; // 从已上线变为已下线
}
定义每个事件需要做的动作
public interface Action {
void execute();
}
定义变换
@Data
@Builder
public class Transition {
private State curState;
private State nextState;
private Event event;
private Action action;
}
动作实现类
public class OnlineWithoutApplyAction implements Action{
@Override
public void execute() {
System.out.println("执行sql版本上线相关的操作");
System.out.println("修改sql版本为已上线");
}
}
public class OnlineApplyAction implements Action {
@Override
public void execute() {
System.out.println("发起上线审批, 执行sql版本审批相关的操作");
System.out.println("修改状态为审批中");
}
}
public class RejectAction implements Action {
@Override
public void execute() {
System.out.println("执行sql版本审批不通过相关的操作");
System.out.println("设置sql版本状态为审批不通过");
}
}
public class CancelPublishAction implements Action {
@Override
public void execute() {
System.out.println("执行sql版本下线相关的操作");
System.out.println("修改sql版本状态为已下线");
}
}
状态机类
public class StateMachine {
private final List<Transition> transitionList = Stream.of(
// 草稿状态到上线
Transition.builder()
.curState(State.TEMP)
.nextState(State.ONLINE)
.event(Event.ONLINE_WITHOUT_APPLY)
.action(new OnlineWithoutApplyAction())
.build(),
// 已下线状态到上线
Transition.builder()
.curState(State.OFFLINE)
.nextState(State.ONLINE)
.event(Event.ONLINE_WITHOUT_APPLY)
.action(new OnlineWithoutApplyAction())
.build(),
// 草稿状态到审批中
Transition.builder()
.curState(State.TEMP)
.nextState(State.PROCESSING)
.event(Event.ONLINE_APPLY)
.action(new OnlineApplyAction())
.build(),
// 已下线状态到审批中
Transition.builder()
.curState(State.OFFLINE)
.nextState(State.PROCESSING)
.event(Event.ONLINE_APPLY)
.action(new OnlineApplyAction())
.build(),
// 审批中到已上线
Transition.builder()
.curState(State.PROCESSING)
.nextState(State.ONLINE)
.event(Event.PUBLISH)
// 这里可以复用OnlineWithoutApplyAction()的逻辑
.action(new OnlineWithoutApplyAction())
.build(),
// 从审批中变成审批不通过
Transition.builder()
.curState(State.PROCESSING)
.nextState(State.REJECTED)
.event(Event.REJECT)
.action(new RejectAction())
.build(),
// 从已上线变为已下线
Transition.builder()
.curState(State.ONLINE)
.nextState(State.OFFLINE)
.event(Event.CANCEL_PUBLISH)
.action(new CancelPublishAction())
.build()
).collect(Collectors.toList());
private State state;
public StateMachine(State state) {
this.state = state;
}
public void execute(Event event) {
// 如果觉得这里次次遍历不太好, 可以用HashMap来存储, 以Event为key, 以Transition为value
Transition transition = transitionList.stream()
.filter(trans -> trans.getEvent().equals(event))
.findFirst()
.orElseThrow(() -> new RuntimeException("in valid event" + event));
this.state = transition.getNextState();
transition.getAction().execute();
}
}
使用状态机
@Test
void test() {
StateMachine stateMachine = new StateMachine(State.TEMP);
stateMachine.execute(Event.ONLINE_APPLY);
stateMachine.execute(Event.PUBLISH);
stateMachine.execute(Event.CANCEL_PUBLISH);
stateMachine.execute(Event.ONLINE_WITHOUT_APPLY);
}
从这里可以看出, 使用状态集合实现状态机, 比较符合开闭原则, 而且集合中清楚的定义了从某状态到另一状态的触发事件与动作. 可读性强, 修改方便.
spring状态机
引入依赖
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
实体类
@Data
public class SyncTaskVersion {
private Long id;
private SyncTaskVersionStates versionStates;
// ... 省略其他字段
}
状态枚举
public enum SyncTaskVersionStates {
TEMP(0, "草稿"),
APPLYING(1, "已上线"),
PROCESSING(2, "审批中"),
REJECTED(3, "审批不通过"),
OFFLINE(4, "已下线");
private final int code;
private final String desc;
SyncTaskVersionStates(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "SyncTaskVersionStates{" +
"code=" + code +
", desc='" + desc + '\'' +
'}';
// return name();
}
}
事件
public enum SyncTaskVersionEvents {
ONLINE_WITHOUT_APPLY, // 从 草稿/已上线 变成已上线
ONLINE_APPLY, // 从 草稿/已上线 变成审批中
PUBLISH, // 从审批中变成已上线
REJECT, // 从审批中变成审批不通过
CANCEL_PUBLISH; // 从已上线变为已下线
}
状态机配置 注: @EnableStateMachine需要给状态机名字, 然后后续监听时@WithStateMachine需要指定状态机名字
@Configuration
@EnableStateMachine(name = "syncTaskStateMachine")
public class StateMachineConfig extends StateMachineConfigurerAdapter<SyncTaskVersionStates, SyncTaskVersionEvents> {
/**
* 配置状态
*/
@Override
public void configure(StateMachineStateConfigurer<SyncTaskVersionStates, SyncTaskVersionEvents> states) throws Exception {
states.withStates()
.initial(SyncTaskVersionStates.TEMP)
.states(EnumSet.allOf(SyncTaskVersionStates.class));
}
/**
* 配置状态转换与事件的关系
*/
@Override
public void configure(StateMachineTransitionConfigurer<SyncTaskVersionStates, SyncTaskVersionEvents> transitions) throws Exception {
transitions
// 草稿状态到上线
.withExternal()
.source(SyncTaskVersionStates.TEMP)
.target(SyncTaskVersionStates.ONLINE)
.event(SyncTaskVersionEvents.ONLINE_WITHOUT_APPLY)
.and()
// 已下线状态到上线
.withExternal()
.source(SyncTaskVersionStates.OFFLINE)
.target(SyncTaskVersionStates.ONLINE)
.event(SyncTaskVersionEvents.ONLINE_WITHOUT_APPLY)
.and()
// 草稿状态到审批中
.withExternal()
.source(SyncTaskVersionStates.TEMP)
.target(SyncTaskVersionStates.APPLYING)
.event(SyncTaskVersionEvents.ONLINE_APPLY)
.and()
// 已下线状态到审批中
.withExternal()
.source(SyncTaskVersionStates.OFFLINE)
.target(SyncTaskVersionStates.APPLYING)
.event(SyncTaskVersionEvents.ONLINE_APPLY)
.and()
// 审批中到已上线
.withExternal()
.source(SyncTaskVersionStates.APPLYING)
.target(SyncTaskVersionStates.ONLINE)
.event(SyncTaskVersionEvents.PUBLISH)
.and()
// 从审批中变成审批不通过
.withExternal()
.source(SyncTaskVersionStates.APPLYING)
.target(SyncTaskVersionStates.REJECTED)
.event(SyncTaskVersionEvents.REJECT)
.and()
// 从已上线变为已下线
.withExternal()
.source(SyncTaskVersionStates.ONLINE)
.target(SyncTaskVersionStates.OFFLINE)
.event(SyncTaskVersionEvents.CANCEL_PUBLISH);
}
@Bean
public StateMachinePersister<SyncTaskVersionStates, SyncTaskVersionEvents, SyncTaskVersion> persister() {
return new DefaultStateMachinePersister<>(new StateMachinePersist<SyncTaskVersionStates, SyncTaskVersionEvents, SyncTaskVersion>() {
@Override
public void write(StateMachineContext<SyncTaskVersionStates, SyncTaskVersionEvents> context, SyncTaskVersion syncTaskVersion) throws Exception {
//此处并没有进行持久化操作
// 这里持久化一般是将StateMachineContext状态上下文保存到内存或者redis中, 下次读取在用, 但是实际上状态一般随着实体类保存在数据库表中, 并且状态可能会因为其他原因被修改, 所以实际开发中一般不进行持久化操作. 都是实时获取状态
}
@Override
public StateMachineContext<SyncTaskVersionStates, SyncTaskVersionEvents> read(SyncTaskVersion syncTaskVersion) throws Exception {
//此处直接获取order中的状态,其实并没有进行持久化读取操作
return new DefaultStateMachineContext<>(syncTaskVersion.getVersionStates(), null, null, null);
}
});
}
}
自定义@StatesOnTransition 注解, 里面用了@OnTransition , 所以source和target传入枚举类就能触发. 是spring官方推荐的方式.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnTransition
public @interface StatesOnTransition {
SyncTaskVersionStates[] source() default {};
SyncTaskVersionStates[] target() default {};
}
监听
@Component
@WithStateMachine(name = "syncTaskStateMachine")
public class SyncTaskVersionListener {
@StatesOnTransition(source = {SyncTaskVersionStates.TEMP, SyncTaskVersionStates.OFFLINE, SyncTaskVersionStates.APPLYING}, target = SyncTaskVersionStates.ONLINE)
public void online(@EventHeaders Map<String, Object> headers) {
SyncTaskVersion syncTaskVersion = (SyncTaskVersion) headers.get("syncTaskVersion");
System.out.println("执行sql版本上线相关的操作");
System.out.println("修改sql版本为已上线");
}
@StatesOnTransition(source = {SyncTaskVersionStates.TEMP, SyncTaskVersionStates.OFFLINE}, target = SyncTaskVersionStates.APPLYING)
public void apply(@EventHeaders Map<String, Object> headers) {
SyncTaskVersion syncTaskVersion = (SyncTaskVersion) headers.get("syncTaskVersion");
System.out.println("发起上线审批, 执行sql版本审批相关的操作");
System.out.println("修改状态为审批中");
}
@StatesOnTransition(source = SyncTaskVersionStates.APPLYING, target = SyncTaskVersionStates.REJECTED)
public void reject(@EventHeaders Map<String, Object> headers) {
SyncTaskVersion syncTaskVersion = (SyncTaskVersion) headers.get("syncTaskVersion");
System.out.println("执行sql版本审批不通过相关的操作");
System.out.println("设置sql版本状态为审批不通过");
}
@StatesOnTransition(source = SyncTaskVersionStates.ONLINE, target = SyncTaskVersionStates.OFFLINE)
public void offline(@EventHeaders Map<String, Object> headers) {
SyncTaskVersion syncTaskVersion = (SyncTaskVersion) headers.get("syncTaskVersion");
System.out.println("执行sql版本已下线相关的操作");
System.out.println("修改sql版本状态为已下线");
}
}
sevice层来使用状态机
@Service
public class SyncTaskServiceImpl implements SyncTaskService {
@Resource
private StateMachine<SyncTaskVersionStates, SyncTaskVersionEvents> syncTaskStateMachine;
@Resource
private StateMachinePersister<SyncTaskVersionStates, SyncTaskVersionEvents, SyncTaskVersion> persister;
@Override
public void onlineApply(Long id) {
// 假装从数据中获取SyncTaskVersion
SyncTaskVersion syncTaskVersion = new SyncTaskVersion();
syncTaskVersion.setId(id);
syncTaskVersion.setVersionStates(SyncTaskVersionStates.APPLYING);
boolean sendRes = sendEvent(SyncTaskVersionEvents.PUBLISH, syncTaskVersion);
if (!sendRes) {
throw new RuntimeException("上线审批启动失败");
}
}
// 省略下线, 审批通过, 等方法
// 发送事件的通用方法
private synchronized boolean sendEvent(SyncTaskVersionEvents events, SyncTaskVersion syncTaskVersion) {
boolean res = false;
try {
syncTaskStateMachine.start();
persister.restore(syncTaskStateMachine, syncTaskVersion);
Message<SyncTaskVersionEvents> message = MessageBuilder.withPayload(events).setHeader("syncTaskVersion", syncTaskVersion).build();
res = syncTaskStateMachine.sendEvent(message);
persister.persist(syncTaskStateMachine, syncTaskVersion);
} catch (Exception e) {
// 打印日志
System.out.println(e.getMessage());
} finally {
syncTaskStateMachine.stop();
}
return res;
}
}
代码详情见: github.com/RaisingPigs…