feat(core): 新增 PostProcessNodeExecuteLifeCycle 节点执行生命周期扩展点

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
everywhere.z 2026-06-29 00:29:22 +08:00
parent 5af09b1cc0
commit bbb2d403a9
10 changed files with 187 additions and 0 deletions

View File

@ -24,6 +24,8 @@ import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.flow.entity.CmpStep;
import com.yomahub.liteflow.flow.executor.DefaultNodeExecutor;
import com.yomahub.liteflow.flow.executor.NodeExecutor;
import com.yomahub.liteflow.lifecycle.LifeCycleHolder;
import com.yomahub.liteflow.lifecycle.PostProcessNodeExecuteLifeCycle;
import com.yomahub.liteflow.log.LFLog;
import com.yomahub.liteflow.log.LFLoggerManager;
import com.yomahub.liteflow.monitor.CompStatistics;
@ -109,6 +111,15 @@ public abstract class NodeComponent{
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 节点执行异常引用 finally 中的生命周期钩子使用成功为 null
Exception nodeExecuteException = null;
// 节点执行生命周期list 可叠加 monitorBus 独立
List<PostProcessNodeExecuteLifeCycle> nodeExecuteLifeCycleList = LifeCycleHolder.getPostProcessNodeExecuteLifeCycleList();
if (!nodeExecuteLifeCycleList.isEmpty()) {
nodeExecuteLifeCycleList.forEach(lc -> lc.postProcessBeforeNodeExecute(self));
}
try {
LOG.info("[O]start component[{}] execution", self.getDisplayName());
@ -128,6 +139,7 @@ public abstract class NodeComponent{
// 步骤状态设为false并加入异常
cmpStep.setSuccess(false);
cmpStep.setException(e);
nodeExecuteException = e;
// 执行失败后回调方法
// 这里要注意失败方法本身抛出错误只打出堆栈往外抛出的还是主要的异常
@ -162,6 +174,12 @@ public abstract class NodeComponent{
CompStatistics statistics = new CompStatistics(this.getClass().getSimpleName(), timeSpent);
monitorBus.addStatistics(statistics);
}
// 节点执行生命周期带上耗时与异常
if (!nodeExecuteLifeCycleList.isEmpty()) {
final Exception finalEx = nodeExecuteException;
nodeExecuteLifeCycleList.forEach(lc -> lc.postProcessAfterNodeExecute(self, timeSpent, finalEx));
}
}
}

View File

@ -22,6 +22,8 @@ public class LifeCycleHolder {
private static final List<PostProcessChainExecuteLifeCycle> POST_PROCESS_CHAIN_EXECUTE_LIFE_CYCLE_LIST = new ArrayList<>();
private static final List<PostProcessNodeExecuteLifeCycle> POST_PROCESS_NODE_EXECUTE_LIFE_CYCLE_LIST = new ArrayList<>();
public static void addLifeCycle(LifeCycle lifeCycle){
if (PostProcessScriptEngineInitLifeCycle.class.isAssignableFrom(lifeCycle.getClass())){
@ -34,6 +36,8 @@ public class LifeCycleHolder {
POST_PROCESS_FLOW_EXECUTE_LIFE_CYCLE_LIST.add((PostProcessFlowExecuteLifeCycle)lifeCycle);
}else if(PostProcessChainExecuteLifeCycle.class.isAssignableFrom(lifeCycle.getClass())){
POST_PROCESS_CHAIN_EXECUTE_LIFE_CYCLE_LIST.add((PostProcessChainExecuteLifeCycle)lifeCycle);
}else if(PostProcessNodeExecuteLifeCycle.class.isAssignableFrom(lifeCycle.getClass())){
POST_PROCESS_NODE_EXECUTE_LIFE_CYCLE_LIST.add((PostProcessNodeExecuteLifeCycle)lifeCycle);
}
}
@ -57,11 +61,16 @@ public class LifeCycleHolder {
return POST_PROCESS_CHAIN_EXECUTE_LIFE_CYCLE_LIST;
}
public static List<PostProcessNodeExecuteLifeCycle> getPostProcessNodeExecuteLifeCycleList() {
return POST_PROCESS_NODE_EXECUTE_LIFE_CYCLE_LIST;
}
public static void clean(){
POST_PROCESS_SCRIPT_ENGINE_INIT_LIFE_CYCLE_LIST.clear();
POST_PROCESS_CHAIN_BUILD_LIFE_CYCLE_LIST.clear();
POST_PROCESS_NODE_BUILD_LIFE_CYCLE_LIST.clear();
POST_PROCESS_FLOW_EXECUTE_LIFE_CYCLE_LIST.clear();
POST_PROCESS_CHAIN_EXECUTE_LIFE_CYCLE_LIST.clear();
POST_PROCESS_NODE_EXECUTE_LIFE_CYCLE_LIST.clear();
}
}

View File

@ -0,0 +1,21 @@
package com.yomahub.liteflow.lifecycle;
import com.yomahub.liteflow.core.NodeComponent;
/**
* 生命周期接口
* 执行单个组件Node的时候
*
* @author Bryan.Zhang
*/
public interface PostProcessNodeExecuteLifeCycle extends LifeCycle {
void postProcessBeforeNodeExecute(NodeComponent cmp);
/**
* @param cmp 执行完成的组件 nodeId / chainId / type
* @param timeSpent 本次执行耗时毫秒
* @param e 执行异常成功时为 null
*/
void postProcessAfterNodeExecute(NodeComponent cmp, long timeSpent, Exception e);
}

View File

@ -0,0 +1,32 @@
package com.yomahub.liteflow.test.nodeexecute;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/** 测试用静态收集器,记录节点执行钩子的回调 */
public class NodeExecuteCollector {
public static class Record {
public final String nodeId;
public final long timeSpent;
public final Exception exception;
public Record(String nodeId, long timeSpent, Exception exception) {
this.nodeId = nodeId;
this.timeSpent = timeSpent;
this.exception = exception;
}
}
public static final List<String> BEFORE = new CopyOnWriteArrayList<>();
public static final List<Record> AFTER = new CopyOnWriteArrayList<>();
public static void clear() {
BEFORE.clear();
AFTER.clear();
}
public static List<Record> afters() {
return Collections.unmodifiableList(AFTER);
}
}

View File

@ -0,0 +1,55 @@
package com.yomahub.liteflow.test.nodeexecute;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.test.BaseTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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 javax.annotation.Resource;
@TestPropertySource(value = "classpath:/nodeexecute/application.properties")
@SpringBootTest(classes = NodeExecuteLifeCycleSpringbootTest.class)
@EnableAutoConfiguration
@ComponentScan({ "com.yomahub.liteflow.test.nodeexecute" })
public class NodeExecuteLifeCycleSpringbootTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
@BeforeEach
public void setUp() {
NodeExecuteCollector.clear();
}
@Test
public void testSuccessHooks() {
LiteflowResponse response = flowExecutor.execute2Resp("okChain", "arg");
Assertions.assertTrue(response.isSuccess());
// okChain = THEN(neA, neA) 2 次节点执行
Assertions.assertEquals(2, NodeExecuteCollector.BEFORE.size());
Assertions.assertEquals(2, NodeExecuteCollector.afters().size());
for (NodeExecuteCollector.Record r : NodeExecuteCollector.afters()) {
Assertions.assertEquals("neA", r.nodeId);
Assertions.assertNull(r.exception);
Assertions.assertTrue(r.timeSpent >= 0);
}
}
@Test
public void testErrorHookCarriesException() {
LiteflowResponse response = flowExecutor.execute2Resp("boomChain", "arg");
Assertions.assertFalse(response.isSuccess());
// boomChain = THEN(neA, neBoom)neA 成功neBoom 抛异常
Assertions.assertEquals(2, NodeExecuteCollector.afters().size());
NodeExecuteCollector.Record boom = NodeExecuteCollector.afters().get(1);
Assertions.assertEquals("neBoom", boom.nodeId);
Assertions.assertNotNull(boom.exception);
Assertions.assertEquals(IllegalStateException.class, boom.exception.getClass());
}
}

View File

@ -0,0 +1,17 @@
package com.yomahub.liteflow.test.nodeexecute;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.lifecycle.PostProcessNodeExecuteLifeCycle;
import org.springframework.stereotype.Component;
@Component
public class TestNodeExecuteLifeCycle implements PostProcessNodeExecuteLifeCycle {
@Override
public void postProcessBeforeNodeExecute(NodeComponent cmp) {
NodeExecuteCollector.BEFORE.add(cmp.getNodeId());
}
@Override
public void postProcessAfterNodeExecute(NodeComponent cmp, long timeSpent, Exception e) {
NodeExecuteCollector.AFTER.add(new NodeExecuteCollector.Record(cmp.getNodeId(), timeSpent, e));
}
}

View File

@ -0,0 +1,12 @@
package com.yomahub.liteflow.test.nodeexecute.cmp;
import com.yomahub.liteflow.core.NodeComponent;
import org.springframework.stereotype.Component;
@Component("neA")
public class NeACmp extends NodeComponent {
@Override
public void process() {
// 正常组件
}
}

View File

@ -0,0 +1,12 @@
package com.yomahub.liteflow.test.nodeexecute.cmp;
import com.yomahub.liteflow.core.NodeComponent;
import org.springframework.stereotype.Component;
@Component("neBoom")
public class NeBoomCmp extends NodeComponent {
@Override
public void process() {
throw new IllegalStateException("boom");
}
}

View File

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

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
<flow>
<chain name="okChain">
THEN(neA, neA);
</chain>
<chain name="boomChain">
THEN(neA, neBoom);
</chain>
</flow>