SpringBoot——LiteFlow引擎框架

优质博文:IT-BLOG-CN

一、LiteFlow 简介

LiteFlow是一个轻量且强大的国产规则引擎框架,可用于复杂的组件化业务的编排领域。帮助系统变得更加丝滑且灵活。利用LiteFlow,你可以将瀑布流式的代码,转变成以组件为核心概念的代码结构,这种结构的好处是可以任意编排,组件与组件之间是解耦的,组件可以用脚本来定义,组件之间的流转全靠规则来驱动。LiteFlow拥有开源规则引擎最为简单的DSL语法。 LiteFlow官网

LiteFlow2020年正式开源,2021年获得开源中国年度最受欢迎开源软件殊荣。于2022年获得Gitee最有价值开源项目GVP荣誉。是一个正处在高速发展中的开源项目。LiteFlow是一个由社区驱动的项目,拥有一个2500多人的使用者社区。虽然相比AcitivitiFlowable来说,LiteFlow的知名度要低得多,功能也没有这些知名成熟引擎那么强大,但LiteFlow还是有诸多优点,能够满足你绝大部分的场景。这些优点包括:
【1】规则多样化: 规则支持xmljsonyml三种规则文件写法方式。
【2】使用便捷: 引几个jar包、实现几个接口、写一个流程编排文件,就能运行。
【3】编排丰富: 支持串行、并行、选择、循环、异常处理、嵌套等各种编排方式。
【4】事件监听: 支持事件触发和状态变化监听,可以方便地扩展和定制工作流处理逻辑。
【5】异步超时: 支持异步执行和超时控制,可以提高系统的并发处理能力和稳定性。
【6】支持脚本: 支持各种主流脚本语言。
【7】配置源丰富: 支持将流程定义放到ZK/DB/Etcd/Nacos/Redis/Apollo和自定义扩展等。相当于可以实现动态配置更改。
【8】定制化: 高度可定制化,用户可以根据自己的需求自由扩展和定制LiteFlow的各种组件和功能。
【9】支持众多脚本语言: LiteFlow的脚本组件,支持众多脚本语言Groovy/JavaScript/QLExpress/Python/Lua/Aviator/Java,完全和Java打通,你可以用脚本来实现任何逻辑。
【10】优雅热刷新机制: 规则变化,无需重启您的应用,即时改变应用的规则。高并发下不会因为刷新规则导致正在执行的规则有任何错乱。
【11】支持广泛: 不管你的项目是不是基于SpringbootSpring还是任何其他java框架构建,LiteFlow都能游刃有余。
【12】上下文隔离机制: 可靠的上下文隔离机制,你无需担心高并发情况下的数据串流。
【13】性能卓越: 框架本身几乎不消耗额外性能,性能取决你的组件执行效率。
【14】自带简单监控: 框架内自带一个命令行的监控,能够知道每个组件的运行耗时排行。

适合使用的这项技术的系统

在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,这些核心业务业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取,内部其他系统RPC调用等等。时间一长,项目几经易手,维护成本就会越来越高。各种硬代码判断,分支条件越来越多。代码的抽象,复用率也越来越低,各个模块之间的耦合度很高。一小段逻辑的变动,会影响到其他模块,需要进行完整回归测试来验证。如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更业务流程,几乎很难实现。

如何打破僵局?LiteFlow为解耦逻辑而生,为编排而生,在使用LiteFlow之后,你会发现打造一个低耦合,灵活的系统会变得易如反掌!

二、LiteFlow 原理

如果你要对复杂业务逻辑进行新写或者重构,用LiteFlow最合适不过。它是一个编排式的规则引擎框架,组件编排,帮助解耦业务代码,让每一个业务片段都是一个组件。

LiteFlow的核心是“流程即代码”,即将业务流程和代码结构紧密耦合在一起。LiteFlow采用基于XML文件的流程定义方式,通过定义流程节点和连线来描述整个工作流程。每个流程节点都对应着Java代码中的一个方法,而连线则对应着方法之间的调用关系。这样一来,我们就可以非常直观地看到整个业务流程的处理过程,而且在修改流程时也更加方便快捷。

组件可实时热更替,也可以给编排好的逻辑流里实时增加一个组件,从而改变你的业务逻辑。

编排语法强大到可以编排出任何你想要的逻辑流程例如:

三、使用场景

LiteFlow适用于哪些场景: LiteFlow适用于拥有复杂逻辑的业务,比如说价格引擎,下单流程等,这些业务往往都拥有很多步骤,这些步骤完全可以按照业务粒度拆分成一个个独立的组件,进行装配复用变更。使用LiteFlow,你会得到一个灵活度高,扩展性很强的系统。因为组件之间相互独立,也可以避免改一处而动全身的这样的风险。

LiteFlow不适用于哪些场景: LiteFlow不适合角色任务之间的流转,类似于审批流,A审批完应该是B审批,然后再流转到C角色。这里申明下,LiteFlow只做基于逻辑的流转,而不做基于角色任务的流转。如果你想做基于角色任务的流转,推荐使用flowableactiviti这2个框架。

四、JDK支持情况

LiteFlow要求的最低的JDK版本为8,支持JDK8~JDK17所有的版本。如果你使用JDK11以上,确保LiteFlow的版本为v2.10.6及其以上版本。因为LiteFlowv2.10.6开始,对JDK11JDK17进行了详细的用例测试,通过了全部的900多个测试用例。而在v2.10.6以下版本,在JDK11以上是未经过测试用例保障的。特别需要注意的是,如果你使用JDK11及其以上的版本,请确保jvm参数加上以下参数:

--add-opens java.base/sun.reflect.annotation=ALL-UNNAMED

五、Springboot 整合流程

LiteFlow要求的Springboot的最低的版本是2.0。支持的范围是Springboot 2.X ~ Springboot 3.X。如果你使用了最新的Springboot 3.X,相应的JDK版本也要切换为JDK17

LiteFlow提供了liteflow-spring-boot-starter依赖包,提供自动装配功能

<dependency>
    <groupId>com.yomahub</groupId>
    <artifactId>liteflow-spring-boot-starter</artifactId>
    <version>2.11.3</version>
</dependency>

组件定义

在依赖了以上jar包后,你需要定义并实现一些组件,确保SpringBoot会扫描到这些组件并注册进上下文。

@Component("a")
public class ACmp extends NodeComponent {

	@Override
	public void process() {
		//do your business
	}
}

以此类推再分别定义b,c组件:

@Component("b")
public class BCmp extends NodeComponent {

	@Override
	public void process() {
		//do your business
	}
}


@Component("c")
public class CCmp extends NodeComponent {

	@Override
	public void process() {
		//do your business
	}
}

SpringBoot配置文件

然后,在你的SpringBootapplication.properties或者application.yml里添加配置(这里以yaml为例,properties也是一样的)

liteflow:
  #规则文件路径
  rule-source: config/flow.el.xml
  #-----------------以下非必须-----------------
  #liteflow是否开启,默认为true
  enable: true
  #liteflow的banner打印是否开启,默认为true
  print-banner: true
  #zkNode的节点,只有使用zk作为配置源的时候才起作用,默认为/lite-flow/flow
  zk-node: /lite-flow/flow
  #上下文的最大数量槽,默认值为1024
  slot-size: 1024
  #FlowExecutor的execute2Future的线程数,默认为64
  main-executor-works: 64
  #FlowExecutor的execute2Future的自定义线程池Builder,LiteFlow提供了默认的Builder
  main-executor-class: com.yomahub.liteflow.thread.LiteFlowDefaultMainExecutorBuilder
  #自定义请求ID的生成类,LiteFlow提供了默认的生成类
  request-id-generator-class: com.yomahub.liteflow.flow.id.DefaultRequestIdGenerator
  #并行节点的线程池Builder,LiteFlow提供了默认的Builder
  thread-executor-class: com.yomahub.liteflow.thread.LiteFlowDefaultWhenExecutorBuilder
  #异步线程最长的等待时间(只用于when),默认值为15000
  when-max-wait-time: 15000
  #异步线程最长的等待时间(只用于when),默认值为MILLISECONDS,毫秒
  when-max-wait-time-unit: MILLISECONDS
  #when节点全局异步线程池最大线程数,默认为16
  when-max-workers: 16
  #并行循环子项线程池最大线程数,默认为16
  parallelLoop-max-workers: 16
  #并行循环子项线程池等待队列数,默认为512
  parallelLoop-queue-limit: 512
  #并行循环子项的线程池Builder,LiteFlow提供了默认的Builder
  parallelLoop-executor-class: com.yomahub.liteflow.thread.LiteFlowDefaultParallelLoopExecutorBuilder
  #when节点全局异步线程池等待队列数,默认为512
  when-queue-limit: 512
  #是否在启动的时候就解析规则,默认为true
  parse-on-start: true
  #全局重试次数,默认为0
  retry-count: 0
  #是否支持不同类型的加载方式混用,默认为false
  support-multiple-type: false
  #全局默认节点执行器
  node-executor-class: com.yomahub.liteflow.flow.executor.DefaultNodeExecutor
  #是否打印执行中过程中的日志,默认为true
  print-execution-log: true
  #是否开启本地文件监听,默认为false
  enable-monitor-file: false
  #简易监控配置选项
  monitor:
    #监控是否开启,默认不开启
    enable-log: false
    #监控队列存储大小,默认值为200
    queue-limit: 200
    #监控一开始延迟多少执行,默认值为300000毫秒,也就是5分钟
    delay: 300000
    #监控日志打印每过多少时间执行一次,默认值为300000毫秒,也就是5分钟
    period: 300000

规则文件的定义

同时,你得在resources下的config/flow.el.xml中定义规则:SpringBoot在启动时会自动装载规则文件。

<?xml version="1.0" encoding="UTF-8"?>
<flow>
    <chain name="chain1">
        THEN(a, b, c);
    </chain>
</flow>

执行

声明启动类:

@SpringBootApplication
//把你定义的组件扫入Spring上下文中
@ComponentScan({"com.xxx.xxx.cmp"})
public class LiteflowExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(LiteflowExampleApplication.class, args);
    }
}

然后你就可以在Springboot任意被Spring托管的类中拿到flowExecutor,进行执行链路:这个DefaultContext是默认的上下文,用户可以用最自己的任意Bean当做上下文传入,如果需要传入自己的上下文,则需要传用户BeanClass属性

@Component
public class YourClass{
    
    @Resource
    private FlowExecutor flowExecutor;
    
    public void testConfig(){
        LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
    }
}

六、数据上下文

在执行器执行流程时会分配数据上下文实例给这个请求。不同请求的数据上下文实例是完全隔离的。里面存放着此请求所有的用户数据。不同的组件之间是不传递参数的,所有的数据交互都是通过这个数据上下文来实现的。数据上下文这个概念在LiteFlow框架中非常重要,你所有的业务数据都是放在数据上下文中。要做到可编排,一定是消除每个组件差异性的。如果每个组件出参入参都不一致,那就没法编排了。

LiteFlow对此有独特的设计理念,平时我们写瀑布流的程序时,A调用B,那A一定要把B所需要的参数传递给B,而在LiteFlow框架体系中,每个组件的定义中是不需要接受参数的,也无任何返回的。每个组件只需要从数据上下文中获取自己关心的数据即可,而不用关心此数据是由谁提供的,同样的,每个组件也只要把自己执行所产生的结果数据放到数据上下文中即可,也不用关心此数据到底是提供给谁用的。这样一来,就从数据层面一定程度的解耦了。从而达到可编排的目的。关于这个理念,也在LiteFlow简介中的设计原则有提到过,给了一个形象的例子,大家可以再去看看。

一旦在数据上下文中放入数据,整个链路中的任一节点都是可以取到的。

默认上下文

LiteFlow提供了一个默认的数据上下文的实现:DefaultContext。这个默认的实现其实里面主要存储数据的容器就是一个Map。你可以通过DefaultContext中的setData方法放入数据,通过getData方法获得数据。

::: warning
DefaultContext虽然可以用,但是在实际业务中,用这个会存在大量的弱类型,存取数据的时候都要进行强转,颇为不方便。所以官方建议你自己去实现自己的数据上下文。
:::

自定义上下文

在一个流程中,总会有一些初始的参数,比如订单号,用户Id等等一些的初始参数。这时候需要通过以下方法的第二个参数传入:

//参数为流程ID,无初始流程入参,上下文类型为默认的DefaultContext
public LiteflowResponse execute2Resp(String chainId)
//第一个参数为流程ID,第二个参数为流程入参。上下文类型为默认的DefaultContext
public LiteflowResponse execute2Resp(String chainId, Object param);
//第一个参数为流程ID,第二个参数为流程入参,后面可以传入多个上下文class
public LiteflowResponse execute2Resp(String chainId, Object param, Class<?>... contextBeanClazzArray)
//第一个参数为流程ID,第二个参数为流程入参,后面可以传入多个上下文的Bean
public LiteflowResponse execute2Resp(String chainId, Object param, Object... contextBeanArray)

你可以用你自己的任意的Bean当做上下文进行传入。LiteFlow对上下文的Bean没有任何要求。自己定义的上下文实质上就是一个最简单的值对象,自己定义的上下文因为是强类型,更加贴合业务。你可以像这样进行传入:

LiteflowResponse response = flowExecutor.execute2Resp("chain1", 流程初始参数, CustomContext.class);

传入之后,LiteFlow会在调用时进行初始化,给这个上下文分配唯一的实例。你在组件之中可以这样去获得这个上下文实例:

@LiteflowComponent("yourCmpId")
public class YourCmp extends NodeComponent {

	@Override
	public void process() {
		CustomContext context = this.getContextBean(CustomContext.class);
		//或者你也可以用这个方法去获取上下文实例,和上面是等价的
		//CustomContext context = this.getFirstContextBean();
		...
	}
}

多上下文

LiteFlow在新版本中支持了多上下文,在执行的时候同时初始化你传入的多个上下文。在组件里也可以根据class类型很方便的拿到。你可以像这样进行传入:

LiteflowResponse response = flowExecutor.execute2Resp("chain1", 流程初始参数, OrderContext.class, UserContext.class, SignContext.class);

在组件之中可以这样去获得这个上下文实例:

@LiteflowComponent("yourCmpId")
public class YourCmp extends NodeComponent {

	@Override
	public void process() {
		OrderContext orderContext = this.getContextBean(OrderContext.class);
		UserContext userContext = this.getContextBean(UserContext.class);
		SignContext signContext = this.getContextBean(SignContext.class);
		
		//如果你只想获取第一个上下文,第一个上下文是OrderContext,那么也可以用这个方法
		//OrderContext orderContext = this.getFirstContextBean();
		...
	}
}

用初始化好的上下文传入

LiteFlow2.8.4版本开始,允许用户传入一个或多个已经初始化好的bean作为上下文,而不是传入class对象。在拿到FlowExecutor之后,你可以像如下一样,传入已经初始化好的bean作为上下文(当然也支持多上下文,这里只演示单上下文):

OrderContext orderContext = new OrderContext();
orderContext.setOrderNo("SO11223344");
LiteflowResponse response = flowExecutor.execute2Resp("chain1", null, orderContext);

::: warning
框架并不支持上下文beanclass混传,你要么都传bean,要么都传class
:::

七、异步Future

public Future<LiteflowResponse> execute2Future(String chainId, Object param, Class<?>... contextBeanClazzArray)

如果调用这个方法,那就是无阻塞的,想要拿到response,请用得到的future.get()就可以了。同时,主执行器在这个模式下的线程数和线程池也可以自定义,具体配置如下,LiteFlow已经设置了预设值,你也可自己定义。

liteflow.main-executor-works=64
liteflow.main-executor-class=com.yomahub.liteflow.thread.LiteFlowDefaultMainExecutorBuilder

如果你定义了自定义线程池,你需新建一个类,然后实现ExecutorBuilder接口:

public class CustomThreadBuilder implements ExecutorBuilder {
    @Override
    public ExecutorService buildExecutor() {
        return Executors.newCachedThreadPool();
    }
}

八、规则写法

串行编排

如果你要依次执行a,b,c,d四个组件,你可以用THEN关键字,需要注意的是,THEN必须大写。

<chain name="chain1">
    THEN(a, b, c, d);
</chain>

并行编排

如果你要并行执行a,b,c三个组件,你可以用WHEN关键字,需要注意的是,WHEN必须大写。

<chain name="chain1">
    WHEN(a, b, c);
</chain>

和串行嵌套起来:让我们把THEN和WHEN结合起来用,看一个示例:b,c,d默认并行都执行完毕后,才会执行e

<chain name="chain1">
    THEN(
        a,
        WHEN(b, c, d),
        e
    );
</chain>

上面的示例应该很好理解吧,那么再看一个示例:

<chain name="chain1">
    THEN(
        a,
        WHEN(b, THEN(c, d)),
        e
    );
</chain>

忽略错误: WHEN关键字提供了一个子关键字ignoreError(默认为`false``)来提供忽略错误的特性,用法如下:

<chain name="chain1">
    THEN(
        a,
        WHEN(b, c, d).ignoreError(true),
        e
    );
</chain>

b,c,d中任一一个节点有异常,那么最终e仍旧会被执行。

任一节点先执行完则忽略其他: WHEN关键字提供了一个子关键字any(默认为false)用来提供并行流程中,任一条分支先执行完即忽略其他分支,继续执行的特性。用法如下:假设e节点先执行完,那么不管其他分支是否执行完,会立马执行节点f

<chain name="chain1">
    THEN(
        a,
        WHEN(b, THEN(c, d), e).any(true),
        f
    );
</chain>

指定任意节点先执行完则忽略其他: LiteFlowv2.11.1开始,支持了并行编排中指定节点的执行则忽略其他,WHEN关键字新增子关键字must(不可为空),可用于指定需等待执行的任意节点,可以为1个或者多个,若指定的所有节点率先完成,则继续往下执行,忽略同级别的其他任务,用法如下:must指定了b,c,则b,c是一定会被执行完毕了,如果b,c执行完毕了后d还未执行完,则忽略,直接执行下一个组件f

<chain name="chain1">
    THEN(
        a,
        WHEN(b, c, d).must(b, c),
        f
    );
</chain>

以上是单节点的用法,must还可以指定一个或多个表达式。比如:WHEN里有一个嵌套的THEN,如果需要指定这个表达式,则需要给这个表达式设置一个idmust里需要指定这个id,需要注意的是,must里指定id,需要用引号括起来。

<chain name="chain1">
    THEN(
        a,
        WHEN(b, THEN(c, d).id("t1"), e).must(b, "t1"),
        f
    );
</chain>

开启WHEN线程池隔离:
目前liteflow设计里when线程池,如果你不单独设置自定义线程池,那么就会用默认的线程池。而这个线程池,是所有的when共同一个。LiteFlow从2.11.1开始,提供一个liteflow.when-thread-pool-isolate参数,默认为false,如果设为true,则会开启WHEN的线程池隔离机制,这意味着每一个when都会有单独的线程池。这个特性对于运行复杂的嵌套when时是可以提升运行速度的且规避掉一些锁的问题。

你可以如下配置来开启:

liteflow.when-thread-pool-isolate=true

选择编排

我们在写业务逻辑的时候,通常会碰到选择性问题,即,如果返回结果1,则进入A流程,如果返回结果2,则进入B流程,如果返回结果3,则进入C流程。在有些流程定义中也被定义为排他网关。这个通过LiteFLow的表达式也非常容易实现,你可以用SWITCH...TO的组合关键字,注意的是SWITCH必须大写,to大小写均可。

如果,根据组件a,来选择执行b,c,d中的一个,你可以如下声明:

在这里插入图片描述

@LiteflowComponent("a")
public class ACmp extends NodeSwitchComponent {

    @Override
    public String processSwitch() throws Exception {
        System.out.println("Acomp executed!");
        return "c";
    }
}

DEFAULT关键字: LiteFlow2.9.5开始,对选择编排新增了一个DEFAULT关键字。用法为SWITCH...TO...DEFAULT。比如如下表达式:

<chain name="chain1">
    SWITCH(x).TO(a, b, c).DEFAULT(y);
</chain>

如上表达式的x如果返回非a,b,c中的一个,则默认选择到y。当然DEFAULT里面也可以是一个表达式。

选择编排中的id语法: 接下来展示一个SWITCH中套THENWHEN的例子。如果你阅读过选择组件这一章,就应该知道,LiteFlow通过选择组件的返回来确定该选择什么。那么如果SWITCH中套一个THEN,那么选择组件如果要选择这个THEN应该返回什么呢?LiteFlow中规定,每个表达式都可以有一个id值,你可以设置id值来设置一个表达式的id值。然后在选择组件里返回这个id即可。用法如下:

<chain name="chain1">
    THEN(
        a,
        SWITCH(b).to(
            c, 
            THEN(d, e).id("t1")
        ),
        f
    );
</chain>

如果你想选择THEN这个表达式,那么你可以在选择节点里返回t1:

@LiteflowComponent("b")
public class BCmp extends NodeSwitchComponent {

    @Override
    public String processSwitch() throws Exception {
        //do your biz
        return "t1";
    }
}

选择编排中的tag语法: 事实上,除了给表达式赋值id属性之外,你还可以给表达式赋值tag属性。用法如下:

<chain name="chain1">
    THEN(
        a,
        SWITCH(b).to(
            c, 
            THEN(d, e).tag("t1")
        ),
        f
    );
</chain>

如果你想选择THEN这个表达式,那么你可以在选择节点里返回:

@LiteflowComponent("b")
public class BCmp extends NodeSwitchComponent {

    @Override
    public String processSwitch() throws Exception {
        return "tag:t1";
        //以下这种也是可以的
        return ":t1";
    }
}

条件编排

条件编排是选择编排一个变种,选择编排是根据逻辑去选择多个子项中的一项。而条件编排只有真和假2个子项,这处理某些业务的过程中非常有用。其实简单来说,条件编排就是编程语言中的if else。只不过在LiteFlow EL语法中有一些不一样的用法。以下IFELIF的第一个参数要求定义条件组件。

IF的二元表达式: 其中x为条件节点,为真的情况下,执行链路就为x->a->b,为假链路就为x->b

<chain name="chain1">
    THEN(
        IF(x, a),
        b
    );
</chain>

@Component("x")
public class XCmp extends NodeIfComponent {
	@Override
	public boolean processIf() throws Exception {
	    //do your biz
		return true;
	}
}

IF的三元表达式: 其中x为条件节点,为真的情况下,执行链路就为x->a->c,为假链路就为x->b->c

<chain name="chain1">
    THEN(
        IF(x, a, b),
        c
    );
</chain>

ELIF表达式: ELIF关键字的用法其实和java语言的else if类似,可以跟多个,和IF二元表达式参数一样,一般最后还会跟个ELSE,用于多重条件的判断:

<chain name="chain1">
    IF(x1, a).ELIF(x2, b).ELIF(x3, c).ELIF(x4, d).ELSE(THEN(m, n));
</chain>

循环编排

FOR循环: FOR循环表达式用于固定次数的循环,通常的用法为:

<chain name="chain1">
    FOR(5).DO(THEN(a, b));
</chain>

上述表达式表示把a->b这个链路固定循环了5次。如果你在定义规则的时候并不确定要循环几次,要在代码运行的时候才知道。那你也可以这样定义:

<chain name="chain1">
    FOR(f).DO(THEN(a, b));
</chain>

其中f这个节点需要为次数循环组件,返回一个int循环次数,f节点的定义,需要继承NodeForComponent,需要实现processFor方法:

@LiteflowComponent("f")
public class FCmp extends NodeForComponent {
    @Override
    public int processFor() throws Exception {
        //这里根据业务去返回for的结果
    }
}

循环下标获取:关键字FOR...DO...DO里面的任意java组件都可以通过this.getLoopIndex()来获得下标。在脚本中通过_meta.loopIndex来获取。

WHILE循环:

<chain name="chain1">
    WHILE(w).DO(THEN(a, b));
</chain>

其中w这个节点需要为条件循环组件,返回一个布尔值,为true则继续循环

@LiteflowComponent("w")
public class WCmp extends NodeWhileComponent {
    @Override
    public boolean processWhile() throws Exception {
        //这里根据业务去返回while的结果
    }
}

循环下标获取:关键字WHILE...DO...DO里面的任意节点都可以通过this.getLoopIndex()来获得下标。在脚本中通过_meta.loopIndex来获取。

ITERATOR迭代循环

<chain name="chain1">
    ITERATOR(x).DO(THEN(a, b));
</chain>

其中x这个节点需要为迭代循环组件,返回一个迭代器:x节点的定义,需要继承NodeIteratorComponent,需要实现processIterator方法:

@LiteflowComponent("x")
public class XCmp extends NodeIteratorComponent {
    @Override
    public Iterator<?> processIterator() throws Exception {
        List<String> list = ListUtil.toList("jack", "mary", "tom");
        return list.iterator();
    }
}

BREAK

LiteFlow同样也支持BREAK语法,代表退出循环。BREAK关键字可以跟在FORWHILE后面,通常用法为:

<chain name="chain1">
    FOR(f).DO(THEN(a, b)).BREAK(c);
</chain>

<chain name="chain1">
    WHILE(w).DO(THEN(a, b)).BREAK(c);
</chain>

其中c这个节点需要为退出循环组件,返回一个布尔值,为true则退出循环。c节点的定义,需要继承NodeBreakComponent,需要实现processBreak方法:

@LiteflowComponent("c")
public class CCmp extends NodeBreakComponent {
    @Override
    public boolean processBreak() throws Exception {
        //这里根据业务去返回break的结果
    }
}

BREAK关键字是在每次循环的末尾进行判断的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/189679.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

jQuery实现横版手风琴效果

一、实现效果 当鼠标滑过方块的时候&#xff0c;方块的状态就会发生如下图所示的变化&#xff0c;同理当鼠标滑到其他的方块也会发生同样的效果&#xff0c;不仅大小会改变同时方块的颜色也会跟着发生变化&#xff1a; 二、代码实现 <!DOCTYPE html> <html><h…

Python pandas数据分析

Python pandas数据分析&#xff1a; 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle&#xff0c;尤其…

C语言从入门到精通之【表达式和语句】

1 表达式 表达式由运算符和运算对象组成&#xff0c;最简单的表达式一个单独的运算对象。每个表达式都有一个值&#xff0c;并且是根据运算符优先级规定的顺序来执行&#xff0c;以下是一些表达式&#xff1a; 4 -6 421 a*(b c/d)/20 q 5*2 x q % 3 #q > 3 2 语句 语句…

前端web开发学习笔记

JavaWeb 前端Web开发HTMLCSSjavaScript1.JS引入2.JS基础语法3.JS函数4.JS对象 BOMDOM文档对象模型JS事件监听VueVue常用指令Vue的生命周期 AjaxAxios 前端工程化环境准备NodeJS安装和Vue-cli安装vue项目Vue组件库Element组件的使用 Vue路由Nginx打包部署 前端Web开发 HTML 负…

linux rpm安装软件卸载 以卸载mysql为例

查看rpm包 rpm -qa | grep 内容 卸载rpm rpm -e --nodeps rpm名称

使用VC++设计程序:实现常见的三种图像插值算法:最近邻插值,双线性插值,立方卷积插值

图像放大的三种插值算法 获取源工程可访问gitee可在此工程的基础上进行学习。 该工程的其他文章&#xff1a; 01- 一元熵值、二维熵值 02- 图像平移变换&#xff0c;图像缩放、图像裁剪、图像对角线镜像以及图像的旋转 03-邻域平均平滑算法、中值滤波算法、K近邻均值滤波器 04-…

VSCode 警告:v-on event ‘@toggleClick‘ must be hyphenated

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

【Spring篇】spring核心——AOP面向切面编程

目录 想要彻底理解AOP&#xff0c;我觉得你的先要了解框架的模块化思想&#xff0c;为此先记录框架在讲AOP 什么是java框架&#xff1f;为什么要出现框架&#xff1f; 我总结以下七点来讲述和帮助理解java框架思想 什么是AOP&#xff1f; 如何理解上面这句话呢&#xff1…

【开源】基于Vue+SpringBoot的农家乐订餐系统

项目编号&#xff1a; S 043 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S043&#xff0c;文末获取源码。} 项目编号&#xff1a;S043&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户2.2 管理员 三、系统展示四、核…

Redis面试题:redis做为缓存,数据的持久化是怎么做的?两种持久化方式有什么区别呢?这两种方式,哪种恢复的比较快呢?

目录 面试官&#xff1a;redis做为缓存&#xff0c;数据的持久化是怎么做的&#xff1f; 面试官&#xff1a;这两种持久化方式有什么区别呢&#xff1f; 面试官&#xff1a;这两种方式&#xff0c;哪种恢复的比较快呢&#xff1f; 面试官&#xff1a;redis做为缓存&#xff…

【【Linux编程介绍之关键配置和常用用法】】

Linux编程介绍之关键配置和常用用法 Hello World ! 我们所说的编写代码包括两部分&#xff1a;代码编写和编译&#xff0c;在Windows下可以使用Visual Studio来完成这两部&#xff0c;可以在 Visual Studio 下编写代码然后直接点击编译就可以了。但是在 Linux 下这两部分是分开…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《前景导向的主动配电网智能储能软开关规划方法》

这个标题涉及到电力系统中的主动配电网&#xff08;Active Distribution Network&#xff09;以及与之相关的智能储能软开关的规划方法。下面是对标题中各个关键词的解释&#xff1a; 前景导向的&#xff08;Future-oriented&#xff09;&#xff1a; 这表明该方法是以未来发展…

球面的表面积

此推导需要用到重积分的知识&#xff0c;另外关于曲面的面积公式可以看我之前的博客

线性表,也是Java中数组的知识点!

线性表定义&#xff1a; 由n (n≥0)个数据特性相同的元素构成的有限序列称为线性表&#xff0c;(n0)的时候被称为空表。 线性表的顺序表示 线性表的顺序存储又被称为顺序表 优点 无需为表示表中元素之间的逻辑关系而增加额外的存储空间可以随意读取任意位置的元素 缺点 插入…

【Mybatis】预编译/即时sql 数据库连接池

回顾 Mybatis是一个持久层框架.有两种方式(这两种方式可以共存) 1.注解 2.xml 一.传递参数 以使用#{} 来接受参数为例 (以上两种方式一样适用的) 1)传递单个参数 #{} 可以为任意名称 2)多个参数 默认的参数名称就是接口方法声明的形参 3)参数为对象 默认给每个对象的每个属性都…

中海油“海安杯”一站到底知识竞赛真的很有特色

中海油“海安杯”一站到底知识竞赛规格高&#xff0c;赛制复杂&#xff0c;天纵知识竞赛系统为此次知识竞赛提供了软件支持。本次竞赛设置选手区和擂台区两个区域。比赛共分为五个轮次&#xff0c;五个轮次选手区所有参赛选手均需答题。 第一轮&#xff1a;“脱颖而出” 所有参…

ruoyi-plus-vue docker 部署

本文以 ruoyi-vue-plus 5.x docker 部署为基础 安装虚拟机 部署文档 安装docker 安装docker 安装docker-compose 配置idea环境 上传 /doicker 文件夹 到服务器&#xff1b;赋值 777权限 chmod -R 777 /docker idea构建 jar 包 利用 idea 构建镜像; 创建基础服务 docker…

深入了解 Pinia:现代 Vue 应用的状态管理利器

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

2017年11月16日 Go生态洞察:Go用户调查深度解析

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

CSDN助手:一键下载CSDN博客:高效保存,随时阅读

文章目录 &#x1f4d6; 介绍 &#x1f4d6;&#x1f3e1; 环境 &#x1f3e1;&#x1f4d2; 使用方法 &#x1f4d2;⚓️ 相关链接 ⚓️ &#x1f4d6; 介绍 &#x1f4d6; 这是我自己无聊的时候写的一个应用&#xff0c;以前UI有点丑&#xff0c;这次重写了一下UI 功能如下 …