首页 > 解决方案 > 使用 HTTP 调用启动 Spring 状态机

问题描述

我想使用 HTTP POST 触发从 Papyrus 加载的 Spring State Machine 模型。如果我{"state_machine":"START"}通过 HTTP 发送,我想启动预定义的状态机模型。这可能吗?或者从不同的角度来看,是否可以在 Spring State Machine 模型中创建和运行 REST API?

我正在使用带有 Spring State Machine 依赖项的 Spring Boot。Swagger 上有定义的端点来发送 HTTP POST 到服务类。

它基于这个项目,您可以在其中检查状态机/s 的所有代码,因为我基本上只想将 HTTP 调用集成到它。

考虑到第 65 行的 Service 类的 Spring Statemachine,我得到 NullPointerException,即stateMachineOne = this.stateMachineFactory.getStateMachine("machineone");. 这是完整的例外:

java.lang.NullPointerException
        at test.umlspringstatemachine.SSMService.RunSSM(SSMService.java:65)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.apache.camel.support.ObjectHelper.invokeMethodSafe(ObjectHelper.java:372)
        at org.apache.camel.component.bean.MethodInfo.invoke(MethodInfo.java:494)
        at org.apache.camel.component.bean.MethodInfo$1.doProceed(MethodInfo.java:316)
        at org.apache.camel.component.bean.MethodInfo$1.proceed(MethodInfo.java:286)
        at org.apache.camel.component.bean.AbstractBeanProcessor.process(AbstractBeanProcessor.java:146)
        at org.apache.camel.component.bean.BeanProcessor.process(BeanProcessor.java:81)
        at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler$RedeliveryTask.doRun(RedeliveryErrorHandler.java:780)
        at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler$RedeliveryTask.run(RedeliveryErrorHandler.java:688)
        at org.apache.camel.impl.engine.DefaultReactiveExecutor$Worker.schedule(DefaultReactiveExecutor.java:181)
        at org.apache.camel.impl.engine.DefaultReactiveExecutor.scheduleMain(DefaultReactiveExecutor.java:62)
        at org.apache.camel.processor.Pipeline.process(Pipeline.java:167)
        at org.apache.camel.impl.engine.CamelInternalProcessor.process(CamelInternalProcessor.java:388)
        at org.apache.camel.impl.engine.DefaultAsyncProcessorAwaitManager.process(DefaultAsyncProcessorAwaitManager.java:83)
        at org.apache.camel.support.AsyncProcessorSupport.process(AsyncProcessorSupport.java:41)
        at org.apache.camel.http.common.CamelServlet.doExecute(CamelServlet.java:319)
        at org.apache.camel.http.common.CamelServlet.doService(CamelServlet.java:214)
        at org.apache.camel.http.common.CamelServlet.service(CamelServlet.java:130)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.base/java.lang.Thread.run(Thread.java:829)

我对该项目所做的更改是我从中删除了下一部分代码,UmlSpringStateMachineApplication.java因此当我启动整个应用程序时状态机不会启动:

@Override
    public void run(String... args) throws Exception {

        synchronized (stateMachineOne) {
             stateMachineOne.getExtendedState().getVariables().put("foo", "machine1");
             stateMachineOne.start();
        }
       
        synchronized (stateMachineTwo) {
            if(stateMachineOne.isComplete()) {
                stateMachineTwo.getExtendedState().getVariables().put("foo", (String)stateMachineOne.getExtendedState().getVariables().get("foo"));
                stateMachineTwo.start();
            }
        }
        
    }

在 Service 类中,我在下面有类似这样的内容,但是我得到了空异常,例如 Spring State Machine 上下文或缺少某些内容。我想收到来自 HTTP POST 的信息stateMachineOne.start();START

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineFactory;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class SSMService {

    @Autowired
    private StateMachineFactory<String, String> stateMachineFactory;

    @Autowired
    private StateMachine<String, String> stateMachineOne;

    public SSMService() {
    }

    public void TriggerSSM(Exchange exchange) {

        Message camelMessage = exchange.getIn();

        ObjectMapper mapObject = new ObjectMapper();
        Map<String, Object> mapObj = mapObject.convertValue(camelMessage.getBody(), Map.class);

        try {
            JSONObject json = new JSONObject(mapObj);

            String state_machine= json.getString("state_machine");

                if (state_machine.equals("START")) {

                    stateMachineOne = this.stateMachineFactory.getStateMachine("machineone");
                    stateMachineOne.getExtendedState().getVariables().put("foo", "machine1");
                    stateMachineOne.start();

                }
            }

        } catch (JsonProcessingException | JSONException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

这是配置类:

import ch.qos.logback.classic.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineModelConfigurer;
import org.springframework.statemachine.config.model.StateMachineModelFactory;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.uml.UmlStateMachineModelFactory;
import org.springframework.statemachine.config.EnableStateMachineFactory;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Configuration
//@EnableStateMachineFactory
@EnableStateMachine(name="stateMachineOne")
public class StateMachineOneConfig extends StateMachineConfigurerAdapter<String, String> {

    Logger log;
    
    @Override
    public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
        config.withConfiguration().autoStartup(false).listener(listener1()).machineId("machineone");
    }

    @Override
    public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
        model.withModel().factory(modelFactory1());
    }

    @Bean
    public StateMachineModelFactory<String, String> modelFactory1() {
        return new UmlStateMachineModelFactory("classpath:papyrus/StateMachine1.uml");
    }
    
    @Bean
    public StateMachineListener<String, String> listener1() {
        return new StateMachineListenerAdapter<String, String>() {
            @Override
            public void stateChanged(State<String, String> from, State<String, String> to) {
                log.info("State 1 changed to " + to.getId());
            }
        };

    }

}

这是带有依赖项的 pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>test</groupId>
    <artifactId>camel</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <name>Camel - OpenAPI - Spring State Machine</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring.boot-version>2.3.0.RELEASE</spring.boot-version>
        <jackson.swagger.version>2.12.0</jackson.swagger.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <start-class>test.umlspringstatemachine.UmlSpringStateMachineApplication</start-class>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot BOM -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot-version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Camel BOM -->
            <dependency>
                <groupId>org.apache.camel.springboot</groupId>
                <artifactId>camel-spring-boot-dependencies</artifactId>
                <version>3.9.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-core</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-uml</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-autoconfigure</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-data-jpa</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- Camel -->
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-stream-starter</artifactId>
        </dependency>
        <dependency>
         <groupId>org.apache.camel.springboot</groupId>
         <artifactId>camel-jackson-starter</artifactId>
        </dependency>
        <!-- REST -->
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-rest-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-servlet-starter</artifactId>
        </dependency>
        <!-- Swagger -->
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-swagger-java-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel.springboot</groupId>
            <artifactId>camel-rest-swagger-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-swagger-java</artifactId>
            <version>3.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>swagger-ui</artifactId>
            <version>3.51.2</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator</artifactId>
            <version>0.42</version>
        </dependency>
        <!-- Kafka -->
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-kafka</artifactId>
            <version>3.9.0</version>
        </dependency>
        <!-- JSON -->
        <dependency>
            <groupId>org.codehaus.jettison</groupId>
            <artifactId>jettison</artifactId>
            <version>1.4.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.0</version>
                <executions>
                    <execution>
                        <id>default-prepare-agent</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>default-report</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- OWASP Dependency Check - https://www.owasp.org/index.php/OWASP_Dependency_Check -->
            <plugin>
                <groupId>org.owasp</groupId>
                <artifactId>dependency-check-maven</artifactId>
                <version>3.0.2</version>
                <configuration>
                    <format>XML</format>
                    <outputDirectory>${dependency.check.report.dir}</outputDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

标签: javaspring-bootstate-machinespring-statemachine

解决方案


我克隆了您的项目并进行了一些更改以使其正常工作,主要的可以在此处列出:

1- 向主类添加组件扫描,以便能够找到状态机配置 bean(包括包重命名)。摆脱 SSM 自定义服务并更改为使用您的自定义实现

@SpringBootApplication
@ComponentScan(basePackages = {"uml.statemachine.*"})
public class UmlSpringStateMachineApplication implements CommandLineRunner 


@Autowired
    private SSMService stateMachineService;

2-样板逻辑根据您想要的类型获取机器

@Component
public class SSMService {

    @Autowired
    @Qualifier(value="config_one")
    private StateMachineFactory<String, String> stateMachineFactoryOne;

    @Autowired
    @Qualifier(value="config_two")
    private StateMachineFactory<String, String> stateMachineFactoryTwo;

    public SSMService() {
    }

    public StateMachine<String, String> acquireMachine(String id, String version){
        if (version.equals("one")){
            return stateMachineFactoryOne.getStateMachine(id);
        }
        else{
            return stateMachineFactoryTwo.getStateMachine(id);
        }
    }

3- 由于注入问题而注释掉 StateMachineServiceConfig(您可以进一步查看)

4-替换为您的配置类中的工厂注释

@Configuration
@EnableStateMachineFactory(name = "config_one")
public class StateMachineOneConfig
        extends StateMachineConfigurerAdapter<String, String> {

5-在我的情况下,项目没有编译,所以我不得不在你的 pom 中包含一些额外的依赖项(可能没有必要)

<repositories>
        <repository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/snapshot</url>
            <snapshots><enabled>true</enabled></snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <url>http://repo.spring.io/milestone</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/snapshot</url>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <url>http://repo.spring.io/milestone</url>
        </pluginRepository>
    </pluginRepositories>

您可以在我为帮助您而创建的存储库中找到更改列表

https://github.com/Daanielvb/state-machine-papyrus-help

希望这会有所帮助


推荐阅读