From 0f2c88b657894d01cf21266d861294ef474674f8 Mon Sep 17 00:00:00 2001 From: "everywhere.z" Date: Mon, 29 Dec 2025 11:38:22 +0800 Subject: [PATCH] =?UTF-8?q?feature=20#IDGGGC=20Java=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=97=B6=E6=89=B9=E9=87=8F=E7=BC=96=E8=AF=91?= =?UTF-8?q?=EF=BC=8C=E5=8A=A0=E5=BF=AB=E5=90=AF=E5=8A=A8=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yomahub/liteflow/core/FlowExecutor.java | 11 +++ .../liteflow/script/ScriptExecutor.java | 16 ++++ .../script/ScriptExecutorFactory.java | 3 +- .../script/validator/ScriptValidator.java | 7 +- .../liteflow-script-java/pom.xml | 2 +- .../script/javaxpro/JavaxProExecutor.java | 74 +++++++++++++++++-- .../batchLoad/ScriptJavaxProBatchELTest.java | 43 +++++++++++ pom.xml | 41 +++++----- 8 files changed, 162 insertions(+), 35 deletions(-) create mode 100644 liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/java/com/yomahub/liteflow/test/batchLoad/ScriptJavaxProBatchELTest.java diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java index aa725accb..f2628e21c 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java @@ -49,6 +49,7 @@ import com.yomahub.liteflow.util.ElRegexUtil; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.stream.Collectors; @@ -66,6 +67,8 @@ public class FlowExecutor { private LiteflowConfig liteflowConfig; + private AtomicBoolean startUpPhase = new AtomicBoolean(false); + public FlowExecutor() { // 设置FlowExecutor的Holder,虽然大部分地方都可以通过Spring上下文获取到,但放入Holder,还是为了某些地方能方便的取到 FlowExecutorHolder.setHolder(this); @@ -91,6 +94,8 @@ public class FlowExecutor { * isStart表示是否是系统启动阶段,启动阶段要做额外的事情,而因为reload所调用的init就不用做 */ public void init(boolean isStart) { + startUpPhase.compareAndSet(false, true); + if (ObjectUtil.isNull(liteflowConfig)) { throw new ConfigErrorException("config error, please check liteflow config property"); } @@ -242,6 +247,8 @@ public class FlowExecutor { if (liteflowConfig.getChainCacheEnabled()) { evaluateChainCacheCapacity(); } + + startUpPhase.compareAndSet(true, false); } // 此方法就是从原有的配置源主动拉取新的进行刷新 @@ -709,4 +716,8 @@ public class FlowExecutor { +"it is recommended to be greater than 30% of the number of chains", capacity, chainNum); } } + + public AtomicBoolean getStartUpPhase() { + return startUpPhase; + } } \ No newline at end of file diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/script/ScriptExecutor.java b/liteflow-core/src/main/java/com/yomahub/liteflow/script/ScriptExecutor.java index 4eb8e7bc8..9c42967bc 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/script/ScriptExecutor.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/script/ScriptExecutor.java @@ -3,6 +3,7 @@ package com.yomahub.liteflow.script; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; +import com.yomahub.liteflow.common.entity.ValidationResp; import com.yomahub.liteflow.enums.ScriptTypeEnum; import com.yomahub.liteflow.exception.LiteFlowException; import com.yomahub.liteflow.lifecycle.LifeCycleHolder; @@ -37,6 +38,12 @@ public abstract class ScriptExecutor { public abstract void load(String nodeId, String script); + // 第二段load,通常指合并一起编译/执行 + // 如果实现了第二段,说明第一段并未真正编译,而是把需要编译的存起来,留到第二段的时候一起编译 + public void loadSecondPhase(){ + + } + // 卸载脚本(不包含 node) public abstract void unLoad(String nodeId); @@ -101,6 +108,15 @@ public abstract class ScriptExecutor { */ public abstract Object compile(String script) throws Exception; + public ValidationResp validate(String script){ + try { + compile(script); + } catch (Exception e) { + return ValidationResp.fail(e); + } + return ValidationResp.success(); + } + public boolean executeIsAccess(ScriptExecuteWrap wrap){ return true; } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/script/ScriptExecutorFactory.java b/liteflow-core/src/main/java/com/yomahub/liteflow/script/ScriptExecutorFactory.java index 1b2b56160..b5ca3683a 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/script/ScriptExecutorFactory.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/script/ScriptExecutorFactory.java @@ -70,7 +70,6 @@ public class ScriptExecutorFactory { } public void cleanScriptCache() { - this.scriptExecutorMap.forEach((key, value) -> value.cleanCache()); + this.scriptExecutorMap.clear(); } - } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/script/validator/ScriptValidator.java b/liteflow-core/src/main/java/com/yomahub/liteflow/script/validator/ScriptValidator.java index 3cd6c3269..9f77e2d01 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/script/validator/ScriptValidator.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/script/validator/ScriptValidator.java @@ -58,12 +58,7 @@ public class ScriptValidator { } ScriptExecutor scriptExecutor = (scriptType != null) ? scriptExecutors.get(scriptType) : scriptExecutors.values().iterator().next(); - try { - scriptExecutor.compile(script); - } catch (Exception e) { - return ValidationResp.fail(e); - } - return ValidationResp.success(); + return scriptExecutor.validate(script); } /** diff --git a/liteflow-script-plugin/liteflow-script-java/pom.xml b/liteflow-script-plugin/liteflow-script-java/pom.xml index 31cf5bccd..a43d0283c 100644 --- a/liteflow-script-plugin/liteflow-script-java/pom.xml +++ b/liteflow-script-plugin/liteflow-script-java/pom.xml @@ -11,7 +11,7 @@ - + liteflow-script-java ${project.artifactId} diff --git a/liteflow-script-plugin/liteflow-script-javax-pro/src/main/java/com/yomahub/liteflow/script/javaxpro/JavaxProExecutor.java b/liteflow-script-plugin/liteflow-script-javax-pro/src/main/java/com/yomahub/liteflow/script/javaxpro/JavaxProExecutor.java index a69e4a0a3..d13d08208 100644 --- a/liteflow-script-plugin/liteflow-script-javax-pro/src/main/java/com/yomahub/liteflow/script/javaxpro/JavaxProExecutor.java +++ b/liteflow-script-plugin/liteflow-script-javax-pro/src/main/java/com/yomahub/liteflow/script/javaxpro/JavaxProExecutor.java @@ -1,7 +1,12 @@ package com.yomahub.liteflow.script.javaxpro; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; +import com.yomahub.liteflow.common.entity.ValidationResp; +import com.yomahub.liteflow.core.FlowExecutorHolder; +import com.yomahub.liteflow.core.FlowInitHook; import com.yomahub.liteflow.core.NodeComponent; import com.yomahub.liteflow.enums.ScriptTypeEnum; import com.yomahub.liteflow.property.LiteflowConfig; @@ -12,12 +17,17 @@ import com.yomahub.liteflow.script.exception.ScriptLoadException; import com.yomahub.liteflow.script.javaxpro.vo.JavaxProSettingMapKey; import com.yomahub.liteflow.util.CopyOnWriteHashMap; import org.noear.liquor.eval.CodeSpec; +import org.noear.liquor.eval.Execable; import org.noear.liquor.eval.Scripts; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.stream.Collectors; /** * Javax语言执行器,基于liquor @@ -26,6 +36,9 @@ import java.util.Map; * @since 2.13.0 */ public class JavaxProExecutor extends ScriptExecutor { + + private final Map codeSpecMap = new HashMap<>(); + private final Map compiledScriptMap = new CopyOnWriteHashMap<>(); private boolean isCache; @@ -37,13 +50,27 @@ public class JavaxProExecutor extends ScriptExecutor { isCache = Boolean.parseBoolean(isCacheValue); //如果有生命周期则执行相应生命周期实现 super.lifeCycle(null); + + // 注册第二段批次编译 + FlowInitHook.addHook(() -> { + loadSecondPhase(); + codeSpecMap.clear(); + return true; + }); return this; } @Override public void load(String nodeId, String script) { try{ - compiledScriptMap.put(nodeId, (NodeComponent) compile(script)); + boolean startUpPhase = FlowExecutorHolder.loadInstance().getStartUpPhase().get(); + if (startUpPhase){ + codeSpecMap.put((CodeSpec) compile(script), nodeId); + }else{ + compiledScriptMap.put(nodeId, (NodeComponent)compile(script)); + } + + }catch (InvocationTargetException e){ String errorMsg = StrUtil.format("script loading error for node[{}],error msg:{}", nodeId, e.getTargetException().getMessage()); throw new ScriptLoadException(errorMsg); @@ -53,6 +80,21 @@ public class JavaxProExecutor extends ScriptExecutor { } } + // 第二段编译,指的是合并编译,如果实现了这个方法,说明compile阶段没有真正编译 + @Override + public void loadSecondPhase() { + if (MapUtil.isEmpty(codeSpecMap)) { + return; + } + + Map execableMap = Scripts.compile(new ArrayList<>(codeSpecMap.keySet())); + + execableMap.forEach((k, v) -> { + NodeComponent nodeComponent = (NodeComponent)v.exec(); + compiledScriptMap.put(codeSpecMap.get(k), nodeComponent); + }); + } + @Override public void unLoad(String nodeId) { compiledScriptMap.remove(nodeId); @@ -80,12 +122,34 @@ public class JavaxProExecutor extends ScriptExecutor { return ScriptTypeEnum.JAVA; } + // 如果在启动时阶段,则不进行编译,只是暂存,然后由loadSecondPhase进行统一编译 + // 如果不是在启动阶段,则直接进行编译 @Override public Object compile(String script) throws Exception { - CodeSpec codeSpec = new CodeSpec(convertScript(script)) - .returnType(Object.class) - .cached(isCache); - return Scripts.eval(codeSpec); + boolean startUpPhase = FlowExecutorHolder.loadInstance().getStartUpPhase().get(); + if (startUpPhase){ + return new CodeSpec(convertScript(script)) + .returnType(Object.class) + .cached(isCache); + }else{ + CodeSpec codeSpec = new CodeSpec(convertScript(script)) + .returnType(Object.class) + .cached(isCache); + return Scripts.eval(codeSpec); + } + } + + @Override + public ValidationResp validate(String script){ + try { + CodeSpec codeSpec = new CodeSpec(convertScript(script)) + .returnType(Object.class) + .cached(isCache); + Scripts.eval(codeSpec); + } catch (Exception e) { + return ValidationResp.fail(e); + } + return ValidationResp.success(); } @Override diff --git a/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/java/com/yomahub/liteflow/test/batchLoad/ScriptJavaxProBatchELTest.java b/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/java/com/yomahub/liteflow/test/batchLoad/ScriptJavaxProBatchELTest.java new file mode 100644 index 000000000..00ba25033 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/java/com/yomahub/liteflow/test/batchLoad/ScriptJavaxProBatchELTest.java @@ -0,0 +1,43 @@ +package com.yomahub.liteflow.test.batchLoad; + +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:/common/application.properties") +@SpringBootTest(classes = ScriptJavaxProBatchELTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.script.javaxpro.common.cmp" }) +public class ScriptJavaxProBatchELTest extends BaseTest { + + @Resource + private FlowExecutor flowExecutor; + + @Test + public void testCommon1() { + LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg"); + DefaultContext context = response.getFirstContextBean(); + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals(6, (int)context.getData("s1")); + Assertions.assertEquals("hello,jack", context.getData("hi")); + Assertions.assertEquals(47100, (Integer) context.getData("salary")); + } + + @Test + public void testCommon2() { + LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg"); + Assertions.assertTrue(response.isSuccess()); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index cf8586d92..4823f057a 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ - 2.15.2 + 2.15.3 UTF-8 UTF-8 8 @@ -77,7 +77,7 @@ 3.21.0 3.1.12 2.2.0 - 1.5.8 + 1.6.6 4.3.1 4.1.1 1.14.0 @@ -363,6 +363,24 @@ + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + true + + + + compile + + jar + + + + + org.apache.maven.plugins maven-surefire-plugin @@ -463,25 +481,6 @@ - - - - org.apache.maven.plugins - maven-source-plugin - 3.3.1 - - true - - - - compile - - jar - - - - - org.apache.maven.plugins