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:
DaleLee
2025-07-07 22:48:46 +08:00
9 changed files with 174 additions and 88 deletions

View File

@@ -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()));
}

View File

@@ -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的引用

View File

@@ -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());
}

View File

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

View File

@@ -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

View File

@@ -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());
}
}

View File

@@ -0,0 +1,2 @@
liteflow.rule-source=parseOneMode/flow.xml
liteflow.parse-mode=PARSE_ONE_ON_FIRST_EXEC

View File

@@ -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>

View File

@@ -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>