From 9421ace5e0b19cd49fb05954c9661a72eb2751ac Mon Sep 17 00:00:00 2001 From: sorghum Date: Wed, 31 Aug 2022 15:00:19 +0800 Subject: [PATCH] =?UTF-8?q?#future=20#I581A1=20@LiteflowMethod=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3=E6=94=AF=E6=8C=81=E5=B0=86=E6=96=B9=E6=B3=95=E5=A3=B0?= =?UTF-8?q?=E6=98=8E=E6=88=90=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../liteflow/annotation/LiteflowMethod.java | 4 ++ .../liteflow/core/proxy/ComponentProxy.java | 63 +++++++++++-------- .../com/yomahub/liteflow/flow/FlowBus.java | 53 +++++++++------- .../liteflow/util/LiteFlowProxyUtil.java | 11 ++-- .../liteflow/spring/ComponentScanner.java | 19 ++++-- .../LiteflowMultiELDeclSpringbootTest.java | 41 ++++++++++++ .../cmp/MultiCmpConfiguration.java | 49 +++++++++++++++ .../src/test/resources/cmpMulti/flow.el.xml | 6 ++ 8 files changed, 189 insertions(+), 57 deletions(-) create mode 100644 liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/cmpMultiNode/LiteflowMultiELDeclSpringbootTest.java create mode 100644 liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/cmpMultiNode/cmp/MultiCmpConfiguration.java create mode 100644 liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/cmpMulti/flow.el.xml diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/annotation/LiteflowMethod.java b/liteflow-core/src/main/java/com/yomahub/liteflow/annotation/LiteflowMethod.java index c43b60df8..11b6fb6a0 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/annotation/LiteflowMethod.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/annotation/LiteflowMethod.java @@ -11,4 +11,8 @@ import java.lang.annotation.*; public @interface LiteflowMethod { LiteFlowMethodEnum value(); + + // 节点ID,用于区分节点 + // 默认为空 则按照Spring模式下BeanName为准。 + String nodeId() default ""; } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/core/proxy/ComponentProxy.java b/liteflow-core/src/main/java/com/yomahub/liteflow/core/proxy/ComponentProxy.java index 50b8d5ba2..c16148565 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/core/proxy/ComponentProxy.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/core/proxy/ComponentProxy.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.*; import com.yomahub.liteflow.annotation.LiteflowMethod; import com.yomahub.liteflow.core.NodeComponent; import com.yomahub.liteflow.exception.ComponentMethodDefineErrorException; +import com.yomahub.liteflow.exception.LiteFlowException; import com.yomahub.liteflow.util.LiteFlowProxyUtil; import com.yomahub.liteflow.util.SerialsUtil; import net.bytebuddy.ByteBuddy; @@ -15,10 +16,7 @@ import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; /** @@ -42,7 +40,7 @@ public class ComponentProxy { this.clazz = clazz; } - public Object getProxy() throws Exception{ + public List getProxyList() throws Exception{ //这里要判断bean是否是spring代理过的bean,如果是代理过的bean需要取到原class对象 Class beanClazz; if (LiteFlowProxyUtil.isCglibProxyClass(bean.getClass())){ @@ -51,30 +49,37 @@ public class ComponentProxy { beanClazz = bean.getClass(); } - //得到当前bean里所覆盖的组件方法(一定是被@LiteFlowMethod修饰的),自己定义的不算 - List methodStrList = Arrays.stream(beanClazz.getDeclaredMethods()).filter( + //得到当前bean里所覆盖的LiteflowMethod(一定是被@LiteFlowMethod修饰的),自己定义的不算 + Map> methodListMap = Arrays.stream(beanClazz.getDeclaredMethods()).filter( m -> m.getAnnotation(LiteflowMethod.class) != null - ).map(m -> { - LiteflowMethod liteflowMethod = m.getAnnotation(LiteflowMethod.class); - return liteflowMethod.value().getMethodName(); + ).map(m -> m.getAnnotation(LiteflowMethod.class)).collect(Collectors.groupingBy(LiteflowMethod::nodeId)); + return methodListMap.entrySet().stream().map(entry -> { + String activeNodeId = StrUtil.isEmpty(entry.getKey()) ? nodeId : entry.getKey(); + List methodList = entry.getValue(); + try { + //创建对象 + //这里package进行了重设,放到了被代理对象的所在目录 + //生成的对象也加了上被代理对象拥有的注解 + //被拦截的对象也根据被代理对象根据@LiteFlowMethod所标注的进行了动态判断 + Object instance = new ByteBuddy().subclass(clazz) + .name(StrUtil.format("{}.ByteBuddy${}${}", + ClassUtil.getPackage(bean.getClass()), + activeNodeId, + SerialsUtil.generateShortUUID())) + .method(ElementMatchers.namedOneOf(methodList.stream().map(m -> m.value().getMethodName()).toArray(String[]::new))) + .intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean))) + .annotateType(bean.getClass().getAnnotations()) + .make() + .load(ComponentProxy.class.getClassLoader()) + .getLoaded() + .newInstance(); + NodeComponent nodeComponent = (NodeComponent) instance; + nodeComponent.setNodeId(activeNodeId); + return nodeComponent; + } catch (Exception e) { + throw new LiteFlowException(e); + } }).collect(Collectors.toList()); - - //创建对象 - //这里package进行了重设,放到了被代理对象的所在目录 - //生成的对象也加了上被代理对象拥有的注解 - //被拦截的对象也根据被代理对象根据@LiteFlowMethod所标注的进行了动态判断 - return new ByteBuddy().subclass(clazz) - .name(StrUtil.format("{}.ByteBuddy${}${}", - ClassUtil.getPackage(bean.getClass()), - nodeId, - SerialsUtil.generateShortUUID())) - .method(ElementMatchers.namedOneOf(methodStrList.toArray(new String[]{}))) - .intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean))) - .annotateType(bean.getClass().getAnnotations()) - .make() - .load(ComponentProxy.class.getClassLoader()) - .getLoaded() - .newInstance(); } public class AopInvocationHandler implements InvocationHandler { @@ -93,6 +98,10 @@ public class ComponentProxy { List liteFlowMethodBeanList = Arrays.stream(ReflectUtil.getMethods(bean.getClass())).filter(m -> { LiteflowMethod liteFlowMethod = m.getAnnotation(LiteflowMethod.class); return ObjectUtil.isNotNull(liteFlowMethod); + }).filter(m -> { + // 过滤不属于当前NodeComponent的方法 + LiteflowMethod liteFlowMethod = m.getAnnotation(LiteflowMethod.class); + return StrUtil.isEmpty(liteFlowMethod.nodeId())|| Objects.equals(liteFlowMethod.nodeId(),((NodeComponent) proxy).getNodeId()); }).map(m -> { LiteflowMethod liteFlowMethod = m.getAnnotation(LiteflowMethod.class); return new LiteFlowMethodBean(liteFlowMethod.value().getMethodName(), m); diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java index ebf5b53c8..826205bcd 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java @@ -9,6 +9,7 @@ package com.yomahub.liteflow.flow; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -36,7 +37,10 @@ import com.yomahub.liteflow.util.LiteFlowProxyUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * 流程元数据类 @@ -156,7 +160,7 @@ public class FlowBus { try { //判断此类是否是声明式的组件,如果是声明式的组件,就用动态代理生成实例 //如果不是声明式的,就用传统的方式进行判断 - NodeComponent cmpInstance = null; + List cmpInstances = new ArrayList<>(); if (LiteFlowProxyUtil.isDeclareCmp(cmpClazz)){ //这里的逻辑要仔细看下 //如果是spring体系,把原始的类往spring上下文中进行注册,那么会走到ComponentScanner中 @@ -167,43 +171,50 @@ public class FlowBus { ContextAware contextAware = ContextAwareHolder.loadContextAware(); Object bean = ContextAwareHolder.loadContextAware().registerBean(nodeId, cmpClazz); if (LocalContextAware.class.isAssignableFrom(contextAware.getClass())){ - cmpInstance = LiteFlowProxyUtil.proxy2NodeComponent(bean, nodeId); + cmpInstances = LiteFlowProxyUtil.proxy2NodeComponent(bean, nodeId); }else { - cmpInstance = (NodeComponent) bean; + cmpInstances = ListUtil.toList((NodeComponent) bean); } }else{ //以node方式配置,本质上是为了适配无spring的环境,如果有spring环境,其实不用这么配置 //这里的逻辑是判断是否能从spring上下文中取到,如果没有spring,则就是new instance了 //如果是script类型的节点,因为class只有一个,所以也不能注册进spring上下文,注册的时候需要new Instance if (!CollectionUtil.newArrayList(NodeTypeEnum.SCRIPT, NodeTypeEnum.SWITCH_SCRIPT, NodeTypeEnum.IF_SCRIPT).contains(type)){ - cmpInstance = (NodeComponent) ContextAwareHolder.loadContextAware().registerOrGet(nodeId, cmpClazz); + cmpInstances = ListUtil.toList((NodeComponent) ContextAwareHolder.loadContextAware().registerOrGet(nodeId, cmpClazz)); } - - if (ObjectUtil.isNull(cmpInstance)) { - cmpInstance = (NodeComponent) cmpClazz.newInstance(); + // 去除null元素 + cmpInstances.remove(null); + // 如果为空 + if (cmpInstances.isEmpty()) { + NodeComponent cmpInstance = (NodeComponent) cmpClazz.newInstance(); + cmpInstances.add(cmpInstance); } } - //进行初始化 - cmpInstance = ComponentInitializer.loadInstance().initComponent(cmpInstance, type, name, nodeId); + cmpInstances = cmpInstances.stream().map(cmpInstance -> ComponentInitializer.loadInstance().initComponent(cmpInstance, type, name, nodeId)).collect(Collectors.toList()); //初始化Node - Node node = new Node(cmpInstance); + List nodes = cmpInstances.stream().map(Node::new).collect(Collectors.toList()); - //如果是脚本节点(普通脚本节点/条件脚本节点),则还要加载script脚本 - if (StrUtil.isNotBlank(script)){ - node.setScript(script); - if (type.equals(NodeTypeEnum.SCRIPT)){ - ((ScriptComponent)cmpInstance).loadScript(script); - }else if(type.equals(NodeTypeEnum.SWITCH_SCRIPT)){ - ((ScriptSwitchComponent)cmpInstance).loadScript(script); - }else if(type.equals(NodeTypeEnum.IF_SCRIPT)){ - ((ScriptIfComponent)cmpInstance).loadScript(script); + for (int i = 0; i < nodes.size(); i++) { + Node node = nodes.get(i); + NodeComponent cmpInstance = cmpInstances.get(i); + if (StrUtil.isNotBlank(script)){ + node.setScript(script); + if (type.equals(NodeTypeEnum.SCRIPT)){ + ((ScriptComponent)cmpInstance).loadScript(script); + }else if(type.equals(NodeTypeEnum.SWITCH_SCRIPT)){ + ((ScriptSwitchComponent)cmpInstance).loadScript(script); + }else if(type.equals(NodeTypeEnum.IF_SCRIPT)){ + ((ScriptIfComponent)cmpInstance).loadScript(script); + } } + String activeNodeId = StrUtil.isEmpty(cmpInstance.getNodeId()) ? nodeId : cmpInstance.getNodeId(); + //如果是脚本节点(普通脚本节点/条件脚本节点),则还要加载script脚本 + nodeMap.put(activeNodeId, node); } - - nodeMap.put(nodeId, node); } catch (Exception e) { + e.printStackTrace(); String error = StrUtil.format("component[{}] register error", StrUtil.isEmpty(name)?nodeId:StrUtil.format("{}({})",nodeId,name)); LOG.error(error); throw new ComponentCannotRegisterException(error); diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/util/LiteFlowProxyUtil.java b/liteflow-core/src/main/java/com/yomahub/liteflow/util/LiteFlowProxyUtil.java index e5cb56873..33c3c612d 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/util/LiteFlowProxyUtil.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/util/LiteFlowProxyUtil.java @@ -16,6 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; +import java.util.List; /** * 组件代理类通用方法 @@ -61,8 +62,8 @@ public class LiteFlowProxyUtil { return flag3; } - //对一个满足声明式的bean进行代理 - public static NodeComponent proxy2NodeComponent(Object bean, String nodeId){ + //对一个满足声明式的bean进行代理,生成代理类数组 + public static List proxy2NodeComponent(Object bean, String nodeId){ try{ LiteflowCmpDefine liteflowCmpDefine = bean.getClass().getAnnotation(LiteflowCmpDefine.class); LiteflowSwitchCmpDefine liteflowSwitchCmpDefine = bean.getClass().getAnnotation(LiteflowSwitchCmpDefine.class); @@ -71,17 +72,17 @@ public class LiteFlowProxyUtil { ComponentProxy proxy; if (ObjectUtil.isNotNull(liteflowCmpDefine)){ proxy = new ComponentProxy(nodeId, bean, NodeComponent.class); - return (NodeComponent) proxy.getProxy(); + return proxy.getProxyList(); } if (ObjectUtil.isNotNull(liteflowSwitchCmpDefine)){ proxy = new ComponentProxy(nodeId, bean, NodeSwitchComponent.class); - return (NodeSwitchComponent) proxy.getProxy(); + return proxy.getProxyList(); } if (ObjectUtil.isNotNull(liteflowIfCmpDefine)){ proxy = new ComponentProxy(nodeId, bean, NodeIfComponent.class); - return (NodeIfComponent) proxy.getProxy(); + return proxy.getProxyList(); } throw new RuntimeException(); diff --git a/liteflow-spring/src/main/java/com/yomahub/liteflow/spring/ComponentScanner.java b/liteflow-spring/src/main/java/com/yomahub/liteflow/spring/ComponentScanner.java index ffbde9a47..7665643aa 100644 --- a/liteflow-spring/src/main/java/com/yomahub/liteflow/spring/ComponentScanner.java +++ b/liteflow-spring/src/main/java/com/yomahub/liteflow/spring/ComponentScanner.java @@ -8,9 +8,9 @@ */ package com.yomahub.liteflow.spring; +import cn.hutool.core.util.StrUtil; import com.yomahub.liteflow.aop.ICmpAroundAspect; import com.yomahub.liteflow.core.NodeComponent; -import com.yomahub.liteflow.flow.FlowBus; import com.yomahub.liteflow.property.LiteflowConfig; import com.yomahub.liteflow.util.LOGOPrinter; import com.yomahub.liteflow.util.LiteFlowProxyUtil; @@ -20,6 +20,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -62,9 +63,19 @@ public class ComponentScanner implements BeanPostProcessor { //如果是,就缓存到类属性的map中 if (LiteFlowProxyUtil.isDeclareCmp(bean.getClass())){ LOG.info("proxy component[{}] has been found", beanName); - NodeComponent nodeComponent = LiteFlowProxyUtil.proxy2NodeComponent(bean, beanName); - nodeComponentMap.put(beanName, nodeComponent); - return nodeComponent; + List nodeComponents = LiteFlowProxyUtil.proxy2NodeComponent(bean, beanName); + nodeComponents.forEach( + nodeComponent -> { + String nodeId = nodeComponent.getNodeId(); + nodeId = StrUtil.isEmpty(nodeId) ? beanName : nodeId; + nodeComponentMap.put(nodeId, nodeComponent); + } + ); + // 只有注解支持单bean多Node,所以一个直接返回 + if (nodeComponents.size() == 1){ + return nodeComponents.get(0); + } + return bean; } // 组件的扫描发现,扫到之后缓存到类属性map中 diff --git a/liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/cmpMultiNode/LiteflowMultiELDeclSpringbootTest.java b/liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/cmpMultiNode/LiteflowMultiELDeclSpringbootTest.java new file mode 100644 index 000000000..0a327d954 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/cmpMultiNode/LiteflowMultiELDeclSpringbootTest.java @@ -0,0 +1,41 @@ +package com.yomahub.liteflow.test.cmpMultiNode; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.test.BaseTest; +import com.yomahub.liteflow.test.cmpRetry.LiteflowRetryELDeclSpringbootTest; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +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.junit4.SpringRunner; + +import javax.annotation.Resource; + + +/** + * 测试springboot下的节点执行器 + * @author sorghum + */ +@RunWith(SpringRunner.class) +@TestPropertySource(value = "classpath:/cmpMulti/application.properties") +@SpringBootTest(classes = LiteflowMultiELDeclSpringbootTest.class) +@EnableAutoConfiguration +@ComponentScan({"com.yomahub.liteflow.test.cmpMultiNode.cmp"}) +public class LiteflowMultiELDeclSpringbootTest extends BaseTest { + + @Resource + private FlowExecutor flowExecutor; + + //全局重试配置测试 + @Test + public void testBase() throws Exception{ + LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg"); + Assert.assertTrue(response.isSuccess()); + String executeStepStr = response.getExecuteStepStr(); + Assert.assertEquals("a==>b==>c==>d==>e",executeStepStr); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/cmpMultiNode/cmp/MultiCmpConfiguration.java b/liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/cmpMultiNode/cmp/MultiCmpConfiguration.java new file mode 100644 index 000000000..2529b6c00 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/cmpMultiNode/cmp/MultiCmpConfiguration.java @@ -0,0 +1,49 @@ +package com.yomahub.liteflow.test.cmpMultiNode.cmp; + +import com.yomahub.liteflow.annotation.LiteflowCmpDefine; +import com.yomahub.liteflow.annotation.LiteflowMethod; +import com.yomahub.liteflow.core.NodeComponent; +import com.yomahub.liteflow.enums.LiteFlowMethodEnum; +import org.springframework.context.annotation.Configuration; + +/** + * 多cmp配置 + * + * @author sorghum + */ +@Configuration +@LiteflowCmpDefine +public class MultiCmpConfiguration { + + @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS,nodeId = "a") + public void processA(NodeComponent bindCmp) { + System.out.println("ACmp executed!"); + } + + @LiteflowMethod(value = LiteFlowMethodEnum.IS_ACCESS,nodeId = "a") + public boolean isAccessA(NodeComponent bindCmp) { + System.out.println("ACmp isAccessA!"); + return true; + } + + @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS,nodeId = "b") + public void processB(NodeComponent bindCmp) { + System.out.println("BCmp executed!"); + } + + @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS,nodeId = "c") + public void processC(NodeComponent bindCmp) { + System.out.println("CCmp executed!"); + } + + @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS,nodeId = "d") + public void processD(NodeComponent bindCmp) { + System.out.println("DCmp executed!"); + } + + @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS,nodeId = "e") + public void processE(NodeComponent bindCmp) { + System.out.println("ECmp executed!"); + } + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/cmpMulti/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/cmpMulti/flow.el.xml new file mode 100644 index 000000000..2beecda51 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/cmpMulti/flow.el.xml @@ -0,0 +1,6 @@ + + + + THEN(a,b,c,d,e); + + \ No newline at end of file