组件
LiteFlow中,业务流程节点需要定义为组件,组件中包含我们的自定义逻辑,供上层的流程XML定义使用。这篇笔记我们介绍LiteFlow中的组件类型和使用方式。
@LiteflowComponent注解
LiteFlow中的组件类都需要使用@LiteflowComponent注解标注,这个注解其实继承自Spring的@Component注解,因此组件类会自动注册到Spring容器,组件内部也可以使用Spring的IoC容器获取其它Bean。不过我们一般不会手动直接调用LiteFlow的组件类,组件类是由LiteFlow框架根据注解指定的@LiteflowComponent注解value值作为标识调用的,这个标识也被称为nodeId。下面例子定义了ID是stepA的组件。
@LiteflowComponent("stepA")
public class StepA extends NodeComponent {
// ...
}
nodeId可以自定义任何字符串,但有几点需要注意:
- 不能以数字开头
- 中间不能有运算符号出现
- 不要和其他组件重复
除了nodeId,我们还可以用注解的name属性给组件设置一个别名,这个别名会显示在日志中,方便我们观察组件的执行情况。
@LiteflowComponent(value = "stepA", name = "步骤A")
组件类型及使用
普通组件
普通组件继承自NodeComponent类,需要实现process()方法,前一篇笔记我们已经使用过普通组件了。
package com.gacfox.demo.service;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeComponent;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@LiteflowComponent("stepA")
public class StepA extends NodeComponent {
@Override
public void process() throws Exception {
log.info("stepA");
}
}
普通组件可以使用THEN、WHEN等串行或并行编排,其中使用的名字就是组件的nodeId。
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="demoChain">
THEN(stepA, stepB, stepC);
</chain>
</flow>
选择组件
选择组件也非常常用,它可以实现通过动态的业务逻辑判断接下来执行哪一个节点,选择组件需要继承NodeSwitchComponent并实现processSwitch方法。
package com.gacfox.demo.service;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeSwitchComponent;
@LiteflowComponent("stepChoose")
public class StepChoose extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
return "stepA";
}
}
processSwitch方法返回的字符串就是下一步的节点nodeId。
在XML配置中,选择组件需要使用SWITCH,后面使用to()接上被选择的节点。
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="demoChain">
SWITCH(stepChoose).TO(stepA, stepB, stepC);
</chain>
</flow>
如果SWITCH的结果是一个表达式而不是单个的组件,我们还可以使用id()给表达式添加一个ID,下面例子中当选择组件返回stepBC时THEN(stepB, stepC)将被执行。
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="demoChain">
SWITCH(stepChoose).TO(stepA, THEN(stepB, stepC).id("stepBC"));
</chain>
</flow>
布尔组件
布尔组件可用于实现分支判断、循环条件、循环终止条件的判断。布尔组件需要继承NodeBooleanComponent,并实现processBoolean()方法。
package com.gacfox.demo.service;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeBooleanComponent;
@LiteflowComponent("stepTest")
public class StepTest extends NodeBooleanComponent {
@Override
public boolean processBoolean() throws Exception {
return true;
}
}
下面例子实现当stepTest返回true时执行stepA,否则执行stepB。
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="demoChain">
IF(stepTest, stepA).ELSE(stepB);
</chain>
</flow>
涉及判断的逻辑都会用到布尔组件,布尔组件的用法非常灵活,有关更多编排相关的用法将在下一章节介绍。
次数循环组件
次数循环组件用于实现类似For的固定次数循环逻辑,次数循环组件需要继承NodeForComponent,并实现processFor()方法。
package com.gacfox.demo.service;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeForComponent;
@LiteflowComponent("stepFor")
public class stepFor extends NodeForComponent {
@Override
public int processFor() throws Exception {
return 3;
}
}
编排配置中需要使用FOR().DO()语法,下面例子stepA会执行3次。
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="demoChain">
FOR(stepFor).DO(stepA);
</chain>
</flow>
FOR().DO()循环可以嵌套,此外DO()的内容也可以是另一个表达式。
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="demoChain">
FOR(x).DO(
FOR(y).DO(
FOR(z).DO(
THEN(a,b)
)
)
);
</chain>
</flow>
迭代循环组件
迭代循环需要继承NodeIteratorComponent并实现processIterator()方法,它需要返回迭代器对象。
package com.gacfox.demo.service;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeIteratorComponent;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@LiteflowComponent("stepIter")
public class StepIter extends NodeIteratorComponent {
@Override
public Iterator<?> processIterator() throws Exception {
List<String> fruits = Arrays.asList("apple", "banana", "orange");
return fruits.iterator();
}
}
流程编排配置中使用ITERATOR()...DO()写法进行迭代循环,下面是一个例子。
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="demoChain">
ITERATOR(stepIter).DO(stepPrint);
</chain>
</flow>
在接收迭代对象的组件中,需要使用this.getCurrLoopObj()方法获取迭代对象。
package com.gacfox.demo.service;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeComponent;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@LiteflowComponent("stepPrint")
public class StepPrint extends NodeComponent {
@Override
public void process() throws Exception {
log.info("stepPrint: {}", (String) this.getCurrLoopObj());
}
}
迭代循环组件也支持多层嵌套,下面是一个例子。
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="demoChain">
ITERATOR(x).DO(
ITERATOR(y).DO(
ITERATOR(z).DO(
THEN(a,b)
)
)
);
</chain>
</flow>
多层迭代循环时,取出迭代对象可以使用this.getPreNLoopObj(n)方法,其中n=0取的就是当前迭代对象,这和getCurrLoopObj()方法等价;n=1是向外1层的迭代对象;n=2是向外2层的迭代对象,以此类推。
跳过组件执行
isAccess()方法用于判断是否执行该组件,如果返回false这个组件就不会执行(即被跳过了)。isAccess()方法的一个重要使用场景是编写有状态流程,其实LiteFlow的流程本身是无状态设计的,流程只能从头开始执行而没有中断、恢复一说,如果我们自己实现了流程的状态并持久化,那么当程序意外终止并开始恢复流程执行时,isAccess()就可以根据我们记录的一些状态字段“跳过”一些组件,恢复到流程尚未执行的节点上。
出错继续执行
isContinueOnError()方法用于判断该组件出错后是否继续执行,默认实现是返回false,即出错后中断流程。我们可以覆盖这个方法实现我们自己的特定业务逻辑,例如在某些情况下即使该组件出错也继续向下执行。
组件执行成功和失败事件回调
onSuccess()和onError()是组件执行成功和失败的两个事件回调函数,其中onError()的参数是组件执行抛出的异常对象。
package com.gacfox.demo.service;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeComponent;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@LiteflowComponent("stepA")
public class StepA extends NodeComponent {
@Override
public void process() throws Exception {
log.info("stepA");
}
@Override
public void onSuccess() throws Exception {
super.onSuccess();
log.info("stepA onSuccess");
}
@Override
public void onError(Exception e) throws Exception {
super.onError(e);
log.info("stepA onError");
}
}
这里要注意的是覆盖了onError()并不意味着组件的执行结果状态就变了,如果组件抛出错误,onError()方法执行完毕后组件依然是错误状态。另一点是onError()的触发和外部是否使用了CATCH等机制无关,也就是说组件主逻辑抛出异常onError()就会触发,它不会改变任何状态也不会被异常捕获等机制影响,它仅仅是个因异常而被触发执行的事件回调。
组件回滚机制
rollback()方法用于实现组件出现异常时的回滚逻辑,当我们的流程需要保证幂等性时就需要使用回滚机制。
首先我们要明确回滚触发的前提,当没有使用continueOnError()跳过异常、并行编排没有使用ignoreError()且也没有使用CATCH,即某组件异常时整个流程执行就是失败的,此时回滚才会被触发。此外回滚逻辑执行的不是一个组件,而是会按照已经执行的组件的逆序执行其中的rollback()方法。
举例来说,假设我们的流程编排如下。
THEN(stepA, stepB, stepC);
三个组件的代码均类似如下。
package com.gacfox.demo.service;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeComponent;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@LiteflowComponent("stepA")
public class StepA extends NodeComponent {
@Override
public void process() throws Exception {
log.info("stepA");
}
@Override
public void rollback() throws Exception {
log.info("stepA rollback");
}
}
当stepC抛出异常必须回滚时,回滚的触发顺序依次是stepC -> stepB -> stepA。