0

我正在尝试将弹簧状态机的 Persist 示例扩展到两种不同的状态机配置。 http://docs.spring.io/spring-statemachine/docs/1.0.0.RELEASE/reference/htmlsingle/#statemachine-examples-persist

为此我

  • 添加了新架构
  • 添加了一些测试数据
  • 复制了 Persist 和 PersistCommand 的代码,并根据我的情况对其进行了调整

到目前为止没什么大不了的。现在到配置:

  • 我删除了 StateMachineConfig(以及 @EnableStateMachine 注释)
  • 将 StateMachineConfiguration 作为 Bean 添加到 PersistHandlerConfig 中并使用 Builder
  • 复制该配置并将其调整为我的用例
  • 显然,我为我的案子上了一个像 Order 这样的课程

此外,我调整了 AbstractStateMachineCommands 类并在其中自动装配状态机列表。start/stop 和 state 方法现在启动/停止和打印每个状态机的状态(这里我不关心打印、变量)。

出现的问题是:

  • 持久化不再起作用
  • 我可以启动应用程序和两个状态机
  • 我可以使用所有 persist 和 myUseCase 调用,但没有处理持久数据。
  • 例如,如果我调用持久化进程 1,应用程序将底层 SM 的状态更改为 PROCESSING,但持久化的数据不会改变。
  • 在调试中,我能够在 LifecycleObjectSupport 中解决 getTaskExecutor 方法返回 null 的问题(而在原始示例中,bean 工厂返回 SyncTaskExecutor 的实例)。
  • 此外,似乎 TestEventListener 不再适用于任何状态机
  • 当我在任何包含状态机 bean 的配置中使用 @EnableStateMachine 时,会在 StateMachineConfiguration 的 afterPropertiesSet 处发生 NPE。

那么,谁能告诉我我在哪里搞砸了?还是 Persist 配方不适用于两个状态机?

非常感谢。

代码示例:Application.java 现在包含这些配置和实体:

    @Configuration
static class PersistHandlerConfig {

    @Bean
    public Persist persist() throws Exception {
        return new Persist(persistStateMachineHandler());
    }

    @Bean
    public PersistStateMachineHandler persistStateMachineHandler() throws Exception {
        return new PersistStateMachineHandler(persistSm());
    }

    @Bean
    public StateMachine<String, String> persistSm() throws Exception{

        Builder<String, String> builder = StateMachineBuilder.builder();
        builder.configureStates()
            .withStates()
                .initial("PLACED")
                .state("PROCESSING")
                .state("SENT")
                .state("DELIVERED");

        builder.configureTransitions()
            .withExternal()
                .source("PLACED").target("PROCESSING")
                .event("PROCESS")
                .and()
            .withExternal()
                .source("PROCESSING").target("SENT")
                .event("SEND")
                .and()
            .withExternal()
                .source("SENT").target("DELIVERED")
                .event("DELIVER");

        return builder.build();
    }
}

@Configuration
static class TicketPersistHandlerConfig {

  @Bean
  public TicketPersist ticketPersist() throws Exception {
    return new TicketPersist(ticketPersistStateMachineHandler());
  }

  @Bean
  public PersistStateMachineHandler ticketPersistStateMachineHandler() throws Exception {
    return new PersistStateMachineHandler(buildMachine());
  }

  @Bean
  public StateMachine<String, String> buildMachine() throws Exception {

       Builder<String, String> builder = StateMachineBuilder.builder();
       builder.configureStates()
           .withStates()
               .initial("PRINTED")
               .state("BOOKED")
               .state("SOLD")
               .state("DELIVERED");

       builder.configureTransitions()
           .withExternal()
               .source("PRINTED").target("BOOKED")
               .event("BOOK")
               .and()
           .withExternal()
               .source("BOOKED").target("SOLD")
               .event("SELL")
               .and()
           .withExternal()
               .source("SOLD").target("DELIVERED")
               .event("DELIVER");

       return builder.build();
   }

}

public static class Order {
    int id;
    String state;

    public Order(int id, String state) {
        this.id = id;
        this.state = state;
    }

    @Override
    public String toString() {
        return "Order [id=" + id + ", state=" + state + "]";
    }

}

public static class Ticket {
  int id;
  String state;

  public Ticket(int id, String state) {
    this.id = id;
    this.state = state;
  }

  @Override
  public String toString() {
    return "Ticket [id=" + id + ", state=" + state + "]";
  }

}

TicketPersist.java 和 TicketPersistCommands.java 与订单的相同(只是将订单替换为票证)。我通过以下方式调整了 AbstractStateMachineCommands:

@Autowired
private List<StateMachine<S, E>> stateMachines;
@CliCommand(value = "sm start", help = "Start a state machine")
public String start() {
  for (StateMachine<S, E> stateMachine : stateMachines)
  {
    stateMachine.start();
  }
    return "State machines started";
}

@CliCommand(value = "sm stop", help = "Stop a state machine")
public String stop() {
  for (StateMachine<S, E> stateMachine : stateMachines)
  {
    stateMachine.stop();
  }
    return "State machines stopped";
}
4

2 回答 2

1

@EnableStateMachine普通注释配置(使用和适配器)和手动构建器之间存在概念差异。后者实际上是在 spring 应用程序上下文之外使用的,虽然您可以将使用它创建的机器注册为 bean(就像您尝试做的那样),但很多自动配置并未应用。我可能需要在测试中更加注意这个用例(用户从注册为 的构建器返回机器@Bean)。

  1. 如果在使用 . 创建两台机器时获得 NPE @EnableStateMachine,这是我需要研究的错误。如果要创建多台机器,您应该使用指示 bean 名称适配器/javaconfig 的name字段。默认为 bean 名称并且具有多个具有相同名称的适配器将尝试配置同一台机器。对于多台机器,它会类似于.@EnableStateMachine@EnableStateMachinestateMachine@EnableStateMachine@EnableStateMachine(name = "sm1")

  2. 问题TaskExecutor有点明显,但没有一台机器不应该使用您发布的代码,因为我没有看到它在任何地方创建。通常TaskExecutor来自明确设置的实例或来自 bean 工厂(如果已设置)作为后备。在配置接口http://docs.spring.io/spring-statemachine/docs/1.0.0.RELEASE/reference/htmlsingle/#statemachine-config-commonsettings中有设置这些的钩子。

  3. 这些默认使用@EnableStateMachine的示例会自动进行上下文集成,这意味着 spring 应用程序上下文事件发布者也已注册(手动构建器中的机器不会发生这种情况),因此可以ApplicationListener按照https://github.com/spring-中的正常方式进行创建projects/spring-statemachine/blob/master/spring-statemachine-samples/src/main/java/demo/CommonConfiguration.java#L57不再有效。您还可以StateMachineListenerAdapter通过配置在机器上使用和注册它们。

我不会尝试围绕示例中的特定 shell 概念构建任何应用程序。Shell 只是用来提供从命令行与机器交互的简单方法。我看起来你可以通过为所有机器使用不同的 bean 名称来摆脱所有麻烦,即@EnableStateMachine(name = "sm1").

我将尝试根据这些用例创建一些 gh 问题。人们似乎总是以不同的方式尝试使用我们在测试中没有预料到的东西。

于 2015-11-13T08:25:37.640 回答
0

我尝试使用存储库模型工厂使用两组配置来配置状态机工厂,每组都给出一个名称。

然后在使用持久化配方时,我需要为状态机工厂传递状态机 id 的字符串参数,以获取状态机实例并将其传递给构造处理程序,并使用处理程序进行更新,如示例所示。

所以问题就在于如何使用参数配置处理程序 bean。而不是@Autowired处理程序或任何需要处理程序的东西,用beanFactory.getBean().

它仅在配方实施中对我有用。但从技术上讲,它应该与使用模型工厂的配置一起使用。

于 2018-05-01T17:43:36.380 回答