目录
LiteFlow是一个组件式开发的框架。目前Gitee Star 2.7K。
先放上项目地址:https://gitee.com/dromara/liteFlow?_from=gitee_search
和项目文档:https://liteflow.yomahub.com/pages/5816c5/
所有的框架,使用过程基本类似——提供注解或者基础包,开发者基于注解或者包+业务流程进行开发。代码写好后,一旦业务流程发生变更,就要改代码。很麻烦。
所以LiteFlow出现了。当我们以适当的粒度拆分了业务组件,我们就可以基于LiteFlow的规则引擎进行各种各样的编排,以实现业务需求。
整体来说,LiteFlow可以用以下式子来概括:
LiteFlow=组件+上下文+规则
那么下面我们对每一部分进行说明。
组件是LiteFlow的基础。LiteFlow中有两种组件,分别是
NodeComponent 一般组件和 NodeSwitchComponent 分支组件。
分别对应我们程序中使用的顺序结构中的代码和判断结构中的判断代码。
当需要有条件的决定组件的使用顺序时,NodeSwitchComponent就派上用场了。
在代码实现上,我们可以基于继承或者仅基于注解两种方式来定义组件,这里我给两个基于继承的例子,基于注解方式的小伙伴们可以看文档。代码里不仅仅是声明,还包含了一些其他的知识点,小伙伴们可以边看代码边查文档,了解下自己感兴趣的部分。
import com.example.demo.config.MyContext;
import com.example.demo.vo.BasicParam;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeComponent;
@LiteflowComponent(id = "channelSelector", name = "渠道余量最大选择器")
//失败重试 @LiteflowRetry(retry = 5, forExceptions = {NullPointerException.class,IllegalArgumentException.class})
public class ChannelSelectorCmp extends NodeComponent {
@Override
public void process() throws Exception {
MyContext context = this.getFirstContextBean();
BasicParam requestData = this.getRequestData();
context.setInfo(context.getInfo()+":channelSelector:"+requestData.getBula());
//throw new Exception("test");
//隐式流程
//隐式调用可以完成更为复杂的子流程,比如循环调用,复杂条件判断等等。隐式子流程需要你在组件里通过this.invoke这个语句来调用。
for(int i=0;i<10;i++){
this.invoke("channelSelector2","隐式流程的初始参数,可为null");
//可以使用invoke2Resp进行带返回值的请求
//隐式流程在组件中拿到传入的请求参数,通过this.getSubChainReqData()去拿,用this.getRequestData()是拿不到的
}
//私有投递
for (int i = 0; i < 5; i++) {
this.sendPrivateDeliveryData("b",i+1);
//Integer value = this.getPrivateDeliveryData(); //内部使用队列实现
}
}
}
import com.example.demo.config.MyContext;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeSwitchComponent;
import com.yomahub.liteflow.slot.Slot;
import java.util.Random;
@LiteflowComponent(id = "if_1", name = "业务判断1")
public class IF1SwitchCmp extends NodeSwitchComponent {
/**
* 表示是否进入该节点,可以用于业务参数的预先判断
* @return
*/
@Override
public boolean isAccess() {
return super.isAccess();
}
@Override
public String processSwitch() throws Exception {
//获取上下文
MyContext firstContextBean = (MyContext)getFirstContextBean();
//获取当前执行流程名称
String chainName = getChainName();
//是否立即结束整个流程
setIsEnd(false);
//模拟业务耗时
int time = new Random().nextInt(1000);
Thread.sleep(time);
//这里写死跳到并行获取剩余量那条分支,你可以改成其他分支测试
return "branch1";
}
@Override
public boolean isContinueOnError() {
return super.isContinueOnError();
}
@Override
public <T> void beforeProcess(String nodeId, Slot slot) {
super.beforeProcess(nodeId, slot);
}
@Override
public <T> void afterProcess(String nodeId, Slot slot) {
super.afterProcess(nodeId, slot);
}
@Override
public void onSuccess() throws Exception {
super.onSuccess();
}
@Override
public void onError() throws Exception {
super.onError();
}
}
仔细的小伙伴可能注意到了,上面组件定义的代码中有这样一句
//获取上下文
MyContext firstContextBean = (MyContext)getFirstContextBean();
这个就是获取上下文, MyContext是自定义的一个类,用来存放组件共用的数据,是在工作流刚开始执行的时候定义的。定义的代码如下:
import com.example.demo.config.MyContext;
import com.example.demo.vo.BasicParam;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.enums.FlowParserTypeEnum;
import com.yomahub.liteflow.flow.FlowBus;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.flow.entity.CmpStep;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
import java.util.Queue;
@Component
public class ChainExecute implements CommandLineRunner {
private static final Logger log = LoggerFactory.getLogger(ChainExecute.class);
@Resource
private FlowExecutor flowExecutor;
@Override
public void run(String... args) throws Exception {
//第二个参数为流程入参,示例中没用到,所以传null,实际业务是有值的
//上下文可以使用多个
BasicParam basicParam=new BasicParam();
basicParam.setBula("bula");
LiteflowResponse response = flowExecutor.execute2Resp("channelSenderChain", basicParam, MyContext.class);
MyContext context = response.getFirstContextBean();
/*if (response.isSuccess()){
log.info("Result{}", context.getInfo());
System.out.println(context.getInfo());
}else{
log.error("执行失败", response.getCause());
System.out.println(response.getCause().getMessage());
}*/
//获取执行队列,相同步骤仅有一个定义
/*Map<String, CmpStep> stepMap = response.getExecuteSteps();
stepMap.forEach((s, cmpStep) -> {
System.out.println(s);
});*/
//相同步骤保留多个定义
Queue<CmpStep> stepQueue = response.getExecuteStepQueue();
//获取步骤字符串
System.out.println(response.getExecuteStepStr());
//平滑热刷新,会按启动时的方式取拉取最新的流程配置信息
flowExecutor.reloadRule();
//基于规则文件被动刷新
FlowBus.refreshFlowMetaData(FlowParserTypeEnum.TYPE_EL_XML, "规则文件");
//其实基于动态代码构建的,建议你把动态代码构建的代码封装成一个方法。
// 有变动时,再重新执行一遍构建就可以了。会重新覆盖的。并且这一过程,也是平滑的。
}
}
注意这里这段代码,就是定义上下文,这里实际上只是传了一个class,如果想在工作流初始就传递一些数据,可以使用代码里basicParam定义的部分。
flowExecutor.execute2Resp("channelSenderChain", basicParam, MyContext.class);
这里规则就是LiteFlow的灵魂了。LiteFlow提供了十分灵活的组件定义方式,XML、Json都是支持的。这里给一个截取自官网的例子
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<nodes>
<node id="s1" name="普通脚本" type="script">
<![CDATA[
a=3;
b=2;
defaultContext.setData("s1",a*b);
]]>
</node>
<node id="s2" name="条件脚本" type="switch_script">
<![CDATA[
count = defaultContext.getData("count");
if(count > 100){
return "a";
}else{
return "b";
}
]]>
</node>
</nodes>
<chain name="chain1">
THEN(a, b, c, s1)
</chain>
<chain name="chain2">
THEN(d, SWITCH(s2).to(a, b)
</chain>
</flow>
这里我们注意到里边的THEN和SWICH,分别就是我们在最上面定义的一般组件和分支组件了。
这里我对LiteFlow提供的规则进行了12条总结:
"#1": "串行 then(a,b,c,d)",
"#2": "并行 when(a,b)",
"#3": "忽视串行中节点错误,c仍然会执行 then(s,when(a,b).ignoreError(true),c)",
"#4": "a,b任意一分支执行完则忽略其他分支,继续执行c then(s,when(a,b).any(true),c)",
"#5": "选择执行 SWITCH(a).to(b,c,d),a的返回值只能取bcd",
"#6": "可以使用.id()来给任意组件设置ID,这在switch和then配合使用时很有用,SWITCH(s).to(a,b,THEN(c,d).id('testId'))",
"#7": "可以使用变量和子流程来简化流程",
"#8": "EL方式中注释和js中相同",
"#9": "可以使用node(特殊标签名)来方便处理自动生成的标签名 then(a,node(b))",
"#10": "前置节点ab会先执行 THEN(PRE(a,b),c),可以声明在第一层的任意位置,如果在子流程中使用,则只会对子流程生效",
"#11": "后置节点即时节点出错也会执行,THEN(a,b,FINALLY(c)),可以声明在第一层的任意位置,如果在子流程中使用,则只会对子流程生效",
"#12": "相同组件编排时可以使用tag()打标签,在NodeComponent实例.getTag()获取,THEN(a.tag('1'),a.tag('2'))"
好了,浅谈就到这里。有用的话点个赞呗。