(1). Compileflow是什么?
Compileflow Process引擎是淘宝工作流TBBPM引擎之一,是专注于纯内存执行,无状态的流程引擎,通过将流程文件转换生成java代码编译执行,简洁高效.
Compileflow会识别XML,并生成一个java类,再编译成class,加载内存后反射生存对象缓存起来(有点Jsp转换成Servlet的感觉).等引擎需要执行指定XML的流程时,执行了被创建好的对象类.
(2). 案例
案例代码来源:https://github.com/alibaba/compileflow.git
demo描述:N个人去ktv唱歌,每人唱首歌,ktv消费原价为30元/人,如果总价超过300打九折,小于300按原价付款.
(3). ktvExample.bpmn20
注意:这个流程设计文档(ktvExample.bpmn20)必须要在(classpath:bpmn20/ktv/目录下).
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns:cf="http://compileflow.alibaba.com"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://compileflow.alibaba.com">
<process id="ktv" name="ktv" isExecutable="true">
<extensionElements>
<cf:var name="price" description="支付价格" dataType="java.lang.Integer" inOutType="return"/>
<cf:var name="totalPrice" description="实付价" dataType="java.lang.Integer" inOutType="inner"/>
<cf:var name="pList" description="人员" dataType="java.util.List<java.lang.String>"
inOutType="param"/>
</extensionElements>
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="singLoop"/>
<subProcess id="singLoop" name="sing loop">
<standardLoopCharacteristics cf:collection="pList" cf:elementVar="p"
cf:indexVar="i" cf:elementVarClass="java.lang.String">
</standardLoopCharacteristics>
<startEvent id="singStart"/>
<sequenceFlow id="subFlow1" sourceRef="singStart" targetRef="sing"/>
<serviceTask id="sing" name="sing task" cf:type="spring-bean"
cf:bean="ktvService"
cf:class="com.allibaba.compileflow.test.mock.KtvService"
cf:method="sing">
<extensionElements>
<cf:var name="p1" description="" dataType="java.lang.String" contextVarName="p" defaultValue=""
inOutType="param"/>
</extensionElements>
</serviceTask>
<sequenceFlow id="subFlow2" sourceRef="sing" targetRef="singEnd"/>
<endEvent id="singEnd"/>
</subProcess>
<sequenceFlow id="flow2" sourceRef="singLoop" targetRef="calPrice"/>
<serviceTask id="calPrice" name="calPrice" cf:type="java"
cf:class="com.allibaba.compileflow.test.mock.MockJavaClazz"
cf:method="calPrice">
<extensionElements>
<cf:var name="p1" description="人数" dataType="java.lang.Integer" contextVarName="pList.size()"
defaultValue=""
inOutType="param"/>
<cf:var name="p2" description="价格" dataType="java.lang.Integer" contextVarName="totalPrice"
defaultValue=""
inOutType="return"/>
</extensionElements>
</serviceTask>
<sequenceFlow id="flow3" sourceRef="calPrice" targetRef="payDecision"/>
<exclusiveGateway id="payDecision"/>
<sequenceFlow id="flow4" sourceRef="payDecision" targetRef="originalPrice">
<conditionExpression cf:type="java">
<![CDATA[totalPrice>=300]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" sourceRef="payDecision" targetRef="promotionPrice"/>
<scriptTask id="originalPrice" name="original price" scriptFormat="ql">
<extensionElements>
<cf:var name="price" description="价格" dataType="java.lang.Integer" contextVarName="totalPrice"
defaultValue="" inOutType="param"/>
<cf:var name="price" description="价格" dataType="java.lang.Integer" contextVarName="price"
defaultValue=""
inOutType="return"/>
</extensionElements>
<script><![CDATA[(round(price*0.9,0)).intValue()]]></script>
</scriptTask>
<scriptTask id="promotionPrice" name="promotion task" scriptFormat="ql">
<extensionElements>
<cf:var name="price" description="价格" dataType="java.lang.Integer" contextVarName="totalPrice"
defaultValue="" inOutType="param"/>
<cf:var name="price" description="价格" dataType="java.lang.Integer" contextVarName="price"
defaultValue=""
inOutType="return"/>
</extensionElements>
<script><![CDATA[(round(price*0.9,0)).intValue()]]></script>
</scriptTask>
<sequenceFlow id="flow6" sourceRef="originalPrice" targetRef="pay"/>
<sequenceFlow id="flow7" sourceRef="promotionPrice" targetRef="pay"/>
<serviceTask id="pay" name="pay" cf:type="java"
cf:class="com.allibaba.compileflow.test.mock.KtvService"
cf:method="payMoney">
<extensionElements>
<cf:var name="p1" description="价格" dataType="java.lang.Integer"
contextVarName="price" defaultValue="" inOutType="param"/>
</extensionElements>
</serviceTask>
<sequenceFlow id="flow8" sourceRef="pay" targetRef="end"/>
<endEvent id="end"/>
</process>
</definitions>
(4). ktv.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" default-autowire="byName"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="ktvService" class="com.allibaba.compileflow.test.mock.KtvService"/>
</beans>
(5). common.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" default-autowire="byName"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="applicationContextProvider"
class="com.alibaba.compileflow.engine.process.preruntime.generator.bean.SpringApplicationContextProvider"/>
</beans>
(6). orderFulfillment.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.allibaba.compileflow.test.om"/>
</beans>
(7). KtvService
package com.allibaba.compileflow.test.mock;
import java.io.PrintStream;
/**
* @author pin
*/
public class KtvService {
public void sing(String name) {
System.out.println(name + " is singing");
}
public void payMoney(int price) {
System.out.println("actually paid money: " + price);
}
}
(8). MockJavaClazz
package com.allibaba.compileflow.test.mock;
public class MockJavaClazz {
public int calPrice(int num) {
System.out.println("total price: " + 30 * num);
return 30 * num;
}
}
(9). ProcessEngineTest
package com.allibaba.compileflow.test;
import com.alibaba.compileflow.engine.ProcessEngine;
import com.alibaba.compileflow.engine.ProcessEngineFactory;
import com.alibaba.compileflow.engine.common.constants.FlowModelType;
import com.alibaba.compileflow.engine.definition.tbbpm.TbbpmModel;
import com.alibaba.compileflow.engine.process.preruntime.converter.impl.TbbpmModelConverter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author yusu
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath:bean/common.xml", "classpath:bean/ktv.xml", "classpath:bean/orderFulfillment.xml"
})
public class ProcessEngineTest {
@Test
public void testProcessEngineBpmn20() {
final String code = "bpmn20.ktv.ktvExample";
final Map<String, Object> context = new HashMap<>();
List<String> pList = new ArrayList<>();
pList.add("wuxiang");
pList.add("yusu");
context.put("pList", pList);
final ProcessEngine processEngine = ProcessEngineFactory.getStatelessProcessEngine(FlowModelType.BPMN);
System.out.println(processEngine.start(code, context));
}
}
(10). 运行结果
package compileflow;
import java.util.Map;
import java.lang.Integer;
import com.ql.util.express.DefaultContext;
import java.util.List;
import com.alibaba.compileflow.engine.process.preruntime.generator.script.ScriptExecutorProvider;
import com.allibaba.compileflow.test.mock.KtvService;
import java.lang.String;
import java.util.HashMap;
import com.alibaba.compileflow.engine.runtime.instance.ProcessInstance;
import com.alibaba.compileflow.engine.common.utils.DataType;
import com.allibaba.compileflow.test.mock.MockJavaClazz;
import com.ql.util.express.IExpressContext;
import com.alibaba.compileflow.engine.common.utils.ObjectFactory;
import com.alibaba.compileflow.engine.ProcessEngineFactory;
import com.alibaba.compileflow.engine.process.preruntime.generator.bean.BeanProvider;
public class KtvFlow implements ProcessInstance {
private java.util.List<java.lang.String> pList = null;
private java.lang.Integer price = null;
private java.lang.Integer totalPrice = null;
public Map<String, Object> execute(Map<String, Object> _pContext) throws Exception {
pList = (List)DataType.transfer(_pContext.get("pList"), List.class);
Map<String, Object> _pResult = new HashMap<>();
int i = -1;
for (String p : pList) {
i++;
//SubProcess
{
//ServiceTask
((KtvService)BeanProvider.getBean("ktvService")).sing((String)DataType.transfer(p, String.class));
}
}
//ServiceTask
totalPrice = ((MockJavaClazz)ObjectFactory.getInstance("com.allibaba.compileflow.test.mock.MockJavaClazz")).calPrice((Integer)DataType.transfer(pList.size(), Integer.class));
exclusiveGatewayPayDecision();
//ServiceTask
((KtvService)ObjectFactory.getInstance("com.allibaba.compileflow.test.mock.KtvService")).payMoney(price);
_pResult.put("price", price);
return _pResult;
}
private void exclusiveGatewayPayDecision() {
//ExclusiveGateway
if (totalPrice>=300) {
IExpressContext<String, Object> nfScriptContext = new DefaultContext<>();
nfScriptContext.put("price", totalPrice);
price = (java.lang.Integer)ScriptExecutorProvider.getInstance().getScriptExecutor("QL").execute("(round(price*0.9,0)).intValue()", nfScriptContext);
} else {
IExpressContext<String, Object> nfScriptContext = new DefaultContext<>();
nfScriptContext.put("price", totalPrice);
price = (java.lang.Integer)ScriptExecutorProvider.getInstance().getScriptExecutor("QL").execute("(round(price*0.9,0)).intValue()", nfScriptContext);
}
}
}
(11). 总结
compileflow无非不过就是把流程图(XML)转换成了Java代码,从项目的命名(compileFlow)就能看出来.
为什么要编译成Java代码,优势和缺点是什么?
优势:相比那种责任链的模式的实现,这样做,性能更优.
缺点:CompileFlow底层肯定是自定义了ClassLoader,如果,流程图的更改(哪怕是个小小的变量),就会触发ClassLoader重新加载.
下一小节,剖析生成Java代码和ClassLoader加载,用来验证我的观点.