!188 feature #I7HJFX 循环表达式中支持异步模式
Merge pull request !188 from zhhhhy/dev
This commit is contained in:
commit
bd40dc3c23
|
@ -78,6 +78,7 @@ public class LiteFlowChainELBuilder {
|
||||||
EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DO, Object.class, new DoOperator());
|
EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DO, Object.class, new DoOperator());
|
||||||
EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BREAK, Object.class, new BreakOperator());
|
EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BREAK, Object.class, new BreakOperator());
|
||||||
EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DATA, Object.class, new DataOperator());
|
EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DATA, Object.class, new DataOperator());
|
||||||
|
EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.PARALLEL, Object.class, new ParallelOperator());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LiteFlowChainELBuilder createChain() {
|
public static LiteFlowChainELBuilder createChain() {
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.yomahub.liteflow.builder.el.operator;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.builder.el.operator.base.BaseOperator;
|
||||||
|
import com.yomahub.liteflow.builder.el.operator.base.OperatorHelper;
|
||||||
|
import com.yomahub.liteflow.flow.element.condition.LoopCondition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EL规则中的parallel的操作符
|
||||||
|
*
|
||||||
|
* @author zhhhhy
|
||||||
|
* @since 2.11.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ParallelOperator extends BaseOperator<LoopCondition> {
|
||||||
|
@Override
|
||||||
|
public LoopCondition build(Object[] objects) throws Exception {
|
||||||
|
OperatorHelper.checkObjectSizeEqTwo(objects);
|
||||||
|
|
||||||
|
LoopCondition loopCondition = OperatorHelper.convert(objects[0], LoopCondition.class);
|
||||||
|
|
||||||
|
Boolean parallel = OperatorHelper.convert(objects[1], Boolean.class);
|
||||||
|
loopCondition.setParallel(parallel);
|
||||||
|
return loopCondition;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ package com.yomahub.liteflow.common;
|
||||||
* @author tangkc
|
* @author tangkc
|
||||||
*/
|
*/
|
||||||
public interface ChainConstant {
|
public interface ChainConstant {
|
||||||
|
String PARALLEL = "parallel";
|
||||||
|
|
||||||
String CHAIN = "chain";
|
String CHAIN = "chain";
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,17 @@ import com.yomahub.liteflow.enums.ConditionTypeEnum;
|
||||||
import com.yomahub.liteflow.exception.NoForNodeException;
|
import com.yomahub.liteflow.exception.NoForNodeException;
|
||||||
import com.yomahub.liteflow.flow.element.Executable;
|
import com.yomahub.liteflow.flow.element.Executable;
|
||||||
import com.yomahub.liteflow.flow.element.Node;
|
import com.yomahub.liteflow.flow.element.Node;
|
||||||
|
import com.yomahub.liteflow.flow.parallel.LoopFutureObj;
|
||||||
import com.yomahub.liteflow.slot.DataBus;
|
import com.yomahub.liteflow.slot.DataBus;
|
||||||
import com.yomahub.liteflow.slot.Slot;
|
import com.yomahub.liteflow.slot.Slot;
|
||||||
|
import com.yomahub.liteflow.thread.ExecutorHelper;
|
||||||
import com.yomahub.liteflow.util.LiteFlowProxyUtil;
|
import com.yomahub.liteflow.util.LiteFlowProxyUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 循环次数Condition
|
* 循环次数Condition
|
||||||
*
|
*
|
||||||
|
@ -45,8 +52,9 @@ public class ForCondition extends LoopCondition {
|
||||||
// 获取Break节点
|
// 获取Break节点
|
||||||
Executable breakItem = this.getBreakItem();
|
Executable breakItem = this.getBreakItem();
|
||||||
|
|
||||||
try{
|
try {
|
||||||
// 循环执行
|
if (!isParallel()) {
|
||||||
|
//串行循环执行
|
||||||
for (int i = 0; i < forCount; i++) {
|
for (int i = 0; i < forCount; i++) {
|
||||||
executableItem.setCurrChainId(this.getCurrChainId());
|
executableItem.setCurrChainId(this.getCurrChainId());
|
||||||
// 设置循环index
|
// 设置循环index
|
||||||
|
@ -63,7 +71,31 @@ public class ForCondition extends LoopCondition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}finally {
|
}else{
|
||||||
|
//并行循环执行
|
||||||
|
//存储所有的并行执行子项的CompletableFuture
|
||||||
|
List<CompletableFuture<LoopFutureObj>> futureList = new ArrayList<>();
|
||||||
|
//获取并行循环的线程池
|
||||||
|
ExecutorService parallelExecutor = ExecutorHelper.loadInstance().buildLoopParallelExecutor();
|
||||||
|
for (int i = 0; i < forCount; i++){
|
||||||
|
//提交异步任务
|
||||||
|
CompletableFuture<LoopFutureObj> future =
|
||||||
|
CompletableFuture.supplyAsync(new LoopParallelSupplier(executableItem, this.getCurrChainId(), slotIndex, i), parallelExecutor);
|
||||||
|
futureList.add(future);
|
||||||
|
if (ObjectUtil.isNotNull(breakItem)) {
|
||||||
|
breakItem.setCurrChainId(this.getCurrChainId());
|
||||||
|
setLoopIndex(breakItem, i);
|
||||||
|
breakItem.execute(slotIndex);
|
||||||
|
boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
|
||||||
|
if (isBreak) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//等待所有的异步执行完毕
|
||||||
|
handleFutureList(futureList);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
removeLoopIndex(executableItem);
|
removeLoopIndex(executableItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,17 @@ import com.yomahub.liteflow.enums.ConditionTypeEnum;
|
||||||
import com.yomahub.liteflow.exception.NoIteratorNodeException;
|
import com.yomahub.liteflow.exception.NoIteratorNodeException;
|
||||||
import com.yomahub.liteflow.flow.element.Executable;
|
import com.yomahub.liteflow.flow.element.Executable;
|
||||||
import com.yomahub.liteflow.flow.element.Node;
|
import com.yomahub.liteflow.flow.element.Node;
|
||||||
|
import com.yomahub.liteflow.flow.parallel.LoopFutureObj;
|
||||||
import com.yomahub.liteflow.slot.DataBus;
|
import com.yomahub.liteflow.slot.DataBus;
|
||||||
import com.yomahub.liteflow.slot.Slot;
|
import com.yomahub.liteflow.slot.Slot;
|
||||||
|
import com.yomahub.liteflow.thread.ExecutorHelper;
|
||||||
import com.yomahub.liteflow.util.LiteFlowProxyUtil;
|
import com.yomahub.liteflow.util.LiteFlowProxyUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
public class IteratorCondition extends LoopCondition {
|
public class IteratorCondition extends LoopCondition {
|
||||||
|
|
||||||
|
@ -41,8 +47,10 @@ public class IteratorCondition extends LoopCondition {
|
||||||
// 获取Break节点
|
// 获取Break节点
|
||||||
Executable breakItem = this.getBreakItem();
|
Executable breakItem = this.getBreakItem();
|
||||||
|
|
||||||
try{
|
try {
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
if (!this.isParallel()) {
|
||||||
|
//原本的串行循环执行
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
Object itObj = it.next();
|
Object itObj = it.next();
|
||||||
|
|
||||||
|
@ -66,7 +74,34 @@ public class IteratorCondition extends LoopCondition {
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}finally{
|
} else {
|
||||||
|
//并行循环执行
|
||||||
|
//存储所有的并行执行子项的CompletableFuture
|
||||||
|
List<CompletableFuture<LoopFutureObj>> futureList = new ArrayList<>();
|
||||||
|
//获取并行循环的线程池
|
||||||
|
ExecutorService parallelExecutor = ExecutorHelper.loadInstance().buildLoopParallelExecutor();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Object itObj = it.next();
|
||||||
|
//提交异步任务
|
||||||
|
CompletableFuture<LoopFutureObj> future =
|
||||||
|
CompletableFuture.supplyAsync(new LoopParallelSupplier(executableItem, this.getCurrChainId(), slotIndex, index, itObj), parallelExecutor);
|
||||||
|
futureList.add(future);
|
||||||
|
//break判断
|
||||||
|
if (ObjectUtil.isNotNull(breakItem)) {
|
||||||
|
breakItem.setCurrChainId(this.getCurrChainId());
|
||||||
|
setLoopIndex(breakItem, index);
|
||||||
|
breakItem.execute(slotIndex);
|
||||||
|
boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
|
||||||
|
if (isBreak) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
//等待所有的异步执行完毕
|
||||||
|
handleFutureList(futureList);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
removeLoopIndex(executableItem);
|
removeLoopIndex(executableItem);
|
||||||
removeCurrLoopObject(executableItem);
|
removeCurrLoopObject(executableItem);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,11 @@ import com.yomahub.liteflow.flow.element.Chain;
|
||||||
import com.yomahub.liteflow.flow.element.Condition;
|
import com.yomahub.liteflow.flow.element.Condition;
|
||||||
import com.yomahub.liteflow.flow.element.Executable;
|
import com.yomahub.liteflow.flow.element.Executable;
|
||||||
import com.yomahub.liteflow.flow.element.Node;
|
import com.yomahub.liteflow.flow.element.Node;
|
||||||
|
import com.yomahub.liteflow.flow.parallel.LoopFutureObj;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 循环Condition的抽象类 主要继承对象有ForCondition和WhileCondition
|
* 循环Condition的抽象类 主要继承对象有ForCondition和WhileCondition
|
||||||
|
@ -12,6 +17,8 @@ import com.yomahub.liteflow.flow.element.Node;
|
||||||
* @since 2.9.0
|
* @since 2.9.0
|
||||||
*/
|
*/
|
||||||
public abstract class LoopCondition extends Condition {
|
public abstract class LoopCondition extends Condition {
|
||||||
|
//判断循环是否并行执行,默认为false
|
||||||
|
private boolean parallel = false;
|
||||||
|
|
||||||
protected Executable getBreakItem() {
|
protected Executable getBreakItem() {
|
||||||
return this.getExecutableOne(ConditionKey.BREAK_KEY);
|
return this.getExecutableOne(ConditionKey.BREAK_KEY);
|
||||||
|
@ -32,12 +39,10 @@ public abstract class LoopCondition extends Condition {
|
||||||
protected void setLoopIndex(Executable executableItem, int index) {
|
protected void setLoopIndex(Executable executableItem, int index) {
|
||||||
if (executableItem instanceof Chain) {
|
if (executableItem instanceof Chain) {
|
||||||
((Chain) executableItem).getConditionList().forEach(condition -> setLoopIndex(condition, index));
|
((Chain) executableItem).getConditionList().forEach(condition -> setLoopIndex(condition, index));
|
||||||
}
|
} else if (executableItem instanceof Condition) {
|
||||||
else if (executableItem instanceof Condition) {
|
|
||||||
((Condition) executableItem).getExecutableGroup()
|
((Condition) executableItem).getExecutableGroup()
|
||||||
.forEach((key, value) -> value.forEach(executable -> setLoopIndex(executable, index)));
|
.forEach((key, value) -> value.forEach(executable -> setLoopIndex(executable, index)));
|
||||||
}
|
} else if (executableItem instanceof Node) {
|
||||||
else if (executableItem instanceof Node) {
|
|
||||||
((Node) executableItem).setLoopIndex(index);
|
((Node) executableItem).setLoopIndex(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,40 +50,98 @@ public abstract class LoopCondition extends Condition {
|
||||||
protected void setCurrLoopObject(Executable executableItem, Object obj) {
|
protected void setCurrLoopObject(Executable executableItem, Object obj) {
|
||||||
if (executableItem instanceof Chain) {
|
if (executableItem instanceof Chain) {
|
||||||
((Chain) executableItem).getConditionList().forEach(condition -> setCurrLoopObject(condition, obj));
|
((Chain) executableItem).getConditionList().forEach(condition -> setCurrLoopObject(condition, obj));
|
||||||
}
|
} else if (executableItem instanceof Condition) {
|
||||||
else if (executableItem instanceof Condition) {
|
|
||||||
((Condition) executableItem).getExecutableGroup()
|
((Condition) executableItem).getExecutableGroup()
|
||||||
.forEach((key, value) -> value.forEach(executable -> setCurrLoopObject(executable, obj)));
|
.forEach((key, value) -> value.forEach(executable -> setCurrLoopObject(executable, obj)));
|
||||||
}
|
} else if (executableItem instanceof Node) {
|
||||||
else if (executableItem instanceof Node) {
|
|
||||||
((Node) executableItem).setCurrLoopObject(obj);
|
((Node) executableItem).setCurrLoopObject(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeLoopIndex(Executable executableItem){
|
protected void removeLoopIndex(Executable executableItem) {
|
||||||
if (executableItem instanceof Chain) {
|
if (executableItem instanceof Chain) {
|
||||||
((Chain) executableItem).getConditionList().forEach(this::removeLoopIndex);
|
((Chain) executableItem).getConditionList().forEach(this::removeLoopIndex);
|
||||||
}
|
} else if (executableItem instanceof Condition) {
|
||||||
else if (executableItem instanceof Condition) {
|
|
||||||
((Condition) executableItem).getExecutableGroup()
|
((Condition) executableItem).getExecutableGroup()
|
||||||
.forEach((key, value) -> value.forEach(this::removeLoopIndex));
|
.forEach((key, value) -> value.forEach(this::removeLoopIndex));
|
||||||
}
|
} else if (executableItem instanceof Node) {
|
||||||
else if (executableItem instanceof Node) {
|
|
||||||
((Node) executableItem).removeLoopIndex();
|
((Node) executableItem).removeLoopIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeCurrLoopObject(Executable executableItem){
|
protected void removeCurrLoopObject(Executable executableItem) {
|
||||||
if (executableItem instanceof Chain) {
|
if (executableItem instanceof Chain) {
|
||||||
((Chain) executableItem).getConditionList().forEach(this::removeCurrLoopObject);
|
((Chain) executableItem).getConditionList().forEach(this::removeCurrLoopObject);
|
||||||
}
|
} else if (executableItem instanceof Condition) {
|
||||||
else if (executableItem instanceof Condition) {
|
|
||||||
((Condition) executableItem).getExecutableGroup()
|
((Condition) executableItem).getExecutableGroup()
|
||||||
.forEach((key, value) -> value.forEach(this::removeCurrLoopObject));
|
.forEach((key, value) -> value.forEach(this::removeCurrLoopObject));
|
||||||
}
|
} else if (executableItem instanceof Node) {
|
||||||
else if (executableItem instanceof Node) {
|
|
||||||
((Node) executableItem).removeCurrLoopObject();
|
((Node) executableItem).removeCurrLoopObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isParallel() {
|
||||||
|
return parallel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallel(boolean parallel) {
|
||||||
|
this.parallel = parallel;
|
||||||
|
}
|
||||||
|
|
||||||
|
//循环并行执行的futureList处理
|
||||||
|
protected void handleFutureList(List<CompletableFuture<LoopFutureObj>> futureList)throws Exception{
|
||||||
|
CompletableFuture<?> resultCompletableFuture = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[]{}));
|
||||||
|
resultCompletableFuture.get();
|
||||||
|
//获取所有的执行结果,如果有失败的,那么需要抛出异常
|
||||||
|
for (CompletableFuture<LoopFutureObj> future : futureList) {
|
||||||
|
LoopFutureObj loopFutureObj = future.get();
|
||||||
|
if (!loopFutureObj.isSuccess()) {
|
||||||
|
throw loopFutureObj.getEx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 循环并行执行的Supplier封装
|
||||||
|
public class LoopParallelSupplier implements Supplier<LoopFutureObj> {
|
||||||
|
private final Executable executableItem;
|
||||||
|
private final String currChainId;
|
||||||
|
private final Integer slotIndex;
|
||||||
|
private final Integer loopIndex;
|
||||||
|
private final Object itObj;
|
||||||
|
|
||||||
|
public LoopParallelSupplier(Executable executableItem, String currChainId, Integer slotIndex, Integer loopIndex) {
|
||||||
|
this.executableItem = executableItem;
|
||||||
|
this.currChainId = currChainId;
|
||||||
|
this.slotIndex = slotIndex;
|
||||||
|
this.loopIndex = loopIndex;
|
||||||
|
this.itObj = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoopParallelSupplier(Executable executableItem, String currChainId, Integer slotIndex, Integer loopIndex, Object itObj) {
|
||||||
|
this.executableItem = executableItem;
|
||||||
|
this.currChainId = currChainId;
|
||||||
|
this.slotIndex = slotIndex;
|
||||||
|
this.loopIndex = loopIndex;
|
||||||
|
this.itObj = itObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoopFutureObj get() {
|
||||||
|
try {
|
||||||
|
executableItem.setCurrChainId(this.currChainId);
|
||||||
|
// 设置循环index
|
||||||
|
setLoopIndex(executableItem, loopIndex);
|
||||||
|
//IteratorCondition的情况下,需要设置当前循环对象
|
||||||
|
if(itObj != null){
|
||||||
|
setCurrLoopObject(executableItem, itObj);
|
||||||
|
}
|
||||||
|
executableItem.execute(slotIndex);
|
||||||
|
return LoopFutureObj.success(executableItem.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return LoopFutureObj.fail(executableItem.getId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,13 @@ import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.yomahub.liteflow.enums.ConditionTypeEnum;
|
import com.yomahub.liteflow.enums.ConditionTypeEnum;
|
||||||
import com.yomahub.liteflow.flow.element.Executable;
|
import com.yomahub.liteflow.flow.element.Executable;
|
||||||
import com.yomahub.liteflow.flow.element.Node;
|
import com.yomahub.liteflow.flow.element.Node;
|
||||||
|
import com.yomahub.liteflow.flow.parallel.LoopFutureObj;
|
||||||
|
import com.yomahub.liteflow.thread.ExecutorHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 循环条件Condition
|
* 循环条件Condition
|
||||||
|
@ -30,6 +37,8 @@ public class WhileCondition extends LoopCondition {
|
||||||
|
|
||||||
// 循环执行
|
// 循环执行
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
if(!this.isParallel()){
|
||||||
|
//串行循环
|
||||||
while (getWhileResult(slotIndex)) {
|
while (getWhileResult(slotIndex)) {
|
||||||
executableItem.setCurrChainId(this.getCurrChainId());
|
executableItem.setCurrChainId(this.getCurrChainId());
|
||||||
setLoopIndex(executableItem, index);
|
setLoopIndex(executableItem, index);
|
||||||
|
@ -46,6 +55,30 @@ public class WhileCondition extends LoopCondition {
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
//并行循环逻辑
|
||||||
|
List<CompletableFuture<LoopFutureObj>> futureList = new ArrayList<>();
|
||||||
|
//获取并行循环的线程池
|
||||||
|
ExecutorService parallelExecutor = ExecutorHelper.loadInstance().buildLoopParallelExecutor();
|
||||||
|
while (getWhileResult(slotIndex)){
|
||||||
|
CompletableFuture<LoopFutureObj> future =
|
||||||
|
CompletableFuture.supplyAsync(new LoopParallelSupplier(executableItem, this.getCurrChainId(), slotIndex, index), parallelExecutor);
|
||||||
|
futureList.add(future);
|
||||||
|
//break判断
|
||||||
|
if (ObjectUtil.isNotNull(breakItem)) {
|
||||||
|
breakItem.setCurrChainId(this.getCurrChainId());
|
||||||
|
setLoopIndex(breakItem, index);
|
||||||
|
breakItem.execute(slotIndex);
|
||||||
|
boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
|
||||||
|
if (isBreak) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
//等待所有的异步执行完毕
|
||||||
|
handleFutureList(futureList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean getWhileResult(Integer slotIndex) throws Exception {
|
private boolean getWhileResult(Integer slotIndex) throws Exception {
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.yomahub.liteflow.flow.parallel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 并行循环各个并行子项的执行结果对象
|
||||||
|
* 如果该子项执行成功,则success为true,ex为null
|
||||||
|
* 否则success为false,ex为程序抛出异常
|
||||||
|
*
|
||||||
|
* @author zhhhhy
|
||||||
|
* @since 2.11.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LoopFutureObj {
|
||||||
|
private String executorName;
|
||||||
|
private boolean success;
|
||||||
|
private Exception ex;
|
||||||
|
|
||||||
|
|
||||||
|
public static LoopFutureObj success(String executorName) {
|
||||||
|
LoopFutureObj result = new LoopFutureObj();
|
||||||
|
result.setSuccess(true);
|
||||||
|
result.setExecutorName(executorName);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LoopFutureObj fail(String executorName, Exception ex) {
|
||||||
|
LoopFutureObj result = new LoopFutureObj();
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setExecutorName(executorName);
|
||||||
|
result.setEx(ex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Exception getEx() {
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExecutorName() {
|
||||||
|
return executorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEx(Exception ex) {
|
||||||
|
this.ex = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExecutorName(String executorName) {
|
||||||
|
this.executorName = executorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuccess(boolean success) {
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
}
|
|
@ -103,6 +103,15 @@ public class LiteflowConfig {
|
||||||
// 规则文件/脚本文件变更监听
|
// 规则文件/脚本文件变更监听
|
||||||
private Boolean enableMonitorFile = Boolean.FALSE;
|
private Boolean enableMonitorFile = Boolean.FALSE;
|
||||||
|
|
||||||
|
//并行循环线程池所用class路径
|
||||||
|
private String parallelLoopExecutorClass;
|
||||||
|
|
||||||
|
//使用默认并行循环线程池时,最大线程数
|
||||||
|
private Integer parallelMaxWorkers;
|
||||||
|
|
||||||
|
//使用默认并行循环线程池时,最大队列数
|
||||||
|
private Integer parallelQueueLimit;
|
||||||
|
|
||||||
public Boolean getEnableMonitorFile() {
|
public Boolean getEnableMonitorFile() {
|
||||||
return enableMonitorFile;
|
return enableMonitorFile;
|
||||||
}
|
}
|
||||||
|
@ -409,4 +418,40 @@ public class LiteflowConfig {
|
||||||
public void setWhenMaxWaitTimeUnit(TimeUnit whenMaxWaitTimeUnit) {
|
public void setWhenMaxWaitTimeUnit(TimeUnit whenMaxWaitTimeUnit) {
|
||||||
this.whenMaxWaitTimeUnit = whenMaxWaitTimeUnit;
|
this.whenMaxWaitTimeUnit = whenMaxWaitTimeUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getParallelMaxWorkers() {
|
||||||
|
if(ObjectUtil.isNull(parallelMaxWorkers)){
|
||||||
|
return 16;
|
||||||
|
}else{
|
||||||
|
return parallelMaxWorkers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelMaxWorkers(Integer parallelMaxWorkers) {
|
||||||
|
this.parallelMaxWorkers = parallelMaxWorkers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getParallelQueueLimit() {
|
||||||
|
if(ObjectUtil.isNull(parallelQueueLimit)){
|
||||||
|
return 512;
|
||||||
|
}else{
|
||||||
|
return parallelQueueLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelQueueLimit(Integer parallelQueueLimit) {
|
||||||
|
this.parallelQueueLimit = parallelQueueLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParallelLoopExecutorClass() {
|
||||||
|
if (StrUtil.isBlank(parallelLoopExecutorClass)) {
|
||||||
|
return "com.yomahub.liteflow.thread.LiteFlowDefaultParallelLoopExecutorBuilder";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return parallelLoopExecutorClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void setParallelLoopExecutorClass(String parallelLoopExecutorClass) {
|
||||||
|
this.parallelLoopExecutorClass = parallelLoopExecutorClass;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,12 @@ public class ExecutorHelper {
|
||||||
return getExecutorService(clazz);
|
return getExecutorService(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//构造并行循环的线程池
|
||||||
|
public ExecutorService buildLoopParallelExecutor(){
|
||||||
|
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
|
||||||
|
return getExecutorService(liteflowConfig.getParallelLoopExecutorClass());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据线程执行构建者Class类名获取ExecutorService实例
|
* 根据线程执行构建者Class类名获取ExecutorService实例
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.yomahub.liteflow.thread;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfig;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfigGetter;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LiteFlow默认的并行循环执行器实现
|
||||||
|
*
|
||||||
|
* @author zhhhhy
|
||||||
|
* @since 2.11.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LiteFlowDefaultParallelLoopExecutorBuilder implements ExecutorBuilder {
|
||||||
|
@Override
|
||||||
|
public ExecutorService buildExecutor() {
|
||||||
|
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
|
||||||
|
// 只有在非spring的场景下liteflowConfig才会为null
|
||||||
|
if (ObjectUtil.isNull(liteflowConfig)) {
|
||||||
|
liteflowConfig = new LiteflowConfig();
|
||||||
|
}
|
||||||
|
return buildDefaultExecutor(liteflowConfig.getParallelMaxWorkers(), liteflowConfig.getParallelMaxWorkers(),
|
||||||
|
liteflowConfig.getParallelQueueLimit(), "loop-thead-");
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,9 @@ public class LiteflowAutoConfiguration {
|
||||||
liteflowConfig.setMainExecutorClass(property.getMainExecutorClass());
|
liteflowConfig.setMainExecutorClass(property.getMainExecutorClass());
|
||||||
liteflowConfig.setPrintExecutionLog(property.isPrintExecutionLog());
|
liteflowConfig.setPrintExecutionLog(property.isPrintExecutionLog());
|
||||||
liteflowConfig.setSubstituteCmpClass(property.getSubstituteCmpClass());
|
liteflowConfig.setSubstituteCmpClass(property.getSubstituteCmpClass());
|
||||||
|
liteflowConfig.setParallelMaxWorkers(property.getParallelMaxWorkers());
|
||||||
|
liteflowConfig.setParallelQueueLimit(property.getParallelQueueLimit());
|
||||||
|
liteflowConfig.setParallelLoopExecutorClass(property.getParallelLoopExecutorClass());
|
||||||
return liteflowConfig;
|
return liteflowConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,15 @@ public class LiteflowProperty {
|
||||||
// 替补组件的class路径
|
// 替补组件的class路径
|
||||||
private String substituteCmpClass;
|
private String substituteCmpClass;
|
||||||
|
|
||||||
|
//并行循环线程池类路径
|
||||||
|
private String parallelLoopExecutorClass;
|
||||||
|
|
||||||
|
//使用默认并行循环线程池时,最大线程数
|
||||||
|
private Integer parallelMaxWorkers;
|
||||||
|
|
||||||
|
//使用默认并行循环线程池时,最大队列数
|
||||||
|
private Integer parallelQueueLimit;
|
||||||
|
|
||||||
public boolean isEnable() {
|
public boolean isEnable() {
|
||||||
return enable;
|
return enable;
|
||||||
}
|
}
|
||||||
|
@ -219,4 +228,27 @@ public class LiteflowProperty {
|
||||||
this.ruleSourceExtData = ruleSourceExtData;
|
this.ruleSourceExtData = ruleSourceExtData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getParallelLoopExecutorClass() {
|
||||||
|
return parallelLoopExecutorClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelLoopExecutorClass(String parallelLoopExecutorClass) {
|
||||||
|
this.parallelLoopExecutorClass = parallelLoopExecutorClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getParallelMaxWorkers() {
|
||||||
|
return parallelMaxWorkers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelMaxWorkers(Integer parallelMaxWorkers) {
|
||||||
|
this.parallelMaxWorkers = parallelMaxWorkers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getParallelQueueLimit() {
|
||||||
|
return parallelQueueLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelQueueLimit(Integer parallelQueueLimit) {
|
||||||
|
this.parallelQueueLimit = parallelQueueLimit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,14 @@ public class LiteflowProperty {
|
||||||
// 规则文件/脚本文件变更监听
|
// 规则文件/脚本文件变更监听
|
||||||
private Boolean enableMonitorFile;
|
private Boolean enableMonitorFile;
|
||||||
|
|
||||||
|
private String parallelLoopExecutorClass;
|
||||||
|
|
||||||
|
//使用默认并行循环线程池时,最大线程数
|
||||||
|
private Integer parallelMaxWorkers;
|
||||||
|
|
||||||
|
//使用默认并行循环线程池时,最大队列数
|
||||||
|
private Integer parallelQueueLimit;
|
||||||
|
|
||||||
public Boolean getEnableMonitorFile() {
|
public Boolean getEnableMonitorFile() {
|
||||||
return enableMonitorFile;
|
return enableMonitorFile;
|
||||||
}
|
}
|
||||||
|
@ -257,4 +265,28 @@ public class LiteflowProperty {
|
||||||
public void setWhenMaxWaitTimeUnit(TimeUnit whenMaxWaitTimeUnit) {
|
public void setWhenMaxWaitTimeUnit(TimeUnit whenMaxWaitTimeUnit) {
|
||||||
this.whenMaxWaitTimeUnit = whenMaxWaitTimeUnit;
|
this.whenMaxWaitTimeUnit = whenMaxWaitTimeUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getParallelLoopExecutorClass() {
|
||||||
|
return parallelLoopExecutorClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelLoopExecutorClass(String parallelLoopExecutorClass) {
|
||||||
|
this.parallelLoopExecutorClass = parallelLoopExecutorClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getParallelMaxWorkers() {
|
||||||
|
return parallelMaxWorkers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelMaxWorkers(Integer parallelMaxWorkers) {
|
||||||
|
this.parallelMaxWorkers = parallelMaxWorkers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getParallelQueueLimit() {
|
||||||
|
return parallelQueueLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParallelQueueLimit(Integer parallelQueueLimit) {
|
||||||
|
this.parallelQueueLimit = parallelQueueLimit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,9 @@ public class LiteflowPropertyAutoConfiguration {
|
||||||
liteflowConfig.setPrintExecutionLog(property.isPrintExecutionLog());
|
liteflowConfig.setPrintExecutionLog(property.isPrintExecutionLog());
|
||||||
liteflowConfig.setSubstituteCmpClass(property.getSubstituteCmpClass());
|
liteflowConfig.setSubstituteCmpClass(property.getSubstituteCmpClass());
|
||||||
liteflowConfig.setEnableMonitorFile(property.getEnableMonitorFile());
|
liteflowConfig.setEnableMonitorFile(property.getEnableMonitorFile());
|
||||||
|
liteflowConfig.setParallelMaxWorkers(property.getParallelMaxWorkers());
|
||||||
|
liteflowConfig.setParallelQueueLimit(property.getParallelQueueLimit());
|
||||||
|
liteflowConfig.setParallelLoopExecutorClass(property.getParallelLoopExecutorClass());
|
||||||
return liteflowConfig;
|
return liteflowConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户自定义带状态码的异常
|
||||||
|
*/
|
||||||
|
public class CustomStatefulException extends LiteFlowException {
|
||||||
|
|
||||||
|
public CustomStatefulException(String code, String message) {
|
||||||
|
super(code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfig;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfigGetter;
|
||||||
|
import com.yomahub.liteflow.thread.ExecutorBuilder;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
public class CustomThreadExecutor implements ExecutorBuilder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExecutorService buildExecutor() {
|
||||||
|
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
|
||||||
|
// 只有在非spring的场景下liteflowConfig才会为null
|
||||||
|
if (ObjectUtil.isNull(liteflowConfig)) {
|
||||||
|
liteflowConfig = new LiteflowConfig();
|
||||||
|
}
|
||||||
|
return buildDefaultExecutor(liteflowConfig.getParallelMaxWorkers(), liteflowConfig.getParallelMaxWorkers(),
|
||||||
|
liteflowConfig.getParallelQueueLimit(), "customer-loop-thead-");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
|
import com.yomahub.liteflow.core.FlowExecutor;
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
import com.yomahub.liteflow.flow.LiteflowResponse;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import com.yomahub.liteflow.test.BaseTest;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
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.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* springboot环境EL异步循环测试
|
||||||
|
*
|
||||||
|
* @author zhhhhy
|
||||||
|
* @since 2.11.0
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@TestPropertySource(value = "classpath:/parallelLoop/application.properties")
|
||||||
|
@SpringBootTest(classes = ParallelLoopELDeclMultiSpringbootTest.class)
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
@ComponentScan({ "com.yomahub.liteflow.test.parallelLoop.cmp" })
|
||||||
|
public class ParallelLoopELDeclMultiSpringbootTest extends BaseTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FlowExecutor flowExecutor;
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数直接在el中定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop1() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数由For组件定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop2() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的BREAK组件能够正常发挥作用
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop3() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中,主线程是否会正常等待所有并行子项完成后再继续执行
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop4() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain4", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
//测试并行FOR循环中,某个并行子项抛出异常
|
||||||
|
public void testParallelLoop5() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain5", "arg");
|
||||||
|
Assertions.assertFalse(response.isSuccess());
|
||||||
|
Assertions.assertEquals("300", response.getCode());
|
||||||
|
Assertions.assertNotNull(response.getCause());
|
||||||
|
Assertions.assertTrue(response.getCause() instanceof LiteFlowException);
|
||||||
|
Assertions.assertNotNull(response.getSlot());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的条件循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop6() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的迭代循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop7() throws Exception {
|
||||||
|
List<String> list = ListUtil.toList("1", "2", "3", "4", "5");
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain7", list);
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的index
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop8() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
String regex = "(?!.*(.).*\\1)[0-4]{5}"; //匹配不包含重复数字的0-4的5位数字
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
//e1,e2,e3分别并行执行5次,因此单个循环的顺序可以是任意的
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e1")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e2")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e3")).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//测试自定义线程池配置是否生效
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop9() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain9", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
Assertions.assertTrue(context.getData("threadName").toString().startsWith("customer-loop-thead"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import com.yomahub.liteflow.test.exception.CustomStatefulException;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@LiteflowComponent
|
||||||
|
public class CmpConfig {
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "a")
|
||||||
|
public void processA(NodeComponent bindCmp) {
|
||||||
|
System.out.println("ACmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "b")
|
||||||
|
public void processB(NodeComponent bindCmp) {
|
||||||
|
|
||||||
|
System.out.println("BCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "c")
|
||||||
|
public void processC(NodeComponent bindCmp) {
|
||||||
|
System.out.println("CCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "d")
|
||||||
|
public void processD(NodeComponent bindCmp) {
|
||||||
|
DefaultContext context = bindCmp.getFirstContextBean();
|
||||||
|
String key = "test";
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
int count = context.getData(key);
|
||||||
|
context.setData(key, ++count);
|
||||||
|
} else {
|
||||||
|
context.setData(key, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "e")
|
||||||
|
public void processE(NodeComponent bindCmp) {
|
||||||
|
synchronized (this){
|
||||||
|
DefaultContext context = bindCmp.getFirstContextBean();
|
||||||
|
String key = StrUtil.format("{}_{}", "loop", bindCmp.getTag());
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
String loopStr = context.getData(key);
|
||||||
|
String loopStrReturn = StrUtil.format("{}{}", loopStr, bindCmp.getLoopIndex());
|
||||||
|
context.setData(key, loopStrReturn);
|
||||||
|
} else {
|
||||||
|
context.setData(key, bindCmp.getLoopIndex().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "f")
|
||||||
|
public void processF(NodeComponent bindCmp){
|
||||||
|
try {
|
||||||
|
System.out.println("FCmp start to sleep 5s");
|
||||||
|
Thread.sleep(5000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
System.out.println("FCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "g")
|
||||||
|
public void processG(NodeComponent bindCmp){
|
||||||
|
if(bindCmp.getLoopIndex()==1){
|
||||||
|
throw new CustomStatefulException("300", "chain execute custom stateful execption");
|
||||||
|
}
|
||||||
|
System.out.println("GCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "h")
|
||||||
|
public void processH(NodeComponent bindCmp){
|
||||||
|
DefaultContext context = bindCmp.getFirstContextBean();
|
||||||
|
context.setData("threadName", Thread.currentThread().getName());
|
||||||
|
System.out.println("HCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_ITERATOR, nodeId = "it", nodeType = NodeTypeEnum.ITERATOR)
|
||||||
|
public Iterator<?> processIT(NodeComponent bindCmp) {
|
||||||
|
List<String> list = bindCmp.getRequestData();
|
||||||
|
return list.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_FOR, nodeId = "x", nodeType = NodeTypeEnum.FOR)
|
||||||
|
public int processX(NodeComponent bindCmp) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_BREAK, nodeId = "y", nodeType = NodeTypeEnum.BREAK)
|
||||||
|
public boolean processY(NodeComponent bindCmp) {
|
||||||
|
DefaultContext context = bindCmp.getFirstContextBean();
|
||||||
|
int count = 0;
|
||||||
|
if(context.hasData("test")) {
|
||||||
|
count = context.getData("test");
|
||||||
|
}
|
||||||
|
return count > 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_WHILE, nodeId = "z", nodeType = NodeTypeEnum.WHILE)
|
||||||
|
public boolean processZ(NodeComponent bindCmp) {
|
||||||
|
DefaultContext context = bindCmp.getFirstContextBean();
|
||||||
|
String key = "test";
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
int count = context.getData("test");
|
||||||
|
return count < 5;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
liteflow.rule-source=parallelLoop/flow.xml
|
||||||
|
liteflow.parallel-max-workers = 10
|
||||||
|
liteflow.parallel-queue-limit = 1024
|
||||||
|
liteflow.parallel-loop-executor-class =com.yomahub.liteflow.test.parallelLoop.CustomThreadExecutor
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<flow>
|
||||||
|
<chain name="chain1">
|
||||||
|
FOR(2).DO(THEN(a,b,c));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain2">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,c));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain3">
|
||||||
|
FOR(100).parallel(true).DO(THEN(a,b,d)).BREAK(y);
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain4">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,f));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain5">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,g));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain6">
|
||||||
|
WHILE(z).parallel(true).DO(THEN(a,d));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain7">
|
||||||
|
ITERATOR(it).parallel(true).DO(THEN(a,b));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain8">
|
||||||
|
FOR(5).parallel(true).DO(
|
||||||
|
WHEN(
|
||||||
|
THEN(a,e.tag("e1")),
|
||||||
|
THEN(c,e.tag("e2")),
|
||||||
|
THEN(b,e.tag("e3"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
|
||||||
|
<chain name="chain9">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,h));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
</flow>
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户自定义带状态码的异常
|
||||||
|
*/
|
||||||
|
public class CustomStatefulException extends LiteFlowException {
|
||||||
|
|
||||||
|
public CustomStatefulException(String code, String message) {
|
||||||
|
super(code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfig;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfigGetter;
|
||||||
|
import com.yomahub.liteflow.thread.ExecutorBuilder;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
public class CustomThreadExecutor implements ExecutorBuilder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExecutorService buildExecutor() {
|
||||||
|
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
|
||||||
|
// 只有在非spring的场景下liteflowConfig才会为null
|
||||||
|
if (ObjectUtil.isNull(liteflowConfig)) {
|
||||||
|
liteflowConfig = new LiteflowConfig();
|
||||||
|
}
|
||||||
|
return buildDefaultExecutor(liteflowConfig.getParallelMaxWorkers(), liteflowConfig.getParallelMaxWorkers(),
|
||||||
|
liteflowConfig.getParallelQueueLimit(), "customer-loop-thead-");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
|
import com.yomahub.liteflow.core.FlowExecutor;
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
import com.yomahub.liteflow.flow.LiteflowResponse;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import com.yomahub.liteflow.test.BaseTest;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
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.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* springboot环境EL异步循环测试
|
||||||
|
*
|
||||||
|
* @author zhhhhy
|
||||||
|
* @since 2.11.0
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@TestPropertySource(value = "classpath:/parallelLoop/application.properties")
|
||||||
|
@SpringBootTest(classes = ParallelLoopELDeclSpringbootTest.class)
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
@ComponentScan({ "com.yomahub.liteflow.test.parallelLoop.cmp" })
|
||||||
|
public class ParallelLoopELDeclSpringbootTest extends BaseTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FlowExecutor flowExecutor;
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数直接在el中定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop1() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数由For组件定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop2() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的BREAK组件能够正常发挥作用
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop3() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中,主线程是否会正常等待所有并行子项完成后再继续执行
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop4() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain4", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
//测试并行FOR循环中,某个并行子项抛出异常
|
||||||
|
public void testParallelLoop5() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain5", "arg");
|
||||||
|
Assertions.assertFalse(response.isSuccess());
|
||||||
|
Assertions.assertEquals("300", response.getCode());
|
||||||
|
Assertions.assertNotNull(response.getCause());
|
||||||
|
Assertions.assertTrue(response.getCause() instanceof LiteFlowException);
|
||||||
|
Assertions.assertNotNull(response.getSlot());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的条件循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop6() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的迭代循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop7() throws Exception {
|
||||||
|
List<String> list = ListUtil.toList("1", "2", "3", "4", "5");
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain7", list);
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的index
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop8() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
String regex = "(?!.*(.).*\\1)[0-4]{5}"; //匹配不包含重复数字的0-4的5位数字
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
//e1,e2,e3分别并行执行5次,因此单个循环的顺序可以是任意的
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e1")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e2")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e3")).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//测试自定义线程池配置是否生效
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop9() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain9", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
Assertions.assertTrue(context.getData("threadName").toString().startsWith("customer-loop-thead"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("a")
|
||||||
|
public class ACmp {
|
||||||
|
|
||||||
|
@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
|
||||||
|
public void process(NodeComponent bindCmp) {
|
||||||
|
System.out.println("ACmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("b")
|
||||||
|
public class BCmp {
|
||||||
|
|
||||||
|
@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
|
||||||
|
public void process(NodeComponent bindCmp) {
|
||||||
|
System.out.println("BCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("c")
|
||||||
|
public class CCmp {
|
||||||
|
|
||||||
|
@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
|
||||||
|
public void process(NodeComponent bindCmp) {
|
||||||
|
System.out.println("CCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("d")
|
||||||
|
public class DCmp {
|
||||||
|
|
||||||
|
@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
|
||||||
|
public void process(NodeComponent bindCmp) {
|
||||||
|
DefaultContext context = bindCmp.getFirstContextBean();
|
||||||
|
String key = "test";
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
int count = context.getData(key);
|
||||||
|
context.setData(key, ++count);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.setData(key, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("e")
|
||||||
|
public class ECmp {
|
||||||
|
|
||||||
|
//注意与串行的ECmp相比,并行的ECmp的process方法必须保证线程安全
|
||||||
|
@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
|
||||||
|
public synchronized void process(NodeComponent bindCmp) {
|
||||||
|
DefaultContext context = bindCmp.getFirstContextBean();
|
||||||
|
String key = StrUtil.format("{}_{}", "loop", bindCmp.getTag());
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
String loopStr = context.getData(key);
|
||||||
|
String loopStrReturn = StrUtil.format("{}{}", loopStr, bindCmp.getLoopIndex());
|
||||||
|
context.setData(key, loopStrReturn);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.setData(key, bindCmp.getLoopIndex().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("f")
|
||||||
|
public class FCmp {
|
||||||
|
|
||||||
|
@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
|
||||||
|
public void process(NodeComponent bindCmp) {
|
||||||
|
try {
|
||||||
|
System.out.println("FCmp start to sleep 5s");
|
||||||
|
Thread.sleep(5000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
System.out.println("FCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.test.exception.CustomStatefulException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("g")
|
||||||
|
public class GCmp {
|
||||||
|
|
||||||
|
@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
|
||||||
|
public void process(NodeComponent bindCmp) {
|
||||||
|
if(bindCmp.getLoopIndex()==1){
|
||||||
|
throw new CustomStatefulException("300", "chain execute custom stateful execption");
|
||||||
|
}
|
||||||
|
System.out.println("GCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import com.yomahub.liteflow.test.exception.CustomStatefulException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
||||||
|
@Component("h")
|
||||||
|
public class HCmp {
|
||||||
|
|
||||||
|
@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
|
||||||
|
public void process(NodeComponent bindCmp) {
|
||||||
|
DefaultContext context = bindCmp.getFirstContextBean();
|
||||||
|
context.setData("threadName", Thread.currentThread().getName());
|
||||||
|
System.out.println("HCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.core.NodeIteratorComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component("it")
|
||||||
|
@LiteflowCmpDefine(NodeTypeEnum.ITERATOR)
|
||||||
|
public class ITCmp {
|
||||||
|
|
||||||
|
@LiteflowMethod(LiteFlowMethodEnum.PROCESS_ITERATOR)
|
||||||
|
public Iterator<?> processIterator(NodeComponent bindCmp) throws Exception {
|
||||||
|
List<String> list = bindCmp.getRequestData();
|
||||||
|
return list.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||||
|
|
||||||
|
@LiteflowComponent("x")
|
||||||
|
public class XCmp {
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_FOR, nodeType = NodeTypeEnum.FOR)
|
||||||
|
public int processFor(NodeComponent bindCmp) throws Exception {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
|
||||||
|
@LiteflowComponent("y")
|
||||||
|
public class YCmp {
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_BREAK, nodeType = NodeTypeEnum.BREAK)
|
||||||
|
public boolean processBreak(NodeComponent bindCmp) throws Exception {
|
||||||
|
DefaultContext context = bindCmp.getFirstContextBean();
|
||||||
|
int count = 0;
|
||||||
|
if(context.hasData("test")) {
|
||||||
|
count = context.getData("test");
|
||||||
|
}
|
||||||
|
return count > 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
|
||||||
|
@LiteflowComponent("z")
|
||||||
|
public class ZCmp {
|
||||||
|
|
||||||
|
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_WHILE, nodeType = NodeTypeEnum.WHILE)
|
||||||
|
public boolean processWhile(NodeComponent bindCmp) throws Exception {
|
||||||
|
DefaultContext context = bindCmp.getFirstContextBean();
|
||||||
|
String key = "test";
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
int count = context.getData("test");
|
||||||
|
return count < 5;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
liteflow.rule-source=parallelLoop/flow.xml
|
||||||
|
liteflow.parallel-max-workers = 10
|
||||||
|
liteflow.parallel-queue-limit = 1024
|
||||||
|
liteflow.parallel-loop-executor-class =com.yomahub.liteflow.test.parallelLoop.CustomThreadExecutor
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<flow>
|
||||||
|
<chain name="chain1">
|
||||||
|
FOR(2).parallel(true).DO(THEN(a,b,c));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain2">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,c));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain3">
|
||||||
|
FOR(100).parallel(true).DO(THEN(a,b,d)).BREAK(y);
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain4">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,f));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain5">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,g));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain6">
|
||||||
|
WHILE(z).parallel(true).DO(THEN(a,d));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain7">
|
||||||
|
ITERATOR(it).parallel(true).DO(THEN(a,b));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain8">
|
||||||
|
FOR(5).parallel(true).DO(
|
||||||
|
WHEN(
|
||||||
|
THEN(a,e.tag("e1")),
|
||||||
|
THEN(c,e.tag("e2")),
|
||||||
|
THEN(b,e.tag("e3"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
|
||||||
|
<chain name="chain9">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,h));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
</flow>
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户自定义带状态码的异常
|
||||||
|
*/
|
||||||
|
public class CustomStatefulException extends LiteFlowException {
|
||||||
|
|
||||||
|
public CustomStatefulException(String code, String message) {
|
||||||
|
super(code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfig;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfigGetter;
|
||||||
|
import com.yomahub.liteflow.thread.ExecutorBuilder;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
public class CustomThreadExecutor implements ExecutorBuilder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExecutorService buildExecutor() {
|
||||||
|
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
|
||||||
|
// 只有在非spring的场景下liteflowConfig才会为null
|
||||||
|
if (ObjectUtil.isNull(liteflowConfig)) {
|
||||||
|
liteflowConfig = new LiteflowConfig();
|
||||||
|
}
|
||||||
|
return buildDefaultExecutor(liteflowConfig.getParallelMaxWorkers(), liteflowConfig.getParallelMaxWorkers(),
|
||||||
|
liteflowConfig.getParallelQueueLimit(), "customer-loop-thead-");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
|
import com.yomahub.liteflow.core.FlowExecutor;
|
||||||
|
import com.yomahub.liteflow.core.FlowExecutorHolder;
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
import com.yomahub.liteflow.flow.LiteflowResponse;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfig;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import com.yomahub.liteflow.test.BaseTest;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nospring环境EL异步循环测试
|
||||||
|
*
|
||||||
|
* @author zhhhhy
|
||||||
|
* @since 2.11.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ParallelLoopTest extends BaseTest {
|
||||||
|
|
||||||
|
private static FlowExecutor flowExecutor;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void init() {
|
||||||
|
LiteflowConfig config = new LiteflowConfig();
|
||||||
|
config.setRuleSource("parallelLoop/flow.xml");
|
||||||
|
config.setParallelMaxWorkers(10);
|
||||||
|
config.setParallelQueueLimit(1024);
|
||||||
|
config.setParallelLoopExecutorClass("com.yomahub.liteflow.test.parallelLoop.CustomThreadExecutor");
|
||||||
|
flowExecutor = FlowExecutorHolder.loadInstance(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数直接在el中定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop1() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数由For组件定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop2() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的BREAK组件能够正常发挥作用
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop3() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中,主线程是否会正常等待所有并行子项完成后再继续执行
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop4() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain4", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
//测试并行FOR循环中,某个并行子项抛出异常
|
||||||
|
public void testParallelLoop5() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain5", "arg");
|
||||||
|
Assertions.assertFalse(response.isSuccess());
|
||||||
|
Assertions.assertEquals("300", response.getCode());
|
||||||
|
Assertions.assertNotNull(response.getCause());
|
||||||
|
Assertions.assertTrue(response.getCause() instanceof LiteFlowException);
|
||||||
|
Assertions.assertNotNull(response.getSlot());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的条件循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop6() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的迭代循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop7() throws Exception {
|
||||||
|
List<String> list = ListUtil.toList("1", "2", "3", "4", "5");
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain7", list);
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的index
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop8() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
String regex = "(?!.*(.).*\\1)[0-4]{5}"; //匹配不包含重复数字的0-4的5位数字
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
//e1,e2,e3分别并行执行5次,因此单个循环的顺序可以是任意的
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e1")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e2")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e3")).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//测试自定义线程池配置是否生效
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop9() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain9", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
Assertions.assertTrue(context.getData("threadName").toString().startsWith("customer-loop-thead"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
|
||||||
|
public class ACmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
System.out.println("ACmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
|
||||||
|
public class BCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
System.out.println("BCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
|
||||||
|
public class CCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
System.out.println("CCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
|
||||||
|
public class DCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
String key = "test";
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
int count = context.getData(key);
|
||||||
|
context.setData(key, ++count);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.setData(key, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
|
||||||
|
public class ECmp extends NodeComponent{
|
||||||
|
|
||||||
|
//注意与串行的ECmp相比,并行的ECmp的process方法必须保证线程安全
|
||||||
|
@Override
|
||||||
|
public synchronized void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
String key = StrUtil.format("{}_{}", "loop", this.getTag());
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
String loopStr = context.getData(key);
|
||||||
|
String loopStrReturn = StrUtil.format("{}{}", loopStr, this.getLoopIndex());
|
||||||
|
context.setData(key, loopStrReturn);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.setData(key, this.getLoopIndex().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
|
||||||
|
public class FCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
try {
|
||||||
|
System.out.println("FCmp start to sleep 5s");
|
||||||
|
Thread.sleep(5000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
System.out.println("FCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.test.exception.CustomStatefulException;
|
||||||
|
|
||||||
|
public class GCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
if(this.getLoopIndex()==1){
|
||||||
|
throw new CustomStatefulException("300", "chain execute custom stateful execption");
|
||||||
|
}
|
||||||
|
System.out.println("GCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
|
||||||
|
|
||||||
|
public class HCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
context.setData("threadName", Thread.currentThread().getName());
|
||||||
|
System.out.println("HCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeIteratorComponent;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ITCmp extends NodeIteratorComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<?> processIterator() throws Exception {
|
||||||
|
List<String> list = this.getRequestData();
|
||||||
|
return list.iterator();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeForComponent;
|
||||||
|
|
||||||
|
public class XCmp extends NodeForComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int processFor() throws Exception {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeBreakComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
|
||||||
|
public class YCmp extends NodeBreakComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean processBreak() throws Exception {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
int count = 0;
|
||||||
|
if(context.hasData("test")) {
|
||||||
|
count = context.getData("test");
|
||||||
|
}
|
||||||
|
return count > 3;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeWhileComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
|
||||||
|
public class ZCmp extends NodeWhileComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean processWhile() throws Exception {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
String key = "test";
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
int count = context.getData("test");
|
||||||
|
return count < 5;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<flow>
|
||||||
|
<nodes>
|
||||||
|
<node id="a" class="com.yomahub.liteflow.test.parallelLoop.cmp.ACmp"/>
|
||||||
|
<node id="b" class="com.yomahub.liteflow.test.parallelLoop.cmp.BCmp"/>
|
||||||
|
<node id="c" class="com.yomahub.liteflow.test.parallelLoop.cmp.CCmp"/>
|
||||||
|
<node id="d" class="com.yomahub.liteflow.test.parallelLoop.cmp.DCmp"/>
|
||||||
|
<node id="e" class="com.yomahub.liteflow.test.parallelLoop.cmp.ECmp"/>
|
||||||
|
<node id="f" class="com.yomahub.liteflow.test.parallelLoop.cmp.FCmp"/>
|
||||||
|
<node id="g" class="com.yomahub.liteflow.test.parallelLoop.cmp.GCmp"/>
|
||||||
|
<node id="h" class="com.yomahub.liteflow.test.parallelLoop.cmp.HCmp"/>
|
||||||
|
<node id="it" class="com.yomahub.liteflow.test.parallelLoop.cmp.ITCmp"/>
|
||||||
|
<node id="x" class="com.yomahub.liteflow.test.parallelLoop.cmp.XCmp"/>
|
||||||
|
<node id="y" class="com.yomahub.liteflow.test.parallelLoop.cmp.YCmp"/>
|
||||||
|
<node id="z" class="com.yomahub.liteflow.test.parallelLoop.cmp.ZCmp"/>
|
||||||
|
</nodes>
|
||||||
|
|
||||||
|
<chain name="chain1">
|
||||||
|
FOR(2).DO(THEN(a,b,c));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain2">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,c));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain3">
|
||||||
|
FOR(100).parallel(true).DO(THEN(a,b,d)).BREAK(y);
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain4">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,f));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain5">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,g));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain6">
|
||||||
|
WHILE(z).parallel(true).DO(THEN(a,d));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain7">
|
||||||
|
ITERATOR(it).parallel(true).DO(THEN(a,b));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain8">
|
||||||
|
FOR(5).parallel(true).DO(
|
||||||
|
WHEN(
|
||||||
|
THEN(a,e.tag("e1")),
|
||||||
|
THEN(c,e.tag("e2")),
|
||||||
|
THEN(b,e.tag("e3"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
|
||||||
|
<chain name="chain9">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,h));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
</flow>
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户自定义带状态码的异常
|
||||||
|
*/
|
||||||
|
public class CustomStatefulException extends LiteFlowException {
|
||||||
|
|
||||||
|
public CustomStatefulException(String code, String message) {
|
||||||
|
super(code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfig;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfigGetter;
|
||||||
|
import com.yomahub.liteflow.thread.ExecutorBuilder;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
public class CustomThreadExecutor implements ExecutorBuilder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExecutorService buildExecutor() {
|
||||||
|
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
|
||||||
|
// 只有在非spring的场景下liteflowConfig才会为null
|
||||||
|
if (ObjectUtil.isNull(liteflowConfig)) {
|
||||||
|
liteflowConfig = new LiteflowConfig();
|
||||||
|
}
|
||||||
|
return buildDefaultExecutor(liteflowConfig.getParallelMaxWorkers(), liteflowConfig.getParallelMaxWorkers(),
|
||||||
|
liteflowConfig.getParallelQueueLimit(), "customer-loop-thead-");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
|
import com.yomahub.liteflow.core.FlowExecutor;
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
import com.yomahub.liteflow.flow.LiteflowResponse;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import com.yomahub.liteflow.test.BaseTest;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.noear.solon.annotation.Inject;
|
||||||
|
import org.noear.solon.test.SolonJUnit5Extension;
|
||||||
|
import org.noear.solon.test.annotation.TestPropertySource;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* springboot环境EL异步循环测试
|
||||||
|
*
|
||||||
|
* @author zhhhhy
|
||||||
|
* @since 2.11.0
|
||||||
|
*/
|
||||||
|
@ExtendWith(SolonJUnit5Extension.class)
|
||||||
|
@TestPropertySource("classpath:/parallelLoop/application.properties")
|
||||||
|
public class ParallelLoopELSpringbootTest extends BaseTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private FlowExecutor flowExecutor;
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数直接在el中定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop1() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数由For组件定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop2() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的BREAK组件能够正常发挥作用
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop3() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中,主线程是否会正常等待所有并行子项完成后再继续执行
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop4() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain4", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
//测试并行FOR循环中,某个并行子项抛出异常
|
||||||
|
public void testParallelLoop5() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain5", "arg");
|
||||||
|
Assertions.assertFalse(response.isSuccess());
|
||||||
|
Assertions.assertEquals("300", response.getCode());
|
||||||
|
Assertions.assertNotNull(response.getCause());
|
||||||
|
Assertions.assertTrue(response.getCause() instanceof LiteFlowException);
|
||||||
|
Assertions.assertNotNull(response.getSlot());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的条件循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop6() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的迭代循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop7() throws Exception {
|
||||||
|
List<String> list = ListUtil.toList("1", "2", "3", "4", "5");
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain7", list);
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的index
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop8() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
String regex = "(?!.*(.).*\\1)[0-4]{5}"; //匹配不包含重复数字的0-4的5位数字
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
//e1,e2,e3分别并行执行5次,因此单个循环的顺序可以是任意的
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e1")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e2")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e3")).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//测试自定义线程池配置是否生效
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop9() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain9", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
Assertions.assertTrue(context.getData("threadName").toString().startsWith("customer-loop-thead"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
@Component("a")
|
||||||
|
public class ACmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
System.out.println("ACmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
@Component("b")
|
||||||
|
public class BCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
System.out.println("BCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
@Component("c")
|
||||||
|
public class CCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
System.out.println("CCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
@Component("d")
|
||||||
|
public class DCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
String key = "test";
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
int count = context.getData(key);
|
||||||
|
context.setData(key, ++count);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.setData(key, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
@Component("e")
|
||||||
|
public class ECmp extends NodeComponent{
|
||||||
|
|
||||||
|
//注意与串行的ECmp相比,并行的ECmp的process方法必须保证线程安全
|
||||||
|
@Override
|
||||||
|
public synchronized void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
String key = StrUtil.format("{}_{}", "loop", this.getTag());
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
String loopStr = context.getData(key);
|
||||||
|
String loopStrReturn = StrUtil.format("{}{}", loopStr, this.getLoopIndex());
|
||||||
|
context.setData(key, loopStrReturn);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.setData(key, this.getLoopIndex().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
@Component("f")
|
||||||
|
public class FCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
try {
|
||||||
|
System.out.println("FCmp start to sleep 5s");
|
||||||
|
Thread.sleep(5000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
System.out.println("FCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.test.exception.CustomStatefulException;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
@Component("g")
|
||||||
|
public class GCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
if(this.getLoopIndex()==1){
|
||||||
|
throw new CustomStatefulException("300", "chain execute custom stateful execption");
|
||||||
|
}
|
||||||
|
System.out.println("GCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
|
||||||
|
@Component("h")
|
||||||
|
public class HCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
context.setData("threadName", Thread.currentThread().getName());
|
||||||
|
System.out.println("HCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeIteratorComponent;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component("it")
|
||||||
|
public class ITCmp extends NodeIteratorComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<?> processIterator() throws Exception {
|
||||||
|
List<String> list = this.getRequestData();
|
||||||
|
return list.iterator();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeForComponent;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
@Component("x")
|
||||||
|
public class XCmp extends NodeForComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int processFor() throws Exception {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeBreakComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
@Component("y")
|
||||||
|
public class YCmp extends NodeBreakComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean processBreak() throws Exception {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
int count = 0;
|
||||||
|
if(context.hasData("test")) {
|
||||||
|
count = context.getData("test");
|
||||||
|
}
|
||||||
|
return count > 3;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeWhileComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
|
@Component("z")
|
||||||
|
public class ZCmp extends NodeWhileComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean processWhile() throws Exception {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
String key = "test";
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
int count = context.getData("test");
|
||||||
|
return count < 5;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
liteflow.rule-source=parallelLoop/flow.xml
|
||||||
|
liteflow.parallel-max-workers = 10
|
||||||
|
liteflow.parallel-queue-limit = 1024
|
||||||
|
liteflow.parallel-loop-executor-class =com.yomahub.liteflow.test.parallelLoop.CustomThreadExecutor
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<flow>
|
||||||
|
<chain name="chain1">
|
||||||
|
FOR(2).parallel(true).DO(THEN(a,b,c));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain2">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,c));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain3">
|
||||||
|
FOR(100).parallel(true).DO(THEN(a,b,d)).BREAK(y);
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain4">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,f));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain5">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,g));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain6">
|
||||||
|
WHILE(z).parallel(true).DO(THEN(a,d));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain7">
|
||||||
|
ITERATOR(it).parallel(true).DO(THEN(a,b));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain8">
|
||||||
|
FOR(5).parallel(true).DO(
|
||||||
|
WHEN(
|
||||||
|
THEN(a,e.tag("e1")),
|
||||||
|
THEN(c,e.tag("e2")),
|
||||||
|
THEN(b,e.tag("e3"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
|
||||||
|
<chain name="chain9">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,h));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
</flow>
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户自定义带状态码的异常
|
||||||
|
*/
|
||||||
|
public class CustomStatefulException extends LiteFlowException {
|
||||||
|
|
||||||
|
public CustomStatefulException(String code, String message) {
|
||||||
|
super(code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfig;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfigGetter;
|
||||||
|
import com.yomahub.liteflow.thread.ExecutorBuilder;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
public class CustomThreadExecutor implements ExecutorBuilder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExecutorService buildExecutor() {
|
||||||
|
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
|
||||||
|
// 只有在非spring的场景下liteflowConfig才会为null
|
||||||
|
if (ObjectUtil.isNull(liteflowConfig)) {
|
||||||
|
liteflowConfig = new LiteflowConfig();
|
||||||
|
}
|
||||||
|
return buildDefaultExecutor(liteflowConfig.getParallelMaxWorkers(), liteflowConfig.getParallelMaxWorkers(),
|
||||||
|
liteflowConfig.getParallelQueueLimit(), "customer-loop-thead-");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
|
import com.yomahub.liteflow.core.FlowExecutor;
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
import com.yomahub.liteflow.flow.LiteflowResponse;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import com.yomahub.liteflow.test.BaseTest;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
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.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* springboot环境EL异步循环测试
|
||||||
|
*
|
||||||
|
* @author zhhhhy
|
||||||
|
* @since 2.11.0
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@TestPropertySource(value = "classpath:/parallelLoop/application.properties")
|
||||||
|
@SpringBootTest(classes = ParallelLoopELSpringbootTest.class)
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
@ComponentScan({ "com.yomahub.liteflow.test.parallelLoop.cmp" })
|
||||||
|
public class ParallelLoopELSpringbootTest extends BaseTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FlowExecutor flowExecutor;
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数直接在el中定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop1() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数由For组件定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop2() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的BREAK组件能够正常发挥作用
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop3() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中,主线程是否会正常等待所有并行子项完成后再继续执行
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop4() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain4", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
//测试并行FOR循环中,某个并行子项抛出异常
|
||||||
|
public void testParallelLoop5() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain5", "arg");
|
||||||
|
Assertions.assertFalse(response.isSuccess());
|
||||||
|
Assertions.assertEquals("300", response.getCode());
|
||||||
|
Assertions.assertNotNull(response.getCause());
|
||||||
|
Assertions.assertTrue(response.getCause() instanceof LiteFlowException);
|
||||||
|
Assertions.assertNotNull(response.getSlot());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的条件循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop6() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的迭代循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop7() throws Exception {
|
||||||
|
List<String> list = ListUtil.toList("1", "2", "3", "4", "5");
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain7", list);
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的index
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop8() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
String regex = "(?!.*(.).*\\1)[0-4]{5}"; //匹配不包含重复数字的0-4的5位数字
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
//e1,e2,e3分别并行执行5次,因此单个循环的顺序可以是任意的
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e1")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e2")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e3")).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//测试自定义线程池配置是否生效
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop9() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain9", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
Assertions.assertTrue(context.getData("threadName").toString().startsWith("customer-loop-thead"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("a")
|
||||||
|
public class ACmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
System.out.println("ACmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("b")
|
||||||
|
public class BCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
System.out.println("BCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("c")
|
||||||
|
public class CCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
System.out.println("CCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("d")
|
||||||
|
public class DCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
String key = "test";
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
int count = context.getData(key);
|
||||||
|
context.setData(key, ++count);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.setData(key, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("e")
|
||||||
|
public class ECmp extends NodeComponent{
|
||||||
|
|
||||||
|
//注意与串行的ECmp相比,并行的ECmp的process方法必须保证线程安全
|
||||||
|
@Override
|
||||||
|
public synchronized void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
String key = StrUtil.format("{}_{}", "loop", this.getTag());
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
String loopStr = context.getData(key);
|
||||||
|
String loopStrReturn = StrUtil.format("{}{}", loopStr, this.getLoopIndex());
|
||||||
|
context.setData(key, loopStrReturn);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.setData(key, this.getLoopIndex().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("f")
|
||||||
|
public class FCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
try {
|
||||||
|
System.out.println("FCmp start to sleep 5s");
|
||||||
|
Thread.sleep(5000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
System.out.println("FCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.test.exception.CustomStatefulException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("g")
|
||||||
|
public class GCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
if(this.getLoopIndex()==1){
|
||||||
|
throw new CustomStatefulException("300", "chain execute custom stateful execption");
|
||||||
|
}
|
||||||
|
System.out.println("GCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
||||||
|
@Component("h")
|
||||||
|
public class HCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
context.setData("threadName", Thread.currentThread().getName());
|
||||||
|
System.out.println("HCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.core.NodeIteratorComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component("it")
|
||||||
|
public class ITCmp extends NodeIteratorComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<?> processIterator() throws Exception {
|
||||||
|
List<String> list = this.getRequestData();
|
||||||
|
return list.iterator();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.core.NodeForComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("x")
|
||||||
|
public class XCmp extends NodeForComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int processFor() throws Exception {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeBreakComponent;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("y")
|
||||||
|
public class YCmp extends NodeBreakComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean processBreak() throws Exception {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
int count = 0;
|
||||||
|
if(context.hasData("test")) {
|
||||||
|
count = context.getData("test");
|
||||||
|
}
|
||||||
|
return count > 3;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import com.yomahub.liteflow.annotation.LiteflowMethod;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.core.NodeWhileComponent;
|
||||||
|
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
|
||||||
|
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("z")
|
||||||
|
public class ZCmp extends NodeWhileComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean processWhile() throws Exception {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
String key = "test";
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
int count = context.getData("test");
|
||||||
|
return count < 5;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
liteflow.rule-source=parallelLoop/flow.xml
|
||||||
|
liteflow.parallel-max-workers = 10
|
||||||
|
liteflow.parallel-queue-limit = 1024
|
||||||
|
liteflow.parallel-loop-executor-class =com.yomahub.liteflow.test.parallelLoop.CustomThreadExecutor
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<flow>
|
||||||
|
<chain name="chain1">
|
||||||
|
FOR(2).parallel(true).DO(THEN(a,b,c));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain2">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,c));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain3">
|
||||||
|
FOR(100).parallel(true).DO(THEN(a,b,d)).BREAK(y);
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain4">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,f));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain5">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,g));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain6">
|
||||||
|
WHILE(z).parallel(true).DO(THEN(a,d));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain7">
|
||||||
|
ITERATOR(it).parallel(true).DO(THEN(a,b));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
<chain name="chain8">
|
||||||
|
FOR(5).parallel(true).DO(
|
||||||
|
WHEN(
|
||||||
|
THEN(a,e.tag("e1")),
|
||||||
|
THEN(c,e.tag("e2")),
|
||||||
|
THEN(b,e.tag("e3"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
|
||||||
|
<chain name="chain9">
|
||||||
|
FOR(x).parallel(true).DO(THEN(a,b,h));
|
||||||
|
</chain>
|
||||||
|
|
||||||
|
</flow>
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户自定义带状态码的异常
|
||||||
|
*/
|
||||||
|
public class CustomStatefulException extends LiteFlowException {
|
||||||
|
|
||||||
|
public CustomStatefulException(String code, String message) {
|
||||||
|
super(code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfig;
|
||||||
|
import com.yomahub.liteflow.property.LiteflowConfigGetter;
|
||||||
|
import com.yomahub.liteflow.thread.ExecutorBuilder;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
public class CustomThreadExecutor implements ExecutorBuilder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExecutorService buildExecutor() {
|
||||||
|
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
|
||||||
|
// 只有在非spring的场景下liteflowConfig才会为null
|
||||||
|
if (ObjectUtil.isNull(liteflowConfig)) {
|
||||||
|
liteflowConfig = new LiteflowConfig();
|
||||||
|
}
|
||||||
|
return buildDefaultExecutor(liteflowConfig.getParallelMaxWorkers(), liteflowConfig.getParallelMaxWorkers(),
|
||||||
|
liteflowConfig.getParallelQueueLimit(), "customer-loop-thead-");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
|
import com.yomahub.liteflow.core.FlowExecutor;
|
||||||
|
import com.yomahub.liteflow.exception.LiteFlowException;
|
||||||
|
import com.yomahub.liteflow.flow.LiteflowResponse;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import com.yomahub.liteflow.test.BaseTest;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* springboot环境EL异步循环测试
|
||||||
|
*
|
||||||
|
* @author zhhhhy
|
||||||
|
* @since 2.11.0
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@ContextConfiguration("classpath:/parallelLoop/application.xml")
|
||||||
|
public class ParallelLoopELSpringTest extends BaseTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FlowExecutor flowExecutor;
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数直接在el中定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop1() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环,循环次数由For组件定义
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop2() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的BREAK组件能够正常发挥作用
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop3() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中,主线程是否会正常等待所有并行子项完成后再继续执行
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop4() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain4", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
//测试并行FOR循环中,某个并行子项抛出异常
|
||||||
|
public void testParallelLoop5() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain5", "arg");
|
||||||
|
Assertions.assertFalse(response.isSuccess());
|
||||||
|
Assertions.assertEquals("300", response.getCode());
|
||||||
|
Assertions.assertNotNull(response.getCause());
|
||||||
|
Assertions.assertTrue(response.getCause() instanceof LiteFlowException);
|
||||||
|
Assertions.assertNotNull(response.getSlot());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的条件循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop6() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg");
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//并行的迭代循环
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop7() throws Exception {
|
||||||
|
List<String> list = ListUtil.toList("1", "2", "3", "4", "5");
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain7", list);
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试并行FOR循环中的index
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop8() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
String regex = "(?!.*(.).*\\1)[0-4]{5}"; //匹配不包含重复数字的0-4的5位数字
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
//e1,e2,e3分别并行执行5次,因此单个循环的顺序可以是任意的
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e1")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e2")).matches());
|
||||||
|
Assertions.assertTrue(pattern.matcher(context.getData("loop_e3")).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//测试自定义线程池配置是否生效
|
||||||
|
@Test
|
||||||
|
public void testParallelLoop9() throws Exception {
|
||||||
|
LiteflowResponse response = flowExecutor.execute2Resp("chain9", "arg");
|
||||||
|
DefaultContext context = response.getFirstContextBean();
|
||||||
|
Assertions.assertTrue(response.isSuccess());
|
||||||
|
Assertions.assertTrue(context.getData("threadName").toString().startsWith("customer-loop-thead"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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.parallelLoop.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!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.parallelLoop.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!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.parallelLoop.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!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("d")
|
||||||
|
public class DCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
String key = "test";
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
int count = context.getData(key);
|
||||||
|
context.setData(key, ++count);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.setData(key, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* <p>Title: liteflow</p>
|
||||||
|
* <p>Description: 轻量级的组件式流程框架</p>
|
||||||
|
* @author Bryan.Zhang
|
||||||
|
* @email weenyc31@163.com
|
||||||
|
* @Date 2020/4/1
|
||||||
|
*/
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("e")
|
||||||
|
public class ECmp extends NodeComponent{
|
||||||
|
|
||||||
|
//注意与串行的ECmp相比,并行的ECmp的process方法必须保证线程安全
|
||||||
|
@Override
|
||||||
|
public synchronized void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
String key = StrUtil.format("{}_{}", "loop", this.getTag());
|
||||||
|
if (context.hasData(key)) {
|
||||||
|
String loopStr = context.getData(key);
|
||||||
|
String loopStrReturn = StrUtil.format("{}{}", loopStr, this.getLoopIndex());
|
||||||
|
context.setData(key, loopStrReturn);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.setData(key, this.getLoopIndex().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("f")
|
||||||
|
public class FCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
try {
|
||||||
|
System.out.println("FCmp start to sleep 5s");
|
||||||
|
Thread.sleep(5000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
System.out.println("FCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.test.exception.CustomStatefulException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("g")
|
||||||
|
public class GCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
if(this.getLoopIndex()==1){
|
||||||
|
throw new CustomStatefulException("300", "chain execute custom stateful execption");
|
||||||
|
}
|
||||||
|
System.out.println("GCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
|
import com.yomahub.liteflow.slot.DefaultContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
||||||
|
@Component("h")
|
||||||
|
public class HCmp extends NodeComponent{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() {
|
||||||
|
DefaultContext context = this.getFirstContextBean();
|
||||||
|
context.setData("threadName", Thread.currentThread().getName());
|
||||||
|
System.out.println("HCmp executed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.yomahub.liteflow.test.parallelLoop.cmp;
|
||||||
|
|
||||||
|
import com.yomahub.liteflow.core.NodeIteratorComponent;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component("it")
|
||||||
|
public class ITCmp extends NodeIteratorComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<?> processIterator() throws Exception {
|
||||||
|
List<String> list = this.getRequestData();
|
||||||
|
return list.iterator();
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue