mirror of
https://gitee.com/dromara/liteFlow.git
synced 2026-05-14 04:02:09 +08:00
Merge remote-tracking branch 'upstream/dev' into dev_rule_cache_2
# Conflicts: # liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java
This commit is contained in:
@@ -19,13 +19,13 @@ import com.yomahub.liteflow.core.ComponentInitializer;
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import com.yomahub.liteflow.core.ScriptComponent;
|
||||
import com.yomahub.liteflow.core.proxy.DeclWarpBean;
|
||||
import com.yomahub.liteflow.core.proxy.LiteFlowProxyUtil;
|
||||
import com.yomahub.liteflow.enums.FlowParserTypeEnum;
|
||||
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||
import com.yomahub.liteflow.enums.ParseModeEnum;
|
||||
import com.yomahub.liteflow.exception.ComponentCannotRegisterException;
|
||||
import com.yomahub.liteflow.exception.NullNodeTypeException;
|
||||
import com.yomahub.liteflow.flow.element.Chain;
|
||||
import com.yomahub.liteflow.flow.element.Condition;
|
||||
import com.yomahub.liteflow.flow.element.Node;
|
||||
import com.yomahub.liteflow.lifecycle.LifeCycleHolder;
|
||||
import com.yomahub.liteflow.log.LFLog;
|
||||
@@ -43,14 +43,10 @@ import com.yomahub.liteflow.spi.ContextAware;
|
||||
import com.yomahub.liteflow.spi.holder.ContextAwareHolder;
|
||||
import com.yomahub.liteflow.spi.holder.DeclComponentParserHolder;
|
||||
import com.yomahub.liteflow.util.CopyOnWriteHashMap;
|
||||
import com.yomahub.liteflow.core.proxy.LiteFlowProxyUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 流程元数据类
|
||||
@@ -58,6 +54,7 @@ import java.util.stream.Stream;
|
||||
* @author Bryan.Zhang
|
||||
* @author DaleLee
|
||||
* @author Jay li
|
||||
* @author luo yi
|
||||
*/
|
||||
public class FlowBus {
|
||||
|
||||
@@ -89,9 +86,9 @@ public class FlowBus {
|
||||
}
|
||||
|
||||
// 这一方法主要用于第一阶段chain的预装载
|
||||
public static void addChain(String chainName) {
|
||||
if (!chainMap.containsKey(chainName)) {
|
||||
chainMap.put(chainName, new Chain(chainName));
|
||||
public static void addChain(String chainId) {
|
||||
if (!chainMap.containsKey(chainId)) {
|
||||
chainMap.put(chainId, new Chain(chainId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +201,7 @@ public class FlowBus {
|
||||
}
|
||||
|
||||
Node node = new Node(nodeId, name, nodeType, script, language);
|
||||
nodeMap.put(nodeId, node);
|
||||
put2NodeMap(nodeId, node);
|
||||
} else {
|
||||
addScriptNodeAndCompile(nodeId, name, nodeType, script, language);
|
||||
}
|
||||
@@ -218,80 +215,98 @@ public class FlowBus {
|
||||
* @param type type
|
||||
* @param script script content
|
||||
* @param language language
|
||||
* @return NodeComponent instance
|
||||
*/
|
||||
public static NodeComponent addScriptNodeAndCompile(String nodeId, String name, NodeTypeEnum type, String script,
|
||||
String language) {
|
||||
public static void addScriptNodeAndCompile(String nodeId, String name, NodeTypeEnum type, String script, String language) {
|
||||
addNode(nodeId, name, type, ScriptComponent.ScriptComponentClassMap.get(type), script, language);
|
||||
return nodeMap.get(nodeId).getInstance();
|
||||
}
|
||||
|
||||
private static List<NodeComponent> getNodeComponentList(String nodeId, String name, NodeTypeEnum type, Class<?> cmpClazz) throws Exception {
|
||||
// 判断此类是否是声明式的组件,如果是声明式的组件,就用动态代理生成实例
|
||||
// 如果不是声明式的,就用传统的方式进行判断
|
||||
List<NodeComponent> cmpInstanceList = new ArrayList<>();
|
||||
if (LiteFlowProxyUtil.isDeclareCmp(cmpClazz)) {
|
||||
// 如果是spring体系,把原始的类往spring上下文中进行注册,那么会走到ComponentScanner中
|
||||
// 由于ComponentScanner中已经对原始类进行了动态代理,出来的对象已经变成了动态代理类,所以这时候的bean已经是NodeComponent的子类了
|
||||
// 所以spring体系下,无需再对这个bean做二次代理
|
||||
// 非spring体系下,从2.11.4开始不再支持声明式组件
|
||||
List<DeclWarpBean> declWarpBeanList = DeclComponentParserHolder.loadDeclComponentParser().parseDeclBean(cmpClazz, nodeId, name);
|
||||
|
||||
private static void addNode(String nodeId, String name, NodeTypeEnum type, Class<?> cmpClazz, String script,
|
||||
String language) {
|
||||
cmpInstanceList = declWarpBeanList.stream().map(
|
||||
declWarpBean -> (NodeComponent)ContextAwareHolder.loadContextAware().registerDeclWrapBean(nodeId, declWarpBean)
|
||||
).collect(Collectors.toList());
|
||||
} else {
|
||||
// 以node方式配置,本质上是为了适配无spring的环境,如果有spring环境,其实不用这么配置
|
||||
// 这里的逻辑是判断是否能从spring上下文中取到,如果没有spring,则就是new instance了
|
||||
// 如果是script类型的节点,因为class只有一个,所以也不能注册进spring上下文,注册的时候需要new Instance
|
||||
if (!type.isScript()) {
|
||||
cmpInstanceList = ListUtil
|
||||
.toList((NodeComponent) ContextAwareHolder.loadContextAware().registerOrGet(nodeId, cmpClazz));
|
||||
}
|
||||
// 如果为空
|
||||
if (cmpInstanceList.isEmpty()) {
|
||||
NodeComponent cmpInstance = (NodeComponent) cmpClazz.newInstance();
|
||||
cmpInstanceList.add(cmpInstance);
|
||||
}
|
||||
}
|
||||
// 进行初始化component
|
||||
cmpInstanceList.forEach(cmpInstance -> ComponentInitializer.loadInstance()
|
||||
.initComponent(cmpInstance, type, name, cmpInstance.getNodeId() == null ? nodeId : cmpInstance.getNodeId()));
|
||||
|
||||
return cmpInstanceList;
|
||||
}
|
||||
|
||||
public static void compileScriptNode(Node node) {
|
||||
String nodeId = node.getId(), name = node.getName(), script = node.getScript(), language = node.getLanguage();
|
||||
NodeTypeEnum type = node.getType();
|
||||
try {
|
||||
List<NodeComponent> cmpInstanceList = getNodeComponentList(nodeId, name, type, ScriptComponent.ScriptComponentClassMap.get(type));
|
||||
|
||||
NodeComponent cmpInstance = cmpInstanceList.get(0);
|
||||
|
||||
addCompiledNode2Map(node, nodeId, script, language, type, cmpInstance);
|
||||
} catch (Exception e) {
|
||||
String error = StrUtil.format("component[{}] register error", StrUtil.isEmpty(name) ? nodeId : StrUtil.format("{}({})", nodeId, name));
|
||||
LOG.error(e.getMessage());
|
||||
throw new ComponentCannotRegisterException(StrUtil.format("{} {}", error, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private static void addCompiledNode2Map(Node node, String nodeId, String script, String language, NodeTypeEnum type, NodeComponent cmpInstance) {
|
||||
// 如果是脚本节点,则还要加载script脚本
|
||||
if (type.isScript()) {
|
||||
if (StrUtil.isNotBlank(script)) {
|
||||
node.setScript(script);
|
||||
node.setLanguage(language);
|
||||
((ScriptComponent) cmpInstance).loadScript(script, language);
|
||||
node.setCompiled(true);
|
||||
node.setInstance(cmpInstance);
|
||||
} else {
|
||||
String errorMsg = StrUtil.format("script for node[{}] is empty", nodeId);
|
||||
throw new ScriptLoadException(errorMsg);
|
||||
}
|
||||
}
|
||||
String activeNodeId = StrUtil.isEmpty(cmpInstance.getNodeId()) ? nodeId : cmpInstance.getNodeId();
|
||||
put2NodeMap(activeNodeId, node);
|
||||
addFallbackNode(node);
|
||||
}
|
||||
|
||||
// 如果是spring自动扫描的组件,在addManagedNode方法中就已经完成了组装了
|
||||
// 调用到这里,分两种情况,一是脚本组件,二是通过LiteFlowNodeBuilder代码进行组装的组件
|
||||
private static void addNode(String nodeId, String name, NodeTypeEnum type, Class<?> cmpClazz, String script, String language) {
|
||||
try {
|
||||
// 判断此类是否是声明式的组件,如果是声明式的组件,就用动态代理生成实例
|
||||
// 如果不是声明式的,就用传统的方式进行判断
|
||||
List<NodeComponent> cmpInstanceList = new ArrayList<>();
|
||||
if (LiteFlowProxyUtil.isDeclareCmp(cmpClazz)) {
|
||||
// 如果是spring体系,把原始的类往spring上下文中进行注册,那么会走到ComponentScanner中
|
||||
// 由于ComponentScanner中已经对原始类进行了动态代理,出来的对象已经变成了动态代理类,所以这时候的bean已经是NodeComponent的子类了
|
||||
// 所以spring体系下,无需再对这个bean做二次代理
|
||||
// 非spring体系下,从2.11.4开始不再支持声明式组件
|
||||
List<DeclWarpBean> declWarpBeanList = DeclComponentParserHolder.loadDeclComponentParser().parseDeclBean(cmpClazz, nodeId, name);
|
||||
|
||||
cmpInstanceList = declWarpBeanList.stream().map(
|
||||
declWarpBean -> (NodeComponent)ContextAwareHolder.loadContextAware().registerDeclWrapBean(nodeId, declWarpBean)
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
else {
|
||||
// 以node方式配置,本质上是为了适配无spring的环境,如果有spring环境,其实不用这么配置
|
||||
// 这里的逻辑是判断是否能从spring上下文中取到,如果没有spring,则就是new instance了
|
||||
// 如果是script类型的节点,因为class只有一个,所以也不能注册进spring上下文,注册的时候需要new Instance
|
||||
if (!type.isScript()) {
|
||||
cmpInstanceList = ListUtil
|
||||
.toList((NodeComponent) ContextAwareHolder.loadContextAware().registerOrGet(nodeId, cmpClazz));
|
||||
}
|
||||
// 如果为空
|
||||
if (cmpInstanceList.isEmpty()) {
|
||||
NodeComponent cmpInstance = (NodeComponent) cmpClazz.newInstance();
|
||||
cmpInstanceList.add(cmpInstance);
|
||||
}
|
||||
}
|
||||
// 进行初始化component
|
||||
cmpInstanceList = cmpInstanceList.stream()
|
||||
.map(cmpInstance -> ComponentInitializer.loadInstance()
|
||||
.initComponent(cmpInstance, type, name,
|
||||
cmpInstance.getNodeId() == null ? nodeId : cmpInstance.getNodeId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 获得初始化好的NodeComponent
|
||||
// 按理说一个nodeId对应一个NodeComponent,这里得到的是List<NodeComponent>的原因是,声明式组件有可能会有多个nodeId。
|
||||
// 声明式组件又分类声明和方法声明,如果对于方法声明来说,这里的nodeId其实并不是最终真正的nodeId。
|
||||
List<NodeComponent> cmpInstanceList = getNodeComponentList(nodeId, name, type, cmpClazz);
|
||||
|
||||
// 初始化Node,把component放到Node里去
|
||||
List<Node> nodes = cmpInstanceList.stream().map(Node::new).collect(Collectors.toList());
|
||||
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
Node node = nodes.get(i);
|
||||
NodeComponent cmpInstance = cmpInstanceList.get(i);
|
||||
// 如果是脚本节点,则还要加载script脚本
|
||||
if (type.isScript()) {
|
||||
if (StrUtil.isNotBlank(script)) {
|
||||
node.setScript(script);
|
||||
node.setLanguage(language);
|
||||
((ScriptComponent) cmpInstance).loadScript(script, language);
|
||||
}
|
||||
else {
|
||||
String errorMsg = StrUtil.format("script for node[{}] is empty", nodeId);
|
||||
throw new ScriptLoadException(errorMsg);
|
||||
}
|
||||
}
|
||||
String activeNodeId = StrUtil.isEmpty(cmpInstance.getNodeId()) ? nodeId : cmpInstance.getNodeId();
|
||||
put2NodeMap(activeNodeId, node);
|
||||
addFallbackNode(node);
|
||||
addCompiledNode2Map(nodes.get(i), nodeId, script, language, type, cmpInstanceList.get(i));
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
String error = StrUtil.format("component[{}] register error",
|
||||
StrUtil.isEmpty(name) ? nodeId : StrUtil.format("{}({})", nodeId, name));
|
||||
} catch (Exception e) {
|
||||
String error = StrUtil.format("component[{}] register error", StrUtil.isEmpty(name) ? nodeId : StrUtil.format("{}({})", nodeId, name));
|
||||
LOG.error(e.getMessage());
|
||||
throw new ComponentCannotRegisterException(StrUtil.format("{} {}", error, e.getMessage()));
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.util.List;
|
||||
*
|
||||
* @author Bryan.Zhang
|
||||
* @author jason
|
||||
* @author luo yi
|
||||
* @author DaleLee
|
||||
*/
|
||||
public class Chain implements Executable{
|
||||
@@ -43,11 +44,11 @@ public class Chain implements Executable{
|
||||
|
||||
private Executable routeItem;
|
||||
|
||||
private List<Condition> conditionList = new ArrayList<>();
|
||||
private volatile List<Condition> conditionList = new ArrayList<>();
|
||||
|
||||
private String el;
|
||||
|
||||
private boolean isCompiled = true;
|
||||
private volatile boolean isCompiled = true;
|
||||
|
||||
private String namespace = ChainConstant.DEFAULT_NAMESPACE;
|
||||
|
||||
@@ -109,7 +110,11 @@ public class Chain implements Executable{
|
||||
|
||||
//如果EL还未编译,则进行编译
|
||||
if (BooleanUtil.isFalse(isCompiled)) {
|
||||
LiteFlowChainELBuilder.buildUnCompileChain(this);
|
||||
synchronized (this) {
|
||||
if (BooleanUtil.isFalse(isCompiled)) {
|
||||
LiteFlowChainELBuilder.buildUnCompileChain(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 这里先拿到this.conditionList的引用
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.yomahub.liteflow.enums.ExecuteableTypeEnum;
|
||||
import com.yomahub.liteflow.enums.NodeTypeEnum;
|
||||
import com.yomahub.liteflow.exception.ChainEndException;
|
||||
import com.yomahub.liteflow.exception.FlowSystemException;
|
||||
import com.yomahub.liteflow.flow.FlowBus;
|
||||
import com.yomahub.liteflow.flow.element.condition.LoopCondition;
|
||||
import com.yomahub.liteflow.flow.executor.NodeExecutor;
|
||||
import com.yomahub.liteflow.flow.executor.NodeExecutorHelper;
|
||||
@@ -30,7 +31,6 @@ import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import static com.yomahub.liteflow.flow.FlowBus.*;
|
||||
|
||||
|
||||
/**
|
||||
@@ -60,7 +60,7 @@ public class Node implements Executable, Cloneable, Rollbackable{
|
||||
|
||||
// 增加该注解,避免在使用 Jackson 序列化检测循环引用时出现不必要异常
|
||||
@JsonIgnore
|
||||
private NodeComponent instance;
|
||||
private volatile NodeComponent instance;
|
||||
|
||||
private String tag;
|
||||
|
||||
@@ -71,7 +71,7 @@ public class Node implements Executable, Cloneable, Rollbackable{
|
||||
private String currChainId;
|
||||
|
||||
// 针对于脚本节点,这个属性代表脚本节点的脚本是否已经编译过
|
||||
private boolean isCompiled = true;
|
||||
private volatile boolean isCompiled = true;
|
||||
|
||||
// 此属性代表在EL构建的时候,node节点是否已经从FLowBus中的nodeMap中clone过了。
|
||||
// 如果已经clone过了,不再Clone
|
||||
@@ -171,7 +171,11 @@ public class Node implements Executable, Cloneable, Rollbackable{
|
||||
public NodeComponent getInstance() {
|
||||
// 没有编译的情况,需重新编译
|
||||
if (!this.isCompiled()) {
|
||||
this.instance = addScriptNodeAndCompile(id, name, type, script, language);
|
||||
synchronized (this) {
|
||||
if (!this.isCompiled()) {
|
||||
FlowBus.compileScriptNode(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
@@ -200,12 +204,6 @@ public class Node implements Executable, Cloneable, Rollbackable{
|
||||
.buildNodeExecutor(instance.getNodeExecutorClass());
|
||||
// 调用节点执行器进行执行
|
||||
nodeExecutor.execute(instance);
|
||||
|
||||
// 如果是脚本节点,并且是后置编译的,那么在成功执行好脚本节点后把编译flag置为true
|
||||
// 这个只能在成功执行好之后设置,如果在编译好之后设置,那么设置的只有FlowBus中的nodeMap中的
|
||||
if (this.type.isScript() && !this.isCompiled){
|
||||
this.setCompiled(true);
|
||||
}
|
||||
} else {
|
||||
LOG.info("[X]skip component[{}] execution", instance.getDisplayName());
|
||||
}
|
||||
|
||||
@@ -183,8 +183,8 @@ public class ParserHelper {
|
||||
//首先需要对继承自抽象Chain的chain进行字符串替换
|
||||
parseImplChain(abstratChainMap, implChainSet, chain);
|
||||
//如果一个chain不为抽象chain,则进行解析
|
||||
String chainName = Optional.ofNullable(chain.attributeValue(ID)).orElse(chain.attributeValue(NAME));
|
||||
if(!abstratChainMap.containsKey(chainName)){
|
||||
String chainId = Optional.ofNullable(chain.attributeValue(ID)).orElse(chain.attributeValue(NAME));
|
||||
if(!abstratChainMap.containsKey(chainId)){
|
||||
parseOneChainConsumer.accept(chain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.yomahub.liteflow.script.ScriptExecutor;
|
||||
import com.yomahub.liteflow.script.exception.ScriptLoadException;
|
||||
import com.yomahub.liteflow.script.javax.vo.JavaxSettingMapKey;
|
||||
import com.yomahub.liteflow.util.CopyOnWriteHashMap;
|
||||
import org.noear.liquor.Utils;
|
||||
import org.noear.liquor.eval.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -65,7 +66,7 @@ public class JavaxExecutor extends ScriptExecutor {
|
||||
throw new ScriptLoadException(errorMsg);
|
||||
}
|
||||
Execable execable = compiledScriptMap.get(wrap.getNodeId());
|
||||
return execable.exec(wrap);
|
||||
return execable.exec(Utils.asMap("_meta", wrap));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.yomahub.liteflow.test.script.javapro.parseOneMode;
|
||||
|
||||
import com.yomahub.liteflow.core.FlowExecutor;
|
||||
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;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@TestPropertySource(value = "classpath:/parseOneMode/application.properties")
|
||||
@SpringBootTest(classes = ScriptJavaxProParseOneModeTest.class)
|
||||
@EnableAutoConfiguration
|
||||
public class ScriptJavaxProParseOneModeTest extends BaseTest {
|
||||
|
||||
@Resource
|
||||
private FlowExecutor flowExecutor;
|
||||
|
||||
@Test
|
||||
public void testParseOneMode() {
|
||||
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
|
||||
Assertions.assertTrue(response.isSuccess());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
liteflow.rule-source=parseOneMode/flow.xml
|
||||
liteflow.parse-mode=PARSE_ONE_ON_FIRST_EXEC
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
|
||||
<flow>
|
||||
<nodes>
|
||||
<node id="s1" name="普通脚本1" type="script" language="java">
|
||||
<![CDATA[
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import com.yomahub.liteflow.slot.DefaultContext;
|
||||
import com.yomahub.liteflow.spi.holder.ContextAwareHolder;
|
||||
import com.yomahub.liteflow.test.script.javaxpro.common.cmp.Person;
|
||||
import com.yomahub.liteflow.test.script.javaxpro.common.cmp.TestDomain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Demo extends NodeComponent {
|
||||
@Override
|
||||
public void process() throws Exception {
|
||||
System.out.println("hello world");
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</node>
|
||||
|
||||
|
||||
</nodes>
|
||||
|
||||
<chain name="chain1">
|
||||
THEN(s1);
|
||||
</chain>
|
||||
|
||||
</flow>
|
||||
2
pom.xml
2
pom.xml
@@ -77,7 +77,7 @@
|
||||
<redisson.version>3.21.0</redisson.version>
|
||||
<janino.version>3.1.12</janino.version>
|
||||
<kotlin.version>1.9.23</kotlin.version>
|
||||
<liquor.version>1.4.0</liquor.version>
|
||||
<liquor.version>1.5.6</liquor.version>
|
||||
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
|
||||
<sharding-jdbc.version>4.1.1</sharding-jdbc.version>
|
||||
<apache-commons-test.version>1.13.1</apache-commons-test.version>
|
||||
|
||||
Reference in New Issue
Block a user