场景
在业务开发中,经常遇到一些串行或者并行的业务流程问题,而业务之间不必存在相关性。
使用策略和模板模式的结合可以解决这个问题,但是使用编码的方式会使得文件太多,
在业务的部分环节可以这样操作,在项目角度就无法一眼洞穿其中的环节和逻辑。
一些拥有复杂业务逻辑的系统,核心业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取,内部其他系统RPC调用等等。
时间一长,维护的成本就会越来越高。各种硬代码判断,分支条件越来越多。代码的抽象,复用率也越来越低,各个模块之间的耦合度很高。
一小段逻辑的变动,会影响到其他模块,需要进行完整回归测试来验证。
如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。
实时热变更业务流程,几乎很难实现 。
LiteFlow
LiteFlow就是为解耦复杂逻辑而生,如果你要对复杂业务逻辑进行新写或者重构,用LiteFlow最合适不过。
它是一个轻量,快速的组件式流程引擎框架,组件编排,帮助解耦业务代码,让每一个业务片段都是一个组件,
并支持热加载规则配置,实现即时修改。
使用LiteFlow,你需要去把复杂的业务逻辑按代码片段拆分成一个个小组件,并定义一个规则流程配置。
这样,所有的组件,就能按照你的规则配置去进行复杂的流转。
LiteFlow官方网站:
LiteFlow
LiteFlow的Gitee地址:
liteFlow: 轻量,快速,稳定,可编排的组件式规则引擎/流程引擎。拥有全新设计的DSL规则表达式。组件复用,同步/异步编排,动态编排,支持超多语言脚本,复杂嵌套规则,热部署,平滑刷新规则等等功能,让你加快开发效率!
LiteFlow的特点:
注:
博客:
霸道流氓气质-CSDN博客
实现
1、SpringBoot中集成LiteFlow
LiteFlow要求的Springboot的最低的版本是2.0。
支持的范围是Springboot 2.X ~ Springboot 3.X。
LiteFlow提供了liteflow-spring-boot-starter依赖包,提供自动装配功能
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>2.11.4.2</version>
</dependency>
2、SpringBoot中配置LiteFlow
在你的SpringBoot的application.properties或者application.yml里添加配置
#liteflow规则配置文件位置
liteflow:
rule-source: config/flow.el.xml
规则文件的定义
在resources下的config/flow.el.xml中定义规则:
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="chain1">
THEN(acmp, bcmp, ccmp);
</chain>
</flow>
根据定义的规则,需要定义并实现一些组件,确保SpringBoot会扫描到这些组件并注册进上下文。
import com.yomahub.liteflow.core.NodeComponent;
import org.springframework.stereotype.Component;
@Component("acmp")
public class ACmp extends NodeComponent {
@Override
public void process() {
//do your business
System.out.println("acmp执行");
}
}
以此类推,定义另外两个组件
import com.yomahub.liteflow.core.NodeComponent;
import org.springframework.stereotype.Component;
@Component("bcmp")
public class BCmp extends NodeComponent {
@Override
public void process() {
//do your business
System.out.println("bcmp执行");
}
}
import com.yomahub.liteflow.core.NodeComponent;
import org.springframework.stereotype.Component;
@Component("ccmp")
public class CCmp extends NodeComponent {
@Override
public void process() {
//do your business
System.out.println("ccmp执行");
}
}
更多配置项内容参考文档说明
🌿Springboot下的配置项 | LiteFlow
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
#是否开启快速解析模式,默认为false
fast-load: false
#简易监控配置选项
monitor:
#监控是否开启,默认不开启
enable-log: false
#监控队列存储大小,默认值为200
queue-limit: 200
#监控一开始延迟多少执行,默认值为300000毫秒,也就是5分钟
delay: 300000
#监控日志打印每过多少时间执行一次,默认值为300000毫秒,也就是5分钟
period: 300000
3、SpringBoot中执行LiteFlow
声明启动类,确保定义的组件扫入Spring上下文
@SpringBootApplication
//把你定义的组件扫入Spring上下文中
@ComponentScan({"com.xxx.xxx.cmp"})
public class LiteflowExampleApplication {
public static void main(String[] args) {
SpringApplication.run(LiteflowExampleApplication.class, args);
}
}
然后可以在在Springboot任意被Spring托管的类中拿到flowExecutor,进行执行链路
这里进行单元测试
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RuoYiApplication.class,webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class LiteFlowTest {
@Resource
private FlowExecutor flowExecutor;
@Test
public void helloLiteFlow() {
LiteflowResponse response = flowExecutor.execute2Resp("chain1");
System.out.println(response);
}
}
这里的chain1与上面规则文件中的对应。
运行结果
可以看到三个组件依次执行,这是因为配置的规则文件中配置的规则如此。
除了上面配置的普通组件之外,还可配置其他组件,比如选择组件、条件组件、循环组件等
📎普通组件 | LiteFlow
下面配置一个选择组件为例
在实际业务中,往往要通过动态的业务逻辑判断到底接下去该执行哪一个节点,这就引申出了选择节点,
选择节点可以用于SWITCH关键字中。
关于SWITCH表达式的用法,可以参考选择编排一章。
选择节点a需要继承NodeSwitchComponent。
需要实现方法processSwitch方法
import com.yomahub.liteflow.core.NodeSwitchComponent;
import org.springframework.stereotype.Component;
@Component("switchCmp")
public class SwitchCmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
System.out.println("switchCmp executed!");
//自己业务选择
//以下代表选择了switchCmpA节点
return "switchCmpA";
}
}
配置规则文件
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="chain1">
THEN(acmp, bcmp, ccmp);
</chain>
<chain name="switch_chain">
SWITCH(switchCmp).to(switchCmpA, switchCmpB);
</chain>
</flow>
其中switchCmpA与switchCmpB是普通组件
编写单元测试
@Test
public void switchTest() {
LiteflowResponse response = flowExecutor.execute2Resp("switch_chain");
}
运行结果
条件组件用法
LiteFlow从2.8.5版本开始,提供了条件组件的定义。
条件组件,也可以称之为IF组件,返回是一个true/false。可用于IF...ELIF...ELSE等关键字。
关于IF...ELIF...ELSE表达式的用法,可以参考条件编排这一章。
比如一个IF三元表达式,如下所示,xcmp就是IF组件,为真,执行acmp,为假,执行bcmp:
<chain name="if_chain">
IF(xcmp, acmp, bcmp);
</chain>
编写条件组件
import com.yomahub.liteflow.core.NodeIfComponent;
import org.springframework.stereotype.Component;
@Component("xcmp")
public class XCmp extends NodeIfComponent {
@Override
public boolean processIf() throws Exception {
//自己的业务判断
return false;
}
}
其它更多组件用法参考官方文档。
4、LiteFlow组件传参
在一个流程中,总会有一些初始的参数,比如订单号,用户Id等等一些的初始参数。
这时候需要通过以下方法的第二个参数传入
public LiteflowResponse execute2Resp(String chainId, Object param, Class<?>... contextBeanClazzArray)
这个流程入参,可以是任何对象,一般生产业务场景下,你可以把自己封装好的Bean传入。
编写传参组件
import com.ruoyi.system.domain.BusStudent;
import com.yomahub.liteflow.core.NodeIfComponent;
import org.springframework.stereotype.Component;
@Component("xpcmp")
public class XParamCmp extends NodeIfComponent {
@Override
public boolean processIf() throws Exception {
//自己的业务判断
BusStudent requestData = this.getRequestData();
if(null!=requestData.getName()&&"公众号:霸道的程序猿".equals(requestData.getName())){
return true;
}else{
return false;
}
}
}
传参使用
flowExecutor.execute2Resp("if_param_chain", BusStudent.builder().name("公众号:霸道的程序猿").build());
5、LiteFlow声明式组件
普通组件和条件组件,在写法上需要你自己去定义一个类去继承NodeComponent或者NodeSwitchComponent。
这样一方面造成了耦合,另一方面由于java是单继承制,所以使用者就无法再去继承自己的类了,在自由度上就少了很多玩法。
声明式组件这一特性允许你自定义的组件不继承任何类和实现任何接口,普通的类也可以依靠注解来完成LiteFlow组件的声明。
甚至于你可以用一个类去定义多个组件,仅仅依靠注解就可以完成,这个特性也叫做方法级别式声明
类级别式声明主要用处就是通过注解形式让普通的java bean变成LiteFlow的组件。无需通过继承类或者实现接口的方式。
由于LiteFlow的组件常规方式下需要继承类来定义,使得你无法再继承自己业务的类了。这个特性可以解决这个问题。
但是和常规组件一样,需要一个类对应一个组件
自定义一个组件并使用方法级别声明
import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.annotation.LiteflowMethod;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
@LiteflowComponent("defineCmp")
@LiteflowCmpDefine
public class DefineCmp{
@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
public void processAcmp(NodeComponent bindCmp) {
System.out.println("ACmp executed!");
}
@LiteflowMethod(LiteFlowMethodEnum.IS_ACCESS)
public boolean isAcmpAccess(NodeComponent bindCmp){
return true;
}
@LiteflowMethod(LiteFlowMethodEnum.BEFORE_PROCESS)
public void beforeAcmp(NodeComponent bindCmp){
System.out.println("before A");
}
@LiteflowMethod(LiteFlowMethodEnum.AFTER_PROCESS)
public void afterAcmp(NodeComponent bindCmp){
System.out.println("after A");
}
@LiteflowMethod(LiteFlowMethodEnum.ON_SUCCESS)
public void onAcmpSuccess(NodeComponent bindCmp){
System.out.println("Acmp success");
}
@LiteflowMethod(LiteFlowMethodEnum.ON_ERROR)
public void onAcmpError(NodeComponent bindCmp, Exception e){
System.out.println("Acmp error");
}
@LiteflowMethod(LiteFlowMethodEnum.IS_END)
public boolean isAcmpEnd(NodeComponent bindCmp) {
return false;
}
@LiteflowMethod(value = LiteFlowMethodEnum.ROLLBACK)
public void rollbackA(NodeComponent bindCmp) throws Exception {
System.out.println("ACmp rollback!");
}
}
同样实现效果。
6、LiteFlow还有更多功能和属性
比如EL规则的编排
🍄说明 | LiteFlow
用代码动态构造规则
🍄说明 | LiteFlow
以及各种给高级特性、平滑热更新等。
具体参考官方文档说明。
DEMO案例
滑动验证页面