组件
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
。