mirror of https://gitee.com/dromara/liteFlow
feat(core): 新增 PostProcessNodeExecuteLifeCycle 节点执行生命周期扩展点
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5af09b1cc0
commit
bbb2d403a9
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
// 正常组件
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
liteflow.rule-source=nodeexecute/flow.el.xml
|
||||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue