#feature #I581A1 @LiteflowMethod注解支持将方法声明成组件

This commit is contained in:
sorghum
2022-08-31 15:00:19 +08:00
parent fb67fb4b39
commit dbd3c5a4d7
8 changed files with 189 additions and 57 deletions

View File

@@ -11,4 +11,8 @@ import java.lang.annotation.*;
public @interface LiteflowMethod {
LiteFlowMethodEnum value();
// 节点ID用于区分节点
// 默认为空 则按照Spring模式下BeanName为准。
String nodeId() default "";
}

View File

@@ -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<NodeComponent> 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<String> methodStrList = Arrays.stream(beanClazz.getDeclaredMethods()).filter(
//得到当前bean里所覆盖的LiteflowMethod(一定是被@LiteFlowMethod修饰的),自己定义的不算
Map<String, List<LiteflowMethod>> 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<LiteflowMethod> 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<LiteFlowMethodBean> 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);

View File

@@ -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<NodeComponent> 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<Node> 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);

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="chain1">
THEN(a,b,c,d,e);
</chain>
</flow>