资讯专栏INFORMATION COLUMN

spring statemachine的企业可用级开发指南6-持久化

Jioby / 2977人阅读

摘要:目前为止,我们都是从状态流程的开始阶段创建一个状态机,然后一路走下去。然后就可以愉快的在里面看怎么用了发送事件持久化恢复状态机后的状态为执行完保存后,大家可以自己在客户端查看以下,是不是有内容保存进去了。

目前为止,我们都是从状态流程的开始阶段创建一个状态机,然后一路走下去。但在实际业务中,状态机可能需要在某个环节停留,等待其他业务的触发,然后再继续下面的流程。比如订单,可能在支付环节需要等待一个剁手的用户隔天再下单,所以这里面涉及到一个创建的状态机该何去何从的问题。在spring statemachine中,给出来的办法就是保存起来,到需要的时候取出来用。

1、持久化到本地内存
严格来说,你完全可以自己保存状态机,比如我就自己用map保存下来了。

public class MachineMap {

public static Map> orderMap = new HashMap>();
public static Map> formMap = new HashMap>();

}
这个代码一看就明白,我用唯一id作为key(order就是orderId,form就是formId),把状态机保存到map表里面,在实际的业务中,自由存取

@RequestMapping("/sendEvent")

void sendEvent(String machineId,String events,String id) throws Exception{
    if(machineId.equals("form")) {
        StateMachine sm = MachineMap.formMap.get(id);
        Form form = new Form();
        form.setId(id);
        if(sm == null) {
            if(events.equals("WRITE")) {
                sm = formStateMachineBuilder.build(beanFactory);
                sm.start();
                MachineMap.formMap.put(id, sm);
            }else {
                System.out.println("该表单流程尚未开始,不能做"+events+"转换");
                return;
            }
        }
        
        Message message = MessageBuilder.withPayload(FormEvents.valueOf(events)).setHeader("form", form).build();
        sm.sendEvent(message);
    }
    if(machineId.equals("order")) {
        StateMachine sm = MachineMap.orderMap.get(id);
        Order order = new Order();
        order.setId(id);
        if(sm == null) {
            if(events.equals("PAY")) {
                sm = orderStateMachineBuilder.build(beanFactory);
                sm.start();
                MachineMap.orderMap.put(id, sm);
            }else {
                System.out.println("该订单流程尚未开始,不能做"+events+"转换");
                return;
            }
            
        }
        Message message = MessageBuilder.withPayload(OrderEvents.valueOf(events)).setHeader("order", order).setHeader("test","test1").build();
        sm.sendEvent(message);
    }
}

在这段代码里面,我根据不同种类的状态机去map取和id对应的状态机,如果状态机没在map里面,就创建一个,然后塞进map表。其实写这个代码,并没有其他意思,只是为了告诉你,spring打算管理你的map表,所以出了一个接口来规范这件事:

import java.util.HashMap;
import java.util.Map;

import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.stereotype.Component;

/**

在内存中持久化状态机

*/
@Component
public class InMemoryStateMachinePersist implements StateMachinePersist {

private Map> map = new HashMap>();

@Override
public void write(StateMachineContext context, String contextObj) throws Exception {
    map.put(contextObj, context);
}

@Override
public StateMachineContext read(String contextObj) throws Exception {
    return map.get(contextObj);
}

}
这个接口非常简单,就是write和read,但他保存的对象是StateMachineContext,不是StateMachine,所以我们还不能直接用它,需要配置一下。

import org.springframework.statemachine.StateMachinePersist;
......
import org.springframework.statemachine.persist.StateMachinePersister;
@Configuration
public class PersistConfig {


@Autowired
private InMemoryStateMachinePersist inMemoryStateMachinePersist;

/**
 * 注入StateMachinePersister对象
 * 
 * @return
 */
@Bean(name="orderMemoryPersister")
public StateMachinePersister getPersister() {
    return new DefaultStateMachinePersister<>(inMemoryStateMachinePersist);
}

}
这里有个坑,InMemoryStateMachinePersist 实现的接口是

org.springframework.statemachine.StateMachinePersist
,但在PersistConfig 里面,getPersister()方法返回的值类型是StateMachinePersister类型,看着很像,但并不是上面的这个接口,而是org.springframework.statemachine.persist.StateMachinePersister接口,为了表示这个坑对我的伤害,我要强调一下两个接口:

package org.springframework.statemachine;
public interface StateMachinePersist {

void write(StateMachineContext context, T contextObj) throws Exception;
StateMachineContext read(T contextObj) throws Exception;

}
package org.springframework.statemachine.persist;
import org.springframework.statemachine.StateMachine;
public interface StateMachinePersister {

void persist(StateMachine stateMachine, T contextObj) throws Exception;
StateMachine restore(StateMachine stateMachine, T contextObj) throws Exception;

}
这两个接口名字很类似,很容易搞混,但下面的是有er的,包名也不同的。StateMachinePersister是可以直接保存StateMachine对象的,所以我们需要先实现上面的StateMachinePersist,然后再一个Config类里面转换成下面的StateMachinePersister,转换的代码就在上面的PersistConfig类里。

然后我们就能在controller里面使用了

@Resource(name="orderMemoryPersister")
private StateMachinePersister orderMemorypersister;

......

//保存状态机
@RequestMapping("/testMemoryPersister")

public void tesMemorytPersister(String id) throws Exception {
    StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
    stateMachine.start();
    
    //发送PAY事件
    stateMachine.sendEvent(OrderEvents.PAY);
    Order order = new Order();
    order.setId(id);
    
    //持久化stateMachine
    orderMemorypersister.persist(stateMachine, order.getId());

}

//取出状态机
@RequestMapping("/testMemoryPersisterRestore")

public void testMemoryRestore(String id) throws Exception {
    StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
    orderMemorypersister.restore(stateMachine, id);
    System.out.println("恢复状态机后的状态为:" + stateMachine.getState().getId());
}

2、持久化到redis
真正的业务中,一般都是多台机分布式运行,所以如果状态机只能保存在本地内容,就不能用在分布式应用上了。spring提供了一个方便的办法,使用redis解决这个问题。让我们看看怎么弄。

pom文件引入spring-statemachine-redis

    
        org.springframework.statemachine
        spring-statemachine-redis
        1.2.9.RELEASE
    

在springboot配置文件里面加上redis参数,我这是application.properties

REDIS (RedisProperties) Redis数据库索引(默认为0)

spring.redis.database=0

Redis服务器地址

spring.redis.host=localhost

Redis服务器连接端口

spring.redis.port=6379

Redis服务器连接密码(默认为空)

spring.redis.password=

连接池最大连接数(使用负值表示没有限制)

spring.redis.pool.max-active=8

连接池最大阻塞等待时间(使用负值表示没有限制)

spring.redis.pool.max-wait=-1

连接池中的最大空闲连接

spring.redis.pool.max-idle=8

连接池中的最小空闲连接

spring.redis.pool.min-idle=0

连接超时时间(毫秒)

spring.redis.timeout=0
保证配置的redis开启并能用,我们继续。回到我们熟悉的PersistConfig

@Configuration
public class PersistConfig {

@Autowired
private RedisConnectionFactory redisConnectionFactory;


/**
 * 注入RedisStateMachinePersister对象
 * 
 * @return
 */
@Bean(name = "orderRedisPersister")
public RedisStateMachinePersister redisPersister() {
    return new RedisStateMachinePersister<>(redisPersist());
}

/**
 * 通过redisConnectionFactory创建StateMachinePersist
 * 
 * @return
 */
public StateMachinePersist redisPersist() {
    RedisStateMachineContextRepository repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
    return new RepositoryStateMachinePersist<>(repository);
}

}
这个套路和上面保存到本地内存是一样一样的,先生成一个StateMachinePersist,这里是通过RedisConnectionFactory生成RepositoryStateMachinePersist,然后再包装输出StateMachinePersister,这里是RedisStateMachinePersister。然后就可以愉快的在controller里面看怎么用了

@Resource(name="orderRedisPersister")
private StateMachinePersister orderRedisPersister;

......

@RequestMapping("/testRedisPersister")

public void testRedisPersister(String id) throws Exception {
    StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
    stateMachine.start();
    Order order = new Order();
    order.setId(id);
    //发送PAY事件
    Message message = MessageBuilder.withPayload(OrderEvents.PAY).setHeader("order", order).build();
    stateMachine.sendEvent(message);
    //持久化stateMachine
    orderRedisPersister.persist(stateMachine, order.getId());
}

@RequestMapping("/testRedisPersisterRestore")
public void testRestore(String id) throws Exception {
    StateMachine stateMachine = orderStateMachineBuilder.build(beanFactory);
    orderRedisPersister.restore(stateMachine, id);
    System.out.println("恢复状态机后的状态为:" + stateMachine.getState().getId());
}

执行完redis保存statemachine后,大家可以自己在redis客户端查看以下,是不是有内容保存进去了。

3、下期预告
无论是本地内存还是分布式缓存,似乎都不是很可靠的持久化,但下一章会告诉大家,保不保存也就那么点大的事,没保存我们就再创建一个就好了。但另外一个问题就浮出水面了,状态机执行到中间的时候创建状态机,怎么在中间继续执行,之前的可都是从头开始执行的,这个问题,下一章解决。

码云配套代码地址

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74992.html

相关文章

  • spring statemachine企业可用开发指南7-伪久化和中间段状态机

    摘要:在实际的企业开发中,不可能所有情况都是从头到尾的按状态流程来,会有很多意外,比如历史数据,故障重启后的遗留流程,所以这种可以任意调节状态的才是我们需要的状态机。 1、伪持久化和中间段的状态机我们设想一个业务场景,就比如订单吧,我们一般的设计都会把订单状态存到订单表里面,其他的业务信息也都有表保存,而状态机的主要作用其实是规范整个订单业务流程的状态和事件,所以状态机要不要保存真的不重要,...

    shiyang6017 评论0 收藏0
  • spring statemachine企业可用开发指南5-传递参数message

    摘要:创建了后,状态机就可以不只是传一个,可以组合和数据内容一起发送给状态机变化的处理类了。到这里为止,状态机通过对象就和其他的业务代码做到了数据连接。 在企业开发中,数据在不同的业务间传输是最常见的工作,所以虽然我们的主架构是用的状态机,也就是从流程状态的角度来看待这个项目,但在具体业务中,每个状态的转变中会牵涉到各类业务,这些业务有些需要收到状态机变化的通知,需要把状态值传递给业务类和业...

    YacaToy 评论0 收藏0
  • spring statemachine企业可用开发指南3-多个状态机共存

    摘要:虽然多个状态机的问题解决了,但是对于实际的企业应用而言,还是有问题。这个问题就用到了状态机的持久化,我们下一章就谈谈持久化问题。 1、多个状态机的搞法在实际的企业应用中,基本不可能只有一个状态机流程在跑,比如订单,肯定是很多个订单在运行,每个订单都有自己的订单状态机流程,但上一章的例子,大家可以试一下,当执行到一个状态时,再次刷新页面,不会有任何日志出现,当一个状态流程执行到某个状态,...

    zhongmeizhi 评论0 收藏0
  • spring statemachine企业可用开发指南1-说些废话

    摘要:让我们先看下状态机的概念。下面是状态机模型中的个要素,即现态条件动作次态。因为订单和审批公文都有很多的流程,每个流程都会产生状态的变化,而且流程是这种业务的主轴,其他都是围绕这个流程和状态变化来考虑的,所以看起来蛮适合用状态机来做。 1、背景在我打算学习spring statemachine的时候,我几乎看过了所有网上的中文教程,基本上都处于浅尝辄止的阶段,有几篇讲的比较深入的,都只是...

    BakerJ 评论0 收藏0
  • spring statemachine企业可用开发指南4-多种状态机共存

    摘要:目前为止,多个状态机和多种状态机都可以在里面实现了,下一章我们来解决下状态机和实际业务间的数据传输问题,毕竟我们不是为了让状态机自个独自玩耍,和业务数据互通有无才是企业开发的正道。 在上一章的例子中,我们实现了多个状态机并存执行,不同的订单有各自的状态机运行,但只有一种状态机,这显然不能满足实际业务的要求,比如我就遇到了订单流程和公文审批流程在同一个项目的情况,所以我们这一章讲怎么让多...

    boredream 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<