Spring statemachine-实际项目应用

本文介绍如何使用Spring StateMachine实现一个复杂的权限审批流程,包括创建、审批、管理员审核等多个步骤,通过代码示例详细解释了状态机配置、事件处理及数据库持久化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 公司里一个项目真实的应用,需求如下:

image

创建的一个user或group,需要不同人员去approve,user 或group状态就会发生变化。这个很适合使用spring-statemachine来做。以建立group来说明如下

  1. 建立一个group,状态是pending Approved

  2. approve这个group,状态变成partial Approved

  3. approve这个group,如果建立的group级别是高级,状态变成Pending Admin Approve,需要公司人员(Admin级别)去approve. 如果group级别不是高级,状态直接变成Approved

  4. 公司人员(Admin级别) approve这个group,状态变成Pending Admin Approve Confirm

  5. 公司人员(Admin级别) approve这个group,状态变成Approved

  6. 第4步,如果公司人员(Admin级别) reject这个group,状态变成Pending Admin Reject Confirm

  7. 公司人员(Admin级别) approve这个group,状态变成Rejected

  8. 第7步,如果公司人员(Admin级别) reject这个group,状态变成Pending Admin Approve,重新走流程

以下为代码参考了官方sample-persist并做了更改

2.sql
CREATE TABLE `pagroup` (
  `groupId` int(11) DEFAULT NULL,
  `groupName` varchar(256) DEFAULT NULL,
  `status` int(11) DEFAULT NULL,
  `isAdvance` tinyint(1) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8

对应的Pojo

public class Group {
    private int groupId;
    private String groupName;
    private int state;
    private boolean isAdvance;
   //getter and setter...
}
3.statemahine config

spring statemachine的Configuration,java代码如下,实际上就是用java代码描述1的那个图。刚开始写有点懵,实际写下就很简单了,就是source到target的状态变化需要用什么event(事件)去trigger。需要说明的是withChoice(),这个就是说当状态从pending approved改到choice时,statemahine引擎需要知道如何去选下个状态是Approved还是pending Admin approve,这里逻辑如果group 是isadvance就是pending Admin approve否则就是approved

 
@Configuration
@EnableStateMachine
public class StateMachineConfig extends StateMachineConfigurerAdapter<Status,ActionType>{


    @Override
    public void configure(StateMachineStateConfigurer<Status,ActionType> states) throws Exception {
        states.withStates()
                .initial(Status.PENDING_APPROVAL)
                .choice(Status.CHOICE)
                .states(EnumSet.allOf(Status.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<Status,ActionType> transitions) throws Exception {
        transitions
                .withExternal()
                .source(Status.PENDING_APPROVAL).target(Status.PARTIALLY_APPROVED).event(ActionType.APPROVE)
                .and()
                .withExternal()
                .source(Status.PARTIALLY_APPROVED).target(Status.CHOICE).event(ActionType.APPROVE)
                .and()
                .withChoice()
                .source(Status.CHOICE)
                .first(Status.PENGDING_DOCUMENT_CHECK,(context)->{
                    Group group = context.getMessage().getHeaders().get("group",Group.class);
                    return group.isAdvance();
                })
                .last(Status.APPROVED)
                .and()
                .withExternal()
                .source(Status.PENGDING_DOCUMENT_CHECK).target(Status.PENDING_APPROVAL_CONFIRMATION).event(ActionType.APPROVE)
                .and()
                .withExternal()
                .source(Status.PENDING_APPROVAL_CONFIRMATION).target(Status.APPROVED).event(ActionType.APPROVE)
                .and()
                .withExternal()
                .source(Status.PENDING_APPROVAL_CONFIRMATION).target(Status.PENGDING_DOCUMENT_CHECK).event(ActionType.REJECT)
                .and()
                .withExternal()
                .source(Status.PENGDING_DOCUMENT_CHECK).target(Status.PENDING_REJECT_CONFIRMATION).event(ActionType.REJECT)
                .and()
                .withExternal()
                .source(Status.PENDING_REJECT_CONFIRMATION).target(Status.PENGDING_DOCUMENT_CHECK).event(ActionType.REJECT)
                .and()
                .withExternal()
                .source(Status.PENDING_REJECT_CONFIRMATION).target(Status.REJECTED).event(ActionType.APPROVE)
                .and()
                .withExternal()
                .source(Status.PENDING_APPROVAL).target(Status.REJECTED).event(ActionType.REJECT)
                .and()
                .withExternal()
                .source(Status.PARTIALLY_APPROVED).target(Status.REJECTED).event(ActionType.REJECT);
    }

}
4.事件(ActionType)和状态(Status)定义
public enum ActionType {
 APPROVE(1), REJECT(2);
}
public enum Status {
    PENDING_APPROVAL("status.pending_approval", 1),
    PARTIALLY_APPROVED("status.partially_approval", 2),
    APPROVED("status.approved", 3),
    REJECTED("status.rejected", 4),
    PENGDING_DOCUMENT_CHECK("status.pending_document_check", 5),
    PENDING_APPROVAL_CONFIRMATION("status.pending_approval_confirmation", 10),
    PENDING_REJECT_CONFIRMATION("status.pending_reject_confirmation", 11),
    CHOICE("spring.state.machine.choice",501);
    private String msgKey;
    private int statusCode;
    Status(String desc, int statusCode) {
        this.msgKey = desc;
        this.statusCode = statusCode;
    }
    public static Status valueOf(int statusCode) {
       Iterator<Status> iterator = EnumSet.allOf(Status.class).iterator();
       while (iterator.hasNext()){
           Status st =iterator.next();
           if(st.statusCode==statusCode)
               return st;
       }
        throw new IllegalArgumentException("invalid status code");
    }
    public String getMsgKey() {
        return msgKey;
    }
    public int getStatusCode() {
        return statusCode;
    }
}

PersistStateMachineHandler

该代码就是往statemachine里添加一个拦截器PersistingStateChangeInterceptor
拦截到所有的preStateChange事件,就通知CompositePersistStateChangeListener里面注册的listener去处理


@Component
public class PersistStateMachineHandler extends LifecycleObjectSupport {

    private final StateMachine<Status,ActionType> stateMachine;
    private final PersistingStateChangeInterceptor interceptor = new PersistingStateChangeInterceptor();
    private final CompositePersistStateChangeListener listeners = new CompositePersistStateChangeListener();
    /**
     * Instantiates a new persist state machine handler.
     *
     * @param stateMachine the state machine
     */
    @Autowired
    public PersistStateMachineHandler(StateMachine<Status,ActionType> stateMachine) {
        Assert.notNull(stateMachine, "State machine must be set");
        this.stateMachine = stateMachine;
    }

    //会被LifecycleObjectSupport父类的InitializingBean.afterPropertiesSet()里调用
    protected void onInit() throws Exception {
    
       //往stateMachine加入拦截器PersistingStateChangeInterceptor
        stateMachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction<StateMachineAccess<Status,ActionType>>() {
            public void apply(StateMachineAccess<Status,ActionType> function) {
                function.addStateMachineInterceptor(interceptor);
            }
        });

        //获取所有 PersistStateChangeListener的bean注册到CompositePersistStateChangeListener
        Map<String, PersistStateChangeListener> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors((ListableBeanFactory) this.getBeanFactory(), PersistStateChangeListener.class, true, false);
        if (!matchingBeans.isEmpty()) {
            listeners.setListeners(new ArrayList(matchingBeans.values()));
        }
    }

    /**
     * Handle event with entity.
     *
     * @param event the event
     * @param state the state
     * @return true if event was accepted
     */
    public boolean handleEventWithState(Message<ActionType> event, Status state) {
        stateMachine.stop();
        List<StateMachineAccess<Status,ActionType>> withAllRegions = stateMachine.getStateMachineAccessor().withAllRegions();
        for (StateMachineAccess<Status,ActionType> a : withAllRegions) {
            a.resetStateMachine(new DefaultStateMachineContext<Status,ActionType>(state, null, null, null));
        }
        stateMachine.start();
        return stateMachine.sendEvent(event);
    }

    /**
     * Adds the persist state change listener.
     *
     * @param listener the listener
     */
    public void addPersistStateChangeListener(PersistStateChangeListener listener) {
        listeners.register(listener);
    }

    /**
     * The listener interface for receiving persistStateChange events.
     * The class that is interested in processing a persistStateChange
     * event implements this interface, and the object created
     * with that class is registered with a component using the
     * component's <code>addPersistStateChangeListener</code> method. When
     * the persistStateChange event occurs, that object's appropriate
     * method is invoked.
     */
    public interface PersistStateChangeListener {
        /**
         * Called when state needs to be persisted.
         *
         * @param state        the state
         * @param message      the message
         * @param transition   the transition
         * @param stateMachine the state machine
         */
        void onPersist(State<Status,ActionType> state, Message<ActionType> message, Transition<Status,ActionType> transition,
                       StateMachine<Status,ActionType> stateMachine);
    }

    private class PersistingStateChangeInterceptor extends StateMachineInterceptorAdapter<Status,ActionType> {
        @Override
        public void preStateChange(State<Status, ActionType> state, Message<ActionType> message, Transition<Status, ActionType> transition, StateMachine<Status, ActionType> stateMachine) {
            listeners.onPersist(state,message,transition,stateMachine);
        }
    }

    private class CompositePersistStateChangeListener extends AbstractCompositeListener<PersistStateChangeListener> implements
            PersistStateChangeListener {
        public void onPersist(State<Status,ActionType> state, Message<ActionType> message,
                              Transition<Status,ActionType> transition, StateMachine<Status,ActionType> stateMachine) {
            for (Iterator<PersistStateChangeListener> iterator = getListeners().reverse(); iterator.hasNext(); ) {
                PersistStateChangeListener listener = iterator.next();
                listener.onPersist(state, message, transition, stateMachine);
            }
        }
    }
}

GroupController


@RestController
public class GroupController {

    @Autowired
    GroupService groupService;

    @RequestMapping("/group/list")
    public List<Group> list(){
        return groupService.listAll();
    }

    @PostMapping("/group/create")
    public boolean  create(@RequestBody Group group){
        groupService.create(group);
        return true;
    }

   //web入口,处理某个group{id}的某个事件,例如group/2/APPROVE就是对group id 为2  做approve
    @RequestMapping("/group/{id}/{event}")
    public boolean handle(@PathVariable("id")Integer id,@PathVariable("event") String event){
        return groupService.handleAction(id,event);
    }

}

GroupService

@org.springframework.stereotype.Service
public class GroupService {

    @Autowired
    private PersistStateMachineHandler handler;
    @Autowired
    private GroupRepository repository;

    public boolean handleAction(int groupId, String event) {
        Group group = repository.findGroupById(groupId);
        //发送事件去触发状态机
        return handler.handleEventWithState(MessageBuilder.withPayload(ActionType.valueOf(event))
                .setHeader("group", group).build(), Status.valueOf(group.getState()));
    }

    public void create(Group group) {
        repository.create(group);
    }
    public List listAll() {
        return repository.listAll();
    }
}

GroupRepoository 相当于Dao

@Repository
public class GroupRepository {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void create(Group group){
        jdbcTemplate.update("insert into pagroup(groupId,groupName,status) values (?,?,?)",
                group.getGroupId(),
                group.getGroupName(),
                Status.PENDING_APPROVAL.getStatusCode());
    }

    public List listAll() {
        List list  = jdbcTemplate.query("select groupId,groupName,status,isAdvance from pagroup",rowMapper());
        return list;
    }

    public Group findGroupById(int groupId) {
        Group group = jdbcTemplate.queryForObject("select groupId, groupName,status,isAdvance from pagroup where groupId = ?", new Object[]{groupId},rowMapper());
        return group;
    }
    private RowMapper<Group> rowMapper(){
        return  new RowMapper<Group>() {
            public Group mapRow(ResultSet rs, int rowNum) throws SQLException {
                Group group = new Group(rs.getInt("groupId"), rs.getString("groupName"));
                group.setState(rs.getInt("status"));
                group.setAdvance(rs.getBoolean("isAdvance"));
                return group;
            }
        };
    }
}

GroupPersistStateChangeListener 监听到状态机状态更改,就更新数据库里的对应字段

@Component
public class GroupPersistStateChangeListener  implements PersistStateMachineHandler.PersistStateChangeListener {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public GroupPersistStateChangeListener(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void onPersist(State<Status, ActionType> state, Message<ActionType> message, Transition<Status, ActionType> transition, StateMachine<Status, ActionType> stateMachine) {
        if (message != null && message.getHeaders().containsKey("group")) {
            Group group = message.getHeaders().get("group", Group.class);
            jdbcTemplate.update("update pagroup set status = ? where groupId = ?", state.getId().getStatusCode(), group.getGroupId());
        }
    }
}

启动Spring工程

@SpringBootApplication
public class BootStrap {

    public static void main(String[] args) {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(BootStrap.class);
        builder.run(args);
    }
}

代码在github

<think>好的,用户让我介绍一下如何使用Spring Statemachine编写一个简单的例子。我需要先确保自己对这个框架的理解是正确的。Spring Statemachine是一个用于创建状态机的框架,可以帮助管理复杂的状态转换逻辑。用户可能是一个刚开始学习这个框架的开发者,想要一个简单的入门示例。 首先,我应该考虑一个常见的状态机应用场景,比如订单流程。订单可能有几个基本状态,比如待支付、已支付、已发货、已完成,以及取消状态。这样的例子比较直观,用户容易理解。接下来,需要确定状态和事件之间的转换关系。例如,支付事件会将订单从待支付转为已支付,发货事件会从已支付转为已发货,收货事件则转到已完成,而取消事件可能在待支付或已支付时触发,转到已取消状态。 然后,我需要考虑如何用Spring Statemachine来实现这个状态机。步骤大概包括添加依赖、定义状态和事件枚举、配置状态机配置类,然后编写测试或应用逻辑来触发事件。用户可能需要具体的代码示例,所以每个部分都需要详细说明,比如状态枚举、事件枚举、配置类的编写,以及如何构建状态机并发送事件。 同时,用户可能对某些配置或注解不太熟悉,比如@EnableStateMachine,或者状态转换的配置方法。需要解释这些部分的作用,比如使用StateMachineConfigurerAdapter来配置状态和转换,使用withExternal()来定义外部事件触发的转换。 另外,可能要注意的是,Spring Statemachine的版本问题。不同版本的API可能略有不同,所以需要确认示例代码适用的版本,比如2.x或3.x。在依赖部分,应该给出最新的版本号,或者提示用户根据实际情况选择版本。 在编写示例时,应该保持代码简洁,避免引入不必要的复杂性。例如,状态机配置类中只需要定义状态、事件和转换,不需要涉及持久化或分布式状态机的配置。测试部分可以用简单的JUnit测试,展示状态机的初始状态,发送事件,然后验证状态是否正确转换。 可能还需要提到一些常见的问题,比如状态机需要启动后才能处理事件,所以在测试中要先调用start()方法。此外,状态机的实例化通常是通过Spring的上下文,但在简单示例中也可以直接使用构建器。 最后,总结一下整个流程,确保用户能够按照步骤一步步实现,并且理解每个部分的作用。如果用户有更复杂的需求,可以建议他们查阅官方文档或进一步的学习资源。</think>以下是一个使用Spring Statemachine实现订单状态机的简单示例: ### 1. 添加依赖(Maven) ```xml <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-core</artifactId> <version>3.2.0</version> </dependency> ``` ### 2. 定义状态和事件枚举 ```java // 订单状态 public enum OrderStates { UNPAID, // 待支付 PAID, // 已支付 SHIPPED, // 已发货 COMPLETED, // 已完成 CANCELLED // 已取消 } // 订单事件 public enum OrderEvents { PAY, // 支付 SHIP, // 发货 RECEIVE, // 确认收货 CANCEL // 取消 } ``` ### 3. 配置状态机 ```java @Configuration @EnableStateMachine public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> { @Override public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception { states .withStates() .initial(OrderStates.UNPAID) // 初始状态 .states(EnumSet.allOf(OrderStates.class)); } @Override public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception { transitions // 支付事件:UNPAID -> PAID .withExternal() .source(OrderStates.UNPAID) .target(OrderStates.PAID) .event(OrderEvents.PAY) // 发货事件:PAID -> SHIPPED .and() .withExternal() .source(OrderStates.PAID) .target(OrderStates.SHIPPED) .event(OrderEvents.SHIP) // 收货事件:SHIPPED -> COMPLETED .and() .withExternal() .source(OrderStates.SHIPPED) .target(OrderStates.COMPLETED) .event(OrderEvents.RECEIVE) // 取消事件(仅允许在UNPAID/PAID状态取消) .and() .withExternal() .source(OrderStates.UNPAID) .target(OrderStates.CANCELLED) .event(OrderEvents.CANCEL) .and() .withExternal() .source(OrderStates.PAID) .target(OrderStates.CANCELLED) .event(OrderEvents.CANCEL); } } ``` ### 4. 使用示例 ```java public class OrderStateMachineDemo { public static void main(String[] args) { // 创建状态机 StateMachine<OrderStates, OrderEvents> stateMachine = new StateMachineBuilder.Builder<OrderStates, OrderEvents>() .configureConfiguration() .withConfiguration() .autoStartup(true) .and() .configureModel() .withModel() .model(new DefaultStateMachineModel<>(OrderStates.UNPAID)) .and() .build(); stateMachine.start(); // 发送事件 System.out.println("当前状态:" + stateMachine.getState().getId()); // UNPAID stateMachine.sendEvent(OrderEvents.PAY); System.out.println("支付后状态:" + stateMachine.getState().getId()); // PAID stateMachine.sendEvent(OrderEvents.SHIP); System.out.println("发货后状态:" + stateMachine.getState().getId()); // SHIPPED stateMachine.sendEvent(OrderEvents.RECEIVE); System.out.println("收货后状态:" + stateMachine.getState().getId()); // COMPLETED } } ``` ### 关键点说明: 1. **状态定义**:通过枚举明确业务状态 2. **事件定义**:使用枚举表示触发状态转换的操作 3. **转换配置**: - `withExternal()` 表示外部事件触发的转换 - `source()` 和 `target()` 定义转换路径 - `event()` 指定触发事件 4. **状态机使用**: - 通过sendEvent()发送事件 - 通过getState()获取当前状态 ### 进阶功能建议: 1. 添加状态进入/退出的监听器 2. 使用Guard实现条件判断 3. 持久化状态机状态 4. 分布式状态机支持 该示例展示了最基本的订单状态流转逻辑,实际项目中可以根据业务需求添加更复杂的转换条件和状态处理逻辑。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值