COLA下的cola-statemachine状态机

相比Spring statemachine状态机等的复杂,功能多;我们更需要常用的功能,简单使用,所以这类就显得不简洁;再看cola-statemachine相比就是小巧、无状态、简单、轻量、性能极高的状态机DSL实现,解决业务中的状态流转问题。
github:
https://github.com/alibaba/COLA/tree/master/cola-components/cola-component-statemachine

概念:
State:状态

Event:事件,状态由事件触发,引起变化

Transition:流转,表示从一个状态到另一个状态

External Transition:外部流转,两个不同状态之间的流转

Internal Transition:内部流转,同一个状态之间的流转

Condition:条件,表示是否允许到达某个状态

Action:动作,到达某个状态之后,可以做什么

StateMachine:状态机

外部过程描述:起始状态STATE1,结束状态STATE2,当发生EVENT1时执行状态转移,当满足checkCondition()时,执行doAction,执行成功则返回状态STATE2,否则返回STATE1。

public class StateMachineTest {

    static String MACHINE_ID = "TestStateMachine";

    static enum States {
        STATE1, STATE2, STATE3, STATE4
    }

    static enum Events {
        EVENT1, EVENT2, EVENT3, EVENT4, INTERNAL_EVENT
    }

    static class Context{
        String operator = "frank";
        String entityId = "123465";
    }

    @Test
    public void testExternalNormal(){
        // 第一步:生成一个状态机builder
        StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
        // 第二步:设置一个外部状态转移类型的builder,并设置from\to\on\when\perform
        builder.externalTransition()
                .from(States.STATE1)
                .to(States.STATE2)
                .on(Events.EVENT1)
                .when(checkCondition())
                .perform(doAction()); //这个action 我们可以按自己所需修改,比如这种Action<R,T> service的方法Service::method
 
        // 第三步:设置状态机的id和ready,并在StateMachineFactory中的stateMachineMap进行注册
        StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID);
        // 第四步:触发状态机
        States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context());
        Assert.assertEquals(States.STATE2, target);
    }
}

源码分析:这里只是简单介绍,整个流转的链路,因为具体的代码还是要根据自己的知识储备理解;
第一步:生成一个状态机builder
new com.alibaba.cola.statemachine.builder.StateMachineBuilderImpl

    /**
     * StateMap is the same with stateMachine, as the core of state machine is holding reference to states.
     */
    private final Map<S, State< S, E, C>> stateMap = new ConcurrentHashMap<>();
    private final StateMachineImpl<S, E, C> stateMachine = new StateMachineImpl<>(stateMap);

第二步:设置一个外部状态转移类型的builder,并设置from\to\on\when\perform

@Override
    public ExternalTransitionBuilder<S, E, C> externalTransition() {
        return new TransitionBuilderImpl<>(stateMap, TransitionType.EXTERNAL);
    }

TransitionBuilderImpl为多实现接口实现类,这个设计加上不同接口的唯一方法实现,可以让from to ,按DSL方式运行

class TransitionBuilderImpl<S,E,C> implements ExternalTransitionBuilder<S,E,C>, InternalTransitionBuilder<S,E,C>, From<S,E,C>, On<S,E,C>, To<S,E,C> {

image.png

上述构建就是一个状态流转的过程

class TransitionBuilderImpl<S,E,C> implements ExternalTransitionBuilder<S,E,C>, InternalTransitionBuilder<S,E,C>, From<S,E,C>, On<S,E,C>, To<S,E,C> {
    //状态机
    final Map<S, State<S, E, C>> stateMap;
    //原状态
    private State<S, E, C> source;
    //目标状态
    protected State<S, E, C> target;
    //该具体做事流转
    private Transition<S, E, C> transition;
     //流转类型
    final TransitionType transitionType;
...
    // 此时只是把from的state新增到stateMap中,返回结果赋给本地变量source
    @Override
    public From<S, E, C> from(S stateId) {
        source = StateHelper.getState(stateMap, stateId);
        return this;
    }
 
    // 此时只是把to的state新增到stateMap中,返回结果赋给本地变量target
    @Override
    public To<S, E, C> to(S stateId) {
        target = StateHelper.getState(stateMap, stateId);
        return this;
    }
  //所以ON是在Form和to后面执行,因为它用本流程中的source,向来源state中加入Transition
  //Transition是一个具体做事的流转,其中包含事件,目标状态和流转类型
  //看下面源码就是一个事件对应Transition
  @Override
    public On<S, E, C> on(E event) {
        transition = source.addTransition(event, target, transitionType);
        return this;
    }
//该执行流转的条件
@Override
    public When<S, E, C> when(Condition<C> condition) {
        transition.setCondition(condition);
        return this;
    }
//设置transition的action
 @Override
    public void perform(Action<S, E, C> action) {
        transition.setAction(action);
    }

...
}

第三步:
builder.build(MACHINE_ID) 此步就是状态机已构建完成,一般直接@Bean注入,使用时直接调用


public class StateMachineBuilderImpl<S, E, C> implements StateMachineBuilder<S, E, C> {
 
...
 
    private final StateMachineImpl<S, E, C> stateMachine = new StateMachineImpl<>(stateMap);
 
...
 
    // 设置状态机的id和ready,并在StateMachineFactory中的stateMachineMap进行注册
    @Override
    public StateMachine<S, E, C> build(String machineId) {
        stateMachine.setMachineId(machineId);
        stateMachine.setReady(true);
        StateMachineFactory.register(stateMachine);
        return stateMachine;
    }
 
}

第四步:States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context()); 业务具体使用时,这个方法入参是原状态和事件和action的入参,这个就按上面配置的状态机进行流转;from to on when perform

public class StateMachineImpl<S,E,C> implements StateMachine<S, E, C> {

    private String machineId;

    private final Map<S, State<S,E,C>> stateMap;

    private boolean ready;

    public StateMachineImpl(Map<S, State< S, E, C>> stateMap){
        this.stateMap = stateMap;
    }

    public S fireEvent(S sourceStateId, E event, C ctx){
        isReady();
        State sourceState = getState(sourceStateId);
        return doTransition(sourceState, event, ctx).getId();
    }

    private State<S, E, C> doTransition(State sourceState, E event, C ctx) {
        //根据原状态和事件获取流转transition
        Optional<Transition<S,E,C>> transition = sourceState.getTransition(event);
        if(transition.isPresent()){
            //ctx为执行入参
            return transition.get().transit(ctx);
        }
        Debugger.debug("There is no Transition for " + event);
        return sourceState;

Transition是具体做事的流转

/**
 * TransitionImpl。
 *
 * This should be designed to be immutable, so that there is no thread-safe risk
 *
 * @author Frank Zhang
 * @date 2020-02-07 10:32 PM
 */
public class TransitionImpl<S,E,C> implements Transition<S,E,C> {

    private State<S, E, C> source;

    private State<S, E, C> target;

    private E event;

    private Condition<C> condition;

    private Action<S,E,C> action;

    private TransitionType type = TransitionType.EXTERNAL;
    
...
 @Override
    public State<S, E, C> transit(C ctx) {
        Debugger.debug("Do transition: "+this);
        this.verify();
        if(condition == null || condition.isSatisfied(ctx)){
            if(action != null){
                action.execute(source.getId(), target.getId(), event, ctx);
            }
            return target;
        }

        Debugger.debug("Condition is not satisfied, stay at the "+source+" state ");
        return source;
    }
...
    @Override
    public final String toString() {
        return source + "-[" + event.toString() +", "+type+"]->" + target;
    }

    @Override
    public boolean equals(Object anObject){
        if(anObject instanceof Transition){
            Transition other = (Transition)anObject;
            if(this.event.equals(other.getEvent())
                    && this.source.equals(other.getSource())
                    && this.target.equals(other.getTarget())){
                return true;
            }
        }
        return false;
    }

    @Override
    public void verify() {
        if(type== TransitionType.INTERNAL && source != target) {
            throw new StateMachineException(String.format("Internal transition source state '%s' " +
                    "and target state '%s' must be same.", source, target));
        }
    }

State是整个流转中状态类

public interface State<S,E,C> extends Visitable{

    /**
     * Gets the state identifier.
     *
     * @return the state identifiers
     */
    S getId();

    /**
     * Add transition to the state
     * @param event the event of the Transition
     * @param target the target of the transition
     * @return
     */
    Transition<S,E,C> addTransition(E event, State<S, E, C> target, TransitionType transitionType);

    Optional<Transition<S,E,C>> getTransition(E event);

    Collection<Transition<S,E,C>> getTransitions();

}
public class StateImpl<S,E,C> implements State<S,E,C> {
    protected final S stateId;
    private HashMap<E, Transition<S, E,C>> transitions = new HashMap<>();

    StateImpl(S stateId){
        this.stateId = stateId;
    }

    @Override
    public Transition<S, E, C> addTransition(E event, State<S,E,C> target, TransitionType transitionType) {
        Transition<S, E, C> newTransition = new TransitionImpl<>();
        newTransition.setSource(this);
        newTransition.setTarget(target);
        newTransition.setEvent(event);
        newTransition.setType(transitionType);

        Debugger.debug("Begin to add new transition: "+ newTransition);
        verify(event, newTransition);
        transitions.put(event, newTransition);
        return newTransition;
    }
 /**
     * @param event
     * @param newTransition
     */
    private void verify(E event, Transition<S,E,C> newTransition) {
        Transition existingTransition = transitions.get(event);
        if(existingTransition != null){
            if(existingTransition.equals(newTransition)){
                throw new StateMachineException(existingTransition+" already Exist, you can not add another one");
            }
        }
    }

最后用层级展示下源码的设计:存储下来类似树形分支
第一维度:状态
第二维度:原状态 对应 事件
第三维度:事件对应具体流转


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容