组件

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");
    }
}

普通组件可以使用THENWHEN等串行或并行编排,其中使用的名字就是组件的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,下面例子中当选择组件返回stepBCTHEN(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

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。