浏览代码

feature #I4HGOW 支持链路的前置和后置节点

bryan31 3 年之前
父节点
当前提交
ecfebfdc19
共有 20 个文件被更改,包括 346 次插入49 次删除
  1. 24 7
      liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/Chain.java
  2. 21 0
      liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/FinallyCondition.java
  3. 21 0
      liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/PreCondition.java
  4. 0 6
      liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/ThenCondition.java
  5. 0 13
      liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/WhenCondition.java
  6. 3 1
      liteflow-core/src/main/java/com/yomahub/liteflow/enums/ConditionTypeEnum.java
  7. 31 16
      liteflow-core/src/main/java/com/yomahub/liteflow/parser/FlowParser.java
  8. 2 3
      liteflow-core/src/main/java/com/yomahub/liteflow/parser/JsonFlowParser.java
  9. 2 3
      liteflow-core/src/main/java/com/yomahub/liteflow/parser/XmlFlowParser.java
  10. 56 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/PreAndFinallySpringbootTest.java
  11. 20 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/ACmp.java
  12. 21 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/BCmp.java
  13. 21 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/CCmp.java
  14. 22 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/DCmp.java
  15. 20 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/Finally1Cmp.java
  16. 20 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/Finally2Cmp.java
  17. 20 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/Pre1Cmp.java
  18. 20 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/Pre2Cmp.java
  19. 1 0
      liteflow-testcase-springboot/src/test/resources/preAndFinally/application.properties
  20. 21 0
      liteflow-testcase-springboot/src/test/resources/preAndFinally/flow.xml

+ 24 - 7
liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/Chain.java

@@ -16,6 +16,7 @@ import com.yomahub.liteflow.entity.data.Slot;
 import com.yomahub.liteflow.entity.flow.parallel.CompletableFutureTimeout;
 import com.yomahub.liteflow.entity.flow.parallel.ParallelSupplier;
 import com.yomahub.liteflow.entity.flow.parallel.WhenFutureObj;
+import com.yomahub.liteflow.enums.ConditionTypeEnum;
 import com.yomahub.liteflow.enums.ExecuteTypeEnum;
 import com.yomahub.liteflow.exception.FlowSystemException;
 import com.yomahub.liteflow.exception.WhenExecuteException;
@@ -72,15 +73,31 @@ public class Chain implements Executable {
 
         Slot slot = DataBus.getSlot(slotIndex);
 
-        //循环chain里包含的condition,每一个condition有可能是then,也有可能是when
-        //when的话为异步,用闭锁进行等待,所有when结束后才能进入下一个condition
-        for (Condition condition : conditionList) {
-            if (condition instanceof ThenCondition) {
-                for (Executable executableItem : condition.getNodeList()) {
+        //先把finally的节点过滤出来
+        List<Condition> finallyConditionList = conditionList.stream().filter(condition ->
+                condition.getConditionType().equals(ConditionTypeEnum.TYPE_FINALLY.getType())).collect(Collectors.toList());
+
+        //循环chain里包含的condition,每一个condition分四种类型:pre,then,when,finally
+        //这里conditionList其实已经是有序的,pre一定在最前面,finally一定在最后面
+        try{
+            for (Condition condition : conditionList) {
+                if (condition instanceof PreCondition){
+                    for (Executable executableItem : condition.getNodeList()) {
+                        executableItem.execute(slotIndex);
+                    }
+                } else if (condition instanceof ThenCondition) {
+                    for (Executable executableItem : condition.getNodeList()) {
+                        executableItem.execute(slotIndex);
+                    }
+                } else if (condition instanceof WhenCondition) {
+                    executeAsyncCondition((WhenCondition) condition, slotIndex, slot.getRequestId());
+                }
+            }
+        }finally {
+            for (Condition finallyCondition : finallyConditionList){
+                for(Executable executableItem : finallyCondition.getNodeList()){
                     executableItem.execute(slotIndex);
                 }
-            } else if (condition instanceof WhenCondition) {
-                executeAsyncCondition((WhenCondition) condition, slotIndex, slot.getRequestId());
             }
         }
     }

+ 21 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/FinallyCondition.java

@@ -0,0 +1,21 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.entity.flow;
+
+/**
+ * 前置Condition
+ * @author Bryan.Zhang
+ * @since 2.6.4
+ */
+public class FinallyCondition extends Condition {
+
+	public FinallyCondition(Condition condition){
+		super(condition.getNodeList());
+		super.setConditionType(condition.getConditionType());
+	}
+}

+ 21 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/PreCondition.java

@@ -0,0 +1,21 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.entity.flow;
+
+/**
+ * 前置Condition
+ * @author Bryan.Zhang
+ * @since 2.6.4
+ */
+public class PreCondition extends Condition {
+
+	public PreCondition(Condition condition){
+		super(condition.getNodeList());
+		super.setConditionType(condition.getConditionType());
+	}
+}

+ 0 - 6
liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/ThenCondition.java

@@ -7,18 +7,12 @@
  */
 package com.yomahub.liteflow.entity.flow;
 
-import java.util.List;
-
 /**
  * 串行器
  * @author Bryan.Zhang
  */
 public class ThenCondition extends Condition {
 
-	public ThenCondition(List<Executable> nodeList) {
-		super(nodeList);
-	}
-
 	public ThenCondition(Condition condition){
 		super(condition.getNodeList());
 		super.setConditionType(condition.getConditionType());

+ 0 - 13
liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/WhenCondition.java

@@ -7,25 +7,12 @@
  */
 package com.yomahub.liteflow.entity.flow;
 
-import java.util.List;
-
 /**
  * 并行器
  * @author Bryan.Zhang
  */
 public class WhenCondition extends Condition{
 
-
-	public WhenCondition(List<Executable> nodeList) {
-		super(nodeList);
-		super.setErrorResume(true);
-	}
-
-	public WhenCondition(List<Executable> nodeList, boolean errorResume) {
-		super(nodeList);
-		super.setErrorResume(errorResume);
-	}
-
 	public WhenCondition(Condition condition) {
 		super(condition.getNodeList());
 		super.setConditionType(condition.getConditionType());

+ 3 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/enums/ConditionTypeEnum.java

@@ -2,7 +2,9 @@ package com.yomahub.liteflow.enums;
 
 public enum ConditionTypeEnum {
     TYPE_THEN("then","then"),
-    TYPE_WHEN("when","when")
+    TYPE_WHEN("when","when"),
+    TYPE_PRE("pre","pre"),
+    TYPE_FINALLY("finally","finally")
     ;
     private String type;
     private String name;

+ 31 - 16
liteflow-core/src/main/java/com/yomahub/liteflow/parser/FlowParser.java

@@ -4,9 +4,8 @@ import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.util.ArrayUtil;
-import com.yomahub.liteflow.entity.flow.Condition;
-import com.yomahub.liteflow.entity.flow.ThenCondition;
-import com.yomahub.liteflow.entity.flow.WhenCondition;
+import com.google.common.collect.Lists;
+import com.yomahub.liteflow.entity.flow.*;
 import com.yomahub.liteflow.enums.ConditionTypeEnum;
 import com.yomahub.liteflow.exception.ConfigErrorException;
 import org.springframework.core.io.Resource;
@@ -15,12 +14,10 @@ import org.springframework.util.Assert;
 import org.springframework.util.ResourceUtils;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 
 /**
+ * 虽则Parser的抽象类,所有的parser需要继承这个抽象类
  * @author guodongqing
  * @since 2.5.0
  */
@@ -28,10 +25,15 @@ public abstract class FlowParser {
 
     public abstract void parseMain(List<String> pathList) throws Exception;
 
-    public abstract void parse(List<String> contentList) throws Exception ;
+    public abstract void parse(List<String> contentList) throws Exception;
 
-    protected void buildBaseFlowConditions(List<Condition> conditionList,Condition condition){
-        if (condition.getConditionType().equals(ConditionTypeEnum.TYPE_THEN.getType())) {
+    protected void buildConditions(List<Condition> conditionList, Condition condition) {
+        //这里进行合并逻辑
+        //对于then来说,相邻的2个then会合并成一个condition
+        //对于when来说,相同组的when会合并成一个condition,不同组的when还是会拆开
+        if (condition.getConditionType().equals(ConditionTypeEnum.TYPE_PRE.getType())) {
+            conditionList.add(new PreCondition(condition));
+        } else if (condition.getConditionType().equals(ConditionTypeEnum.TYPE_THEN.getType())) {
             if (conditionList.size() >= 1 &&
                     CollectionUtil.getLast(conditionList) instanceof ThenCondition) {
                 CollectionUtil.getLast(conditionList).getNodeList().addAll(condition.getNodeList());
@@ -46,24 +48,37 @@ public abstract class FlowParser {
             } else {
                 conditionList.add(new WhenCondition(condition));
             }
+        } else if (condition.getConditionType().equals(ConditionTypeEnum.TYPE_FINALLY.getType())) {
+            conditionList.add(new FinallyCondition(condition));
         }
+
+        //每一次build之后,对conditionList进行排序,pre最前面,finally最后
+        //这里为什么要排序,因为在声明的时候,哪怕有人不把pre放最前,finally放最后,但最终也要确保是正确的顺序
+        CollectionUtil.sort(conditionList, (o1, o2) -> {
+            if (o1.getConditionType().equals(ConditionTypeEnum.TYPE_PRE.getType()) || o2.getConditionType().equals(ConditionTypeEnum.TYPE_FINALLY.getType())){
+                return -1;
+            } else if (o2.getConditionType().equals(ConditionTypeEnum.TYPE_PRE.getType()) || o1.getConditionType().equals(ConditionTypeEnum.TYPE_FINALLY.getType())){
+                return 1;
+            }
+            return 0;
+        });
     }
-    
+
     /**
-     *  根据配置的ruleSource查找匹配的资源
+     * 根据配置的ruleSource查找匹配的资源
      */
-    protected Resource[] matchRuleResources(final List<String> pathList)  throws IOException {
+    protected Resource[] matchRuleResources(final List<String> pathList) throws IOException {
         Assert.notEmpty(pathList, "rule source must not be null");
 
         List<Resource> allResource = new ArrayList<>();
-        for (String path : pathList){
+        for (String path : pathList) {
             String locationPattern = path;
             if (!locationPattern.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
                 locationPattern = ResourceUtils.CLASSPATH_URL_PREFIX + locationPattern;
             }
             PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
             Resource[] resources = resolver.getResources(locationPattern);
-            if(ArrayUtil.isEmpty(resources)) {
+            if (ArrayUtil.isEmpty(resources)) {
                 throw new ConfigErrorException("config error,please check rule source property");
             }
             allResource.addAll(ListUtil.toList(resources));
@@ -72,7 +87,7 @@ public abstract class FlowParser {
         //如果有多个资源,检查资源都是同一个类型,如果出现不同类型的配置,则抛出错误提示
         Set<String> fileTypeSet = new HashSet<>();
         allResource.forEach(resource -> fileTypeSet.add(FileUtil.extName(resource.getFilename())));
-        if (fileTypeSet.size() != 1){
+        if (fileTypeSet.size() != 1) {
             throw new ConfigErrorException("config error,please use the same type of configuration");
         }
 

+ 2 - 3
liteflow-core/src/main/java/com/yomahub/liteflow/parser/JsonFlowParser.java

@@ -131,14 +131,13 @@ public abstract class JsonFlowParser extends FlowParser {
         String condArrayStr;
         String[] condArray;
         List<Executable> chainNodeList;
-        List<Condition> conditionList;
+        List<Condition> conditionList = new ArrayList<>();
         String group;
         String errorResume;
         Condition condition;
         String any;
         String chainName = chainObject.getString("name");
         JSONArray conditionArray = chainObject.getJSONArray("condition");
-        conditionList = new ArrayList<>();
         for (Object o : conditionArray) {
             JSONObject condObject = (JSONObject) o;
             String condType = condObject.getString("type");
@@ -199,7 +198,7 @@ public abstract class JsonFlowParser extends FlowParser {
             condition.setAny(any.equals(Boolean.TRUE.toString()));
             condition.setConditionType(condType);
             condition.setNodeList(chainNodeList);
-            super.buildBaseFlowConditions(conditionList, condition);
+            super.buildConditions(conditionList, condition);
         }
         FlowBus.addChain(chainName, new Chain(chainName, conditionList));
     }

+ 2 - 3
liteflow-core/src/main/java/com/yomahub/liteflow/parser/XmlFlowParser.java

@@ -135,10 +135,9 @@ public abstract class XmlFlowParser extends FlowParser {
         Condition condition;
         Element condE;
         List<Executable> chainNodeList;
-        List<Condition> conditionList;
+        List<Condition> conditionList = new ArrayList<>();
 
         String chainName = e.attributeValue("name");
-        conditionList = new ArrayList<>();
         for (Iterator<Element> it = e.elementIterator(); it.hasNext(); ) {
             condE = it.next();
             condArrayStr = condE.attributeValue("value");
@@ -198,7 +197,7 @@ public abstract class XmlFlowParser extends FlowParser {
             condition.setAny(any.equals(Boolean.TRUE.toString()));
             condition.setConditionType(condE.getName());
             condition.setNodeList(chainNodeList);
-            super.buildBaseFlowConditions(conditionList, condition);
+            super.buildConditions(conditionList, condition);
         }
         FlowBus.addChain(chainName, new Chain(chainName, conditionList));
     }

+ 56 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/PreAndFinallySpringbootTest.java

@@ -0,0 +1,56 @@
+package com.yomahub.liteflow.test.preAndFinally;
+
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.entity.data.DefaultSlot;
+import com.yomahub.liteflow.entity.data.LiteflowResponse;
+import com.yomahub.liteflow.test.BaseTest;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.annotation.Resource;
+
+/**
+ * springboot环境下pre节点和finally节点的测试
+ * @author Bryan.Zhang
+ * @since 2.6.4
+ */
+@RunWith(SpringRunner.class)
+@TestPropertySource(value = "classpath:/preAndFinally/application.properties")
+@SpringBootTest(classes = PreAndFinallySpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({"com.yomahub.liteflow.test.preAndFinally.cmp"})
+public class PreAndFinallySpringbootTest extends BaseTest {
+
+    @Resource
+    private FlowExecutor flowExecutor;
+
+    //测试普通的pre和finally节点
+    @Test
+    public void testPreAndFinally1() throws Exception{
+        LiteflowResponse<DefaultSlot> response = flowExecutor.execute2Resp("chain1", "arg");
+        Assert.assertTrue(response.isSuccess());
+        Assert.assertEquals("p1==>p2==>a==>b==>c==>f1==>f2",response.getSlot().printStep());
+    }
+
+    //测试pre和finally节点不放在开头和结尾的情况
+    @Test
+    public void testPreAndFinally2() throws Exception{
+        LiteflowResponse<DefaultSlot> response = flowExecutor.execute2Resp("chain2", "arg");
+        Assert.assertTrue(response.isSuccess());
+        Assert.assertEquals("p1==>p2==>a==>b==>c==>f1==>f2",response.getSlot().printStep());
+    }
+
+    //测试有节点报错是否还执行finally节点的情况,其中d节点会报错,但依旧执行f1,f2节点
+    @Test
+    public void testPreAndFinally3() throws Exception{
+        LiteflowResponse<DefaultSlot> response = flowExecutor.execute2Resp("chain3", "arg");
+        Assert.assertFalse(response.isSuccess());
+        Assert.assertEquals("p1==>p2==>a==>d==>f1==>f2", response.getSlot().printStep());
+    }
+}

+ 20 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/ACmp.java

@@ -0,0 +1,20 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.preAndFinally.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("a")
+public class ACmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("ACmp executed!");
+	}
+}

+ 21 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/BCmp.java

@@ -0,0 +1,21 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.preAndFinally.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("b")
+public class BCmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("BCmp executed!");
+	}
+
+}

+ 21 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/CCmp.java

@@ -0,0 +1,21 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.preAndFinally.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("c")
+public class CCmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("CCmp executed!");
+	}
+
+}

+ 22 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/DCmp.java

@@ -0,0 +1,22 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.preAndFinally.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("d")
+public class DCmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("CCmp executed!");
+		int i = 1/0;
+	}
+
+}

+ 20 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/Finally1Cmp.java

@@ -0,0 +1,20 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.preAndFinally.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("f1")
+public class Finally1Cmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("Finally1Cmp executed!");
+	}
+}

+ 20 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/Finally2Cmp.java

@@ -0,0 +1,20 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.preAndFinally.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("f2")
+public class Finally2Cmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("Finally2Cmp executed!");
+	}
+}

+ 20 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/Pre1Cmp.java

@@ -0,0 +1,20 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.preAndFinally.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("p1")
+public class Pre1Cmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("Pre1Cmp executed!");
+	}
+}

+ 20 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/preAndFinally/cmp/Pre2Cmp.java

@@ -0,0 +1,20 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.preAndFinally.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("p2")
+public class Pre2Cmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("Pre2Cmp executed!");
+	}
+}

+ 1 - 0
liteflow-testcase-springboot/src/test/resources/preAndFinally/application.properties

@@ -0,0 +1 @@
+liteflow.rule-source=preAndFinally/flow.xml

+ 21 - 0
liteflow-testcase-springboot/src/test/resources/preAndFinally/flow.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<flow>
+    <chain name="chain1">
+        <pre value="p1,p2"/>
+        <then value="a,b,c"/>
+        <finally value="f1,f2"/>
+    </chain>
+
+    <chain name="chain2">
+        <then value="a"/>
+        <pre value="p1,p2"/>
+        <finally value="f1,f2"/>
+        <then value="b,c"/>
+    </chain>
+
+    <chain name="chain3">
+        <pre value="p1,p2"/>
+        <then value="a,d,c"/>
+        <finally value="f1,f2"/>
+    </chain>
+</flow>