业务流程编排框架Liteflow源码解析

931 阅读3分钟

用代码动态构造规则

LiteFlowChainELBuilder类中通过QLExpress的扩展操作符operator对表达式中的操作进行扩展,操作包括THEN、WHEN、IF、SWITCH等。 com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder

image.png 通过com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder#setEL执行表达式

image.png

QL表达式:THEN(a, b, THEN(c, d));

QLExpress的上下文DefaultContext中放参数,key为nodeId,如abcd,value则是它们对应的node。表达式执行后返回Condition,如THEN对应的操作ThenOperator执行后返回ThenCondition,ThenCondition中存放着可执行元素的集合。这些Condition会在com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder#build方法中调用com.yomahub.liteflow.flow.FlowBus#addChain把表达式的执行结果存到chainMap中,执行表达式的时通过chainId获取到可执行的元素集合,递归调用com.yomahub.liteflow.flow.element.Condition#execute。execute方法中使用了模板模式,提供了抽象方法executeCondition供子类实现。

com.yomahub.liteflow.builder.el.operator.ThenOperator

image.png }

com.yomahub.liteflow.flow.element.condition.ThenCondition#addExecutable

image.png com.yomahub.liteflow.flow.element.Condition#addExecutable(com.yomahub.liteflow.flow.element.Executable)

image.png 表达式执行后把结果放到chain中,把chain放到FlowBus的静态变量chainMap中,供执行流程时根据chainId获取。

com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder#build

image.png

image.png liteflow扩展的QLExpress操作和Condition

image.png

数据上下文的传递

创建FlowExecutor对象的时候,调用DataBus.init()方法对静态变量SLOTS和QUEUE进行初始化。

ConcurrentHashMap SLOTS:key为slot的索引,在执行流程的时候分配(从QUEUE中弹出一个值),执行完流程会再放回QUEUE,供后面执行流程时使用。后面执行每个Condition或node的时候都会传递slotIndex,根据slotIndex从DataBus中获取

ConcurrentLinkedQueue QUEUE:预分配的slot索引值,初始化是0-1024,如果同时执行的流程较多,则会进行扩容,在扩容的时候需要用到synchronized重量级锁,扩一次容,增强原来size的0.75,因为初始slot容量为1024,从某种层面来说,即便并发很大。但是扩容的次数不会很多。因为单个机器的tps上限总归是有一个极限的,不可能无限制的增长。

脚本组件的实现

新增node时addNode方法会加载脚本(编译后的脚本放到map中,nodeId作为key),流程执行时通过nodeId获取编译的脚本,根据脚本类型获取到对应的执行器,执行脚本

脚本组件中注册时加载脚本

com.yomahub.liteflow.flow.FlowBus#addNode

image.png 脚本组件都实现了ScriptComponent接口的loadScript方法,如ScriptCommonComponent的实现如下:

image.png 其中ScriptExecutorFactory是ScriptExecutor执行器的工厂类,用来获取不同类型脚本的执行器,

ScriptExecutorFactory是单例的,第一次调用getScriptExecutor方法获取执行器时会通过SPI机制加载支持的脚本执行器,存放到scriptExecutorMap中。

image.png 获取到ScriptExecutor后执行load方法,通过load方法编译脚本,并把编译后的脚本放到compiledScriptMap中,key为 nodeId。

image.png 需要注意的是有些脚本编译前需要通过convertScript方法转换,如javascript转换时把脚本封装到函数中,然后调用函数。

Java6平台提出了JSR223规范,提供了一套标准的API为脚本语言的执行提供了内置支持。也就是说,你只要熟悉这一套API就能执行大部分的脚本语言。

下面给出AviatorScript和JavaScript的例子:

image.png

image.png 现在很多的语言在java平台都推出了自己的java三方执行依赖包,而且很多的包都支持了JSR223规范。只要支持了JSR223规范的语言,都可以利用上述的代码来执行。

JSR223规范的API可以支持java和其他语言的绑定和互通,一个java对象通过bindings对象也是可以传到脚本语言中的,在脚本语言中,你可以获得java的对象,来进行调用其方法和进行逻辑计算。