写在前面

这里只介绍liteflow的简单基础使用以及作者对liteflow进行可视化扩展的相关阐述

一、背景及意义

背景:对于拥有复杂业务逻辑的系统承载着核心业务逻辑,这些核心业务逻辑涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取,内部其他系统RPC调用等等。项目几经易手,维护的成本就会越来越高。各种硬代码判断,分支条件越来越多。代码的抽象,复用率也越来越低,各个模块之间的耦合度很高。一小段逻辑的变动,会影响到其他模块,需要进行完整回归测试来验证。如要灵活改变业务流程的顺序,则要进行代码大改动进行抽象,重新写方法。实时热变更业务流程,几乎很难实现

意义:逻辑解耦、提高扩展性、降低维护成本、能力充分复用、流程灵活编排

二、常用流程编排框架

liteflow(开源)asyncTool(开源)JDEasyFlow(开源)disruptor介绍LiteFlow是一个非常强大的现代化的规则引擎框架,融合了编排特性和规则引擎的所有特性。如果你要对复杂业务逻辑进行新写或者重构,用LiteFlow最合适不过。它是一个编排式的规则引擎框架,组件编排,帮助解耦业务代码,让每一个业务片段都是一个组件。解决任意的多线程并行、串行、阻塞、依赖、回调的并发框架,可以任意组合各线程的执行顺序,带全链路回调和超时控制。通用流程编排技术组件,适用于服务编排、工作流、审批流等场景地址LiteFlowasyncTool: 解决任意的多线程并行、串行、阻塞、依赖、回调的并行框架,可以任意组合各线程的执行顺序,带全链路执行结果回调。多线程编排一站式解决方案。来自于京东主App后台。流程编排、如此简单-通用流程编排组件JDEasyFlow介绍-京东云开发者社区优点复杂业务流程编排、社区成熟活跃基于jdk8 CompletableFuture、轻量级简单、灵活、易扩展基于生产-消费模型、无锁设计缺点开源框架较重,有一定学习成本新框架稳定性待验证较为底层,针对业务场景需要二次封装示例liteflow-example: liteflow的一个业务示例工程https://gitee.com/bryan31/liteFlow

三、liteflow基础使用

1.添加依赖jar包

com.yomahub

liteflow-spring

2.10.4

2.定义组件

定义组件和实现某些组件,注册进上下文

@Component("a")

public class ACmp extends NodeComponent {

@Override

public void process() {

//do your business

}

}

3.配置

添加对应的配置类及配置文件

Spring xml中的配置

4.规则文件的定义

--流程的定义(第3步中liteflowConfig指定了规则文件为config/flow.xml),所以需要在resources下新建文件夹config,在新建flow.xml文件,配置要定义的流程

THEN(a, b, c)

5.执行

编排好的流程,在需要执行的地方注入FlowExecutor,执行execute2Resp

@Component

public class YourClass{

@Resource

private FlowExecutor flowExecutor;

@Test

public void testConfig(){

LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");

}

}

四、liteflow在实际中的应用

这里弱化背后的实际业务只展示作者在实际中的应用案例

1.添加依赖jar包

2.8.0

com.yomahub

liteflow-spring

${liteflow-spring.version}

2.定义组件

定义组件和实现某些组件,注册进上下文

@LiteflowComponent("checkRealNameAuthCmp")

@LiteflowCmpDefine

public class CheckRealNameAuthCmp {

private static final Logger log = LoggerFactory.getLogger(CheckRealNameAuthCmp.class);

@LiteflowMethod(LiteFlowMethodEnum.PROCESS)

public void process(NodeComponent nodeComponent) throws Exception {

// 获取请求参数

GeneratePolicyRightsParam generatePolicyRightsParam = nodeComponent.getSlot().getRequestData();

// 如果pin为空则结束流程

if (generatePolicyRightsParam == null || StringUtil.isEmpty(generatePolicyRightsParam.getUserPin())) {

log.info("CheckRealNameAuthCmp -> process end, nodeComponent={},pin is null.", JsonUtil.toJSONString(nodeComponent));

nodeComponent.setIsEnd(true);

}

//封装设置流程编排上下文信息

GenerateRightsContext generateRightsContext = nodeComponent.getContextBean(GenerateRightsContext.class);

generateRightsContext.setGeneratePolicyRightsParam(generatePolicyRightsParam);

}

}

LiteflowComponent:普通组件 | LiteFlow

LiteflowCmpDefine:声明式组件 | LiteFlow

3.配置

添加对应的配置类及配置文件

Spring xml中的配置

spring-config.xml

spring-config-liteflow.xml

4.规则文件的定义

--流程的定义(第3步中liteflowConfig指定了规则文件为liteflow/flow.xml),所以需要在resources下新建文件夹liteflow,在新建flow.xml文件,配置要定义的流程

flow.xml

5.执行

执行编排好的流程,在需要执行的地方注入FlowExecutor,执行execute2Resp

@Resource

private FlowExecutor flowExecutor;

public Boolean sendPolicyRights(GeneratePolicyRightsParam generatePolicyRightsParam) {

//todo 入参和上下文不能混用,通用信息用map

LiteflowResponse response = flowExecutor.execute2Resp("sendPolicyRightsChain", generatePolicyRightsParam, GenerateRightsContext.class,GenerateRightsContext.class);

}

五、liteflow能力扩展(可视化)

liteflowt提供了流程编排的能力,只有研发人员能够了解这内在的流程编排含义,对于其他产品或者业务并不能直观的了解当前的业务流程,可视化并不友好。这时我们如何让当前的流程可视化呢?编写一个页面直接读取配置文件flow.xml进行显示,这是没有意义的。有意义的是我们能够对组件进行可视化、对流程可视化、对流程编排可视化。

1、思想

提供新的jar包,获取到业务系统声名的组件、流程、显示流程和组件、提供编排能力。

说明:

1、小工具jar包为可视化流程编排小工具,主要提供获取业务系统声明的组件、保存的流程、进行流程可视化展示、进行流程编排可视化等,使用liteflow-util标识区别于业务系统。

2、业务系统为组件声明、流程执行、业务逻辑系统,使用liteflow-test标识

2、实现

2.1获取特定的类或方法

如何从liteflow-util中获取liteflow-test中声明的组件

2.1.1获取上下文环境

ApplicationContextAware

当一个bean的属性初始化后会回调到setApplicationContext,从而设置应用上下文。

public interface ApplicationContextAware extends Aware {

/**

* Set the ApplicationContext that this object runs in.

* Normally this call will be used to initialize the object.

*

Invoked after population of normal bean properties but before an init callback such

* as {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()}

* or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader},

* {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and

* {@link MessageSourceAware}, if applicable.

* @param applicationContext the ApplicationContext object to be used by this object

* @throws ApplicationContextException in case of context initialization errors

* @throws BeansException if thrown by application context methods

* @see org.springframework.beans.factory.BeanInitializationException

*/

void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

在liteflow-util中使用一个类来实现ApplicationContextAware,从而获取到liteflow-test(依赖当前jar包的应用)的上下文环境

@Configuration

public class LiteFlowApplicationContext implements ApplicationContextAware {

private static ApplicationContext controllerApplicationContext;

@Override

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

System.out.println("applicationContext = " + applicationContext);

LiteFlowApplicationContext.controllerApplicationContext=applicationContext;

}

public static ApplicationContext getControllerApplicationContext() {

return controllerApplicationContext;

}

}

2.1.2从上下文获取类

在liteflow-util中根据上下文环境获取组件类这里的重点是Map mvcObjects = context.getBeansWithAnnotation(Service.class);

@Slf4j

public class ReferenceManager {

private static Map, Object> interfaceMapRef = new ConcurrentHashMap, Object>();

private static ReferenceManager instance;

private ReferenceManager() {

}

public synchronized static ReferenceManager getInstance() {

if (null != instance) {

return instance;

}

instance = new ReferenceManager();

ApplicationContext controllerContext = LiteFlowApplicationContext.getControllerApplicationContext();

interfaceMapInit(controllerContext);

return instance;

}

private static void interfaceMapInit(ApplicationContext context) {

try {

Map objects = Maps.newHashMapWithExpectedSize(64);

//优化 允许 ServiceBean 被MVC容器扫描

Map mvcObjects = context.getBeansWithAnnotation(Service.class);

objects.putAll(mvcObjects);

if (objects == null || objects.size() == 0) {

return;

}

for (Entry entry : objects.entrySet()) {

/**

* 获取代理对象的原对象

* 因为 jdk 动态代理通过接口

*/

Object objectImplProxy = entry.getValue();

Object objectImpl = AopTargetUtils.getTarget(objectImplProxy);

Class objectImplClass = objectImpl.getClass();

if (objectImplClass.getInterfaces().length > 0) {

/**

* 规定 每个interface 只对应 一个实现类

* 如果 多个类实现了该接口 接口列表中只 显示第一个实现类

*/

Class interfaceClass = objectImplClass.getInterfaces()[0];

Object object = interfaceMapRef.get(interfaceClass);

if (object == null) {

interfaceMapRef.put(interfaceClass, objectImpl);

} else {

}

} else {

}

}

} catch (Exception e) {

}

}

public Map, Object> getInterfaceMapRef() {

return interfaceMapRef;

}

}

@Component

public class ServiceScanner {

public Set> classes() {

return interfaceMapRef().keySet();

}

public Map, Object> interfaceMapRef() {

return ReferenceManager.getInstance().getInterfaceMapRef();

}

}

public class AopTargetUtils {

/**

* 获取 目标对象

* @param proxy 代理对象

* @return

* @throws Exception

*/

public static Object getTarget(Object proxy) throws Exception {

if(!AopUtils.isAopProxy(proxy)) {

return proxy;//不是代理对象

}

if(AopUtils.isJdkDynamicProxy(proxy)) {

return getJdkDynamicProxyTargetObject(proxy);

} else { //cglib

return getCglibProxyTargetObject(proxy);

}

}

private static Object getCglibProxyTargetObject(Object proxy) {

try{

Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");

h.setAccessible(true);

Object dynamicAdvisedInterceptor = h.get(proxy);

Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");

advised.setAccessible(true);

Object target = ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();

return target;

} catch(Exception e){

e.printStackTrace();

return null;

}

}

private static Object getJdkDynamicProxyTargetObject(Object proxy) {

try{

Field h = proxy.getClass().getSuperclass().getDeclaredField("h");

h.setAccessible(true);

AopProxy aopProxy = (AopProxy) h.get(proxy);

Field advised = aopProxy.getClass().getDeclaredField("advised");

advised.setAccessible(true);

Object target = ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();

return target;

} catch(Exception e){

e.printStackTrace();

return null;

}

}

}

2.2访问liteflow-util页面

如何在liteflow-test里访问到liteflow-util包里的页面并展示

(1)在liteflow-util内编写一个Servlet类,直接继承HttpServlet ,重写doGet或者doPost方法

(2)在liteflow-util内将Servlet类配置到web.xml中

(3)在liteflow-util内准备前端的页面(form表单、按钮交互)

(4)在liteflow-test内引入依赖并启动liteflow-test

2.2.1HttpServlet

public class HandServlet extends HttpServlet {

private static final Logger log = LoggerFactory.getLogger(HandServlet.class);

private String username = null;

private String password = null;

private ServletContext servletContext;

public HandServlet() {

}

@Override

public void init(ServletConfig config) {

log.info("HandServlet->init,start");

this.username = config.getInitParameter("loginUsername");

this.password = config.getInitParameter("loginPassword");

this.servletContext = config.getServletContext();

log.info("HandServlet->init finish");

}

@Override

protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {

String contextPath = request.getContextPath();

String servletPath = request.getServletPath();

String requestURI = request.getRequestURI();

response.setCharacterEncoding("utf-8");

if (contextPath == null) {

contextPath = "";

}

String uri = contextPath + servletPath;

String path = requestURI.substring(contextPath.length() + servletPath.length());

String usernameParam;

if (!Objects.equals("/submitLogin", path)) {

if (this.needLogin(request, path)) {

this.redirect(request, response);

} else {

Result result;

try {

result = this.requestHandler(path, request);

} catch (Throwable var11) {

log.error("HandServlet->service,requestHandler error", var11);

result = Result.buildFail(var11.getMessage());

}

if (null != result) {

response.getWriter().print(JSON.toJSONString(result));

} else {

this.returnResourceFile(path, uri, response);

}

}

} else {

usernameParam = request.getParameter("loginUsername");

String passwordParam = request.getParameter("loginPassword");

System.out.println("usernameParam = " + usernameParam);

System.out.println("passwordParam = " + passwordParam);

// if (this.username.equals(usernameParam) && this.password.equals(passwordParam)) {

HttpSession session = request.getSession();

session.setAttribute("lite-flow", this.username);

session.setMaxInactiveInterval(300);

response.getWriter().print(JSON.toJSONString(Result.buildSuccess("success")));

// } else {

// response.getWriter().print(JSON.toJSONString(Result.buildFail("用户名或密码错误")));

// }

}

}

private void redirect(HttpServletRequest request, HttpServletResponse response) throws IOException {

if (request.getHeader("X-Requested-With") != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {

response.getWriter().print(JSON.toJSONString(Result.buildReLogin()));

} else if (request.getHeader("Accept") != null && request.getHeader("Accept").contains("application/json")) {

response.getWriter().print(JSON.toJSONString(Result.buildReLogin()));

} else {

response.sendRedirect("/lite-flow/login.html");

}

}

private Result requestHandler(String path, HttpServletRequest request) {

System.out.println("path = " + path);

System.out.println("request = " + request);

String initMenu = "/initMenu";

String liteflow = "/liteflow";

if (initMenu.equals(path)) {

Map map = new HashMap(2);

List classObjectMap = getClassObjectMap();

classObjectMap.forEach(item -> {

int i = item.lastIndexOf(".");

String substring = item.substring(i+1);

System.out.println("substring = " + substring);

LiteFlowNodeBuilder.createCommonNode().setId(substring).setName(substring).setClazz(item).build();

});

map.put("interfaceMapRef", classObjectMap);

return Result.buildSuccess(map);

} else if (liteflow.equals(path)) {

try {

try {

String postData = this.getPostData(request);

log.info("HandServlet -> requestHandler start, postData={}", postData);

JSONObject jsonObject = JSONObject.parseObject(postData);

JSONArray checkList = jsonObject.getJSONArray("checkList");

String chainId = (String) jsonObject.get("chainId");

log.info("HandServlet -> requestHandler start, path={},checkList={}", path, checkList);

ArrayList arrayList = new ArrayList();

checkList.forEach(item -> {

String itemStr = (String) item;

int i = itemStr.lastIndexOf(".");

String substring = itemStr.substring(i+1);

arrayList.add(substring);

});

String str = StringUtils.join(arrayList, ",");

log.info("HandServlet -> requestHandler start, str={}", str);

// String elss = "THEN(" + str + ")";

// log.info("HandServlet -> requestHandler start, elss={}", elss);

Condition condition = LiteFlowConditionBuilder.createCondition(ConditionTypeEnum.TYPE_THEN).setValue(str).build();

log.info("HandServlet -> requestHandler start, condition={}", condition);

LiteFlowChainBuilder.createChain().setChainName(chainId).setCondition(condition).build();

} catch (Throwable var3) {

log.error("HandServlet -> requestHandler exception 未知异常, var3={}", var3);

}

} catch (Throwable var3) {

log.info("MqUtil->haveProducer,error", var3);

}

return Result.buildSuccess(false);

} else {

return null;

}

}

public String getPostData(HttpServletRequest request) {

StringBuilder data = new StringBuilder();

String line;

BufferedReader reader;

try {

reader = request.getReader();

while (null != (line = reader.readLine())) {

data.append(line);

}

} catch (IOException e) {

return null;

}

return data.toString();

}

private List getClassObjectMap() {

List result = new ArrayList<>();

WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);

Map serviceScannerMap = webApplicationContext.getBeansOfType(ServiceScanner.class);

ServiceScanner serviceScanner = serviceScannerMap.get("serviceScanner");

Map, Object> interfaceMapRef = serviceScanner.interfaceMapRef();

if (null != interfaceMapRef) {

//排序 所有接口

List, Object>> arrayList = new ArrayList, Object>>(interfaceMapRef.entrySet());

Collections.sort(arrayList, new Comparator, Object>>() {

@Override

public int compare(Map.Entry, Object> o1, Map.Entry, Object> o2) {

return o1.getKey().getSimpleName().compareTo(o2.getKey().getSimpleName());

}

});

//遍历 所有接口

for (Map.Entry, Object> entry : arrayList) {

String className = entry.getValue().getClass().getName();

System.out.println("class = " + className);

result.add(className);

// List interfaceMethodList = Arrays.asList(entry.getKey().getDeclaredMethods());

// //方法列表排序

// Collections.sort(interfaceMethodList, new Comparator() {

// @Override

// public int compare(Method o1, Method o2) {

// return o1.getName().compareTo(o2.getName());

// }

// });

// for (Method method : interfaceMethodList) {

// System.out.println("method = " + method);

// System.out.println("methodName = " + method.getName());

// System.out.println("methodParameterTypes = " + method.getParameterTypes());

// System.out.println("methodReturn = " + method.getReturnType());

// }

}

}

System.out.println("result = " + result);

return result;

}

private boolean needLogin(HttpServletRequest request, String path) {

return this.isRequireAuth() && !this.alreadyLogin(request) && !this.checkLoginParam(request) && !"/login.html".equals(path) && !path.startsWith("/css") && !path.startsWith("/js") && !path.startsWith("/img");

}

private boolean checkLoginParam(HttpServletRequest request) {

String usernameParam = request.getParameter("loginUsername");

String passwordParam = request.getParameter("loginPassword");

if (null != this.username && null != this.password) {

return this.username.equals(usernameParam) && this.password.equals(passwordParam);

} else {

return false;

}

}

private boolean isRequireAuth() {

return this.username != null;

}

private boolean alreadyLogin(HttpServletRequest request) {

HttpSession session = request.getSession(false);

return session != null && session.getAttribute("lite-flow") != null;

}

private void returnResourceFile(String fileName, String uri, HttpServletResponse response) throws IOException {

String filePath = this.getFilePath(fileName);

if (filePath.endsWith(".html")) {

response.setContentType("text/html; charset=utf-8");

}

if (fileName.endsWith(".jpg")) {

byte[] bytes = Utils.readByteArrayFromResource(filePath);

if (bytes != null) {

response.getOutputStream().write(bytes);

}

} else {

String text = Utils.readFromResource(filePath);

if (text == null) {

response.sendRedirect(uri + "/login.html");

} else {

if (fileName.endsWith(".css")) {

response.setContentType("text/css;charset=utf-8");

} else if (fileName.endsWith(".js")) {

response.setContentType("text/javascript;charset=utf-8");

}

response.getWriter().write(text);

}

}

}

private String getFilePath(String fileName) {

return "view" + fileName;

}

2.2.2配置web.xml

在liteflow-util内web.xml配置自定义的servlet

handOfLite

com.xx.utils.liteflow.handler.HandServlet

loginUsername

Username

loginPassword

Password

5

handOfLite

/hand-of-lite/*

2.2.3页面准备

精彩链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: