(《partner4java 讲述jBPM4》仅为技术储备 -- 本人并没有jBPM4实战方面丰富的经验;学习本内容最好有Hibernate的基础)
代码下载地址:文章中贴出的代码可能有所改动,以下载地址为准http://download.csdn.net/detail/partner4java/4998228
工作流:
工作流就是一系列相互衔接、自动进行的业务活动或任务。我们可以将整个业务过程看作是一条河,其中流过的河水就是待审核的表单。
工作流要解决的主要问题是:
为实现某个业务目标,在多个参与者之间,利用计算机,按某种预定规则自动传递文档、信息或者任务。
为什么需要工作流:
1、规范公司流程:特别是国家单位,权利重叠、办事效率的低下,导致一些工作处理起来成本很高。
2、节省业务成本:比如我们要开个票据,传统需要拿着各种单子找各级领导签字,领导不在怎么办?
3、发挥计算机的优势:过程可监控 -- 便于对执行过程的跟踪和监控;数据可管理 -- 便于对数据进行检索、分析。
为什么选择jbpm4:
jbpm创始人Tom Baeyens,jBPM在2004年10月18日,发布了2.0版本,并在同一天加入了JBoss。当发布4.0版本时进行了很大的改进,大部分代码进行了重写。
但是,10年左右Tom Baeyens离开了JBoss(由于对后续版本5的分歧),后推出的5.0版本和前面版本完全不同(架构和代码都是重写的)。所以,我们这里学习的是Tom Baeyens在JBoss贡献的最终版4.4。
jbpm的核心为两部分:
1、描述工作流程;
流程定义可以看做静态的业务过程模块。流程定义由活动(Activity)和转移(Transition)组成。
活动的行为被封装在该类型互动定义的实现中,各种类型的活动定义组成了整个流程结构。
(如果你用过visio或者UML,你会想到最简单的方式就是能够画出来,我们这里选择JBPM自己的流程语言jPDL。)
2、处理工作流程。
我们定义的流程,需要被实例化(或加载),因此我们要创建流程实例;
当流程实例在执行中时,我们要控制和监控流程,以确保业务流程执行在监控之中;
当流程实例执行完毕,jBPM4会将其归档到“历史流程”中去,从而提高运行中流程实例的执行效率,而我们需要从历史流程中进行数据分析以优化和重组业务。
(白话总结就是:我们通过XML这种形式的文件定义流程;然后通过java模块解析处理流程)
整体模块:
PVM:
jBPM4在流程虚拟机(PVM)技术的基础上,能够支持多种流程定义语言,目前已经支持的流程定义语言有:
·jPDL
·BPEL
·Seam PageFlow
jBPM4 PVM(Process Virtual Machine,流程虚拟机)的设计初衷是通过实现接口和定制插件等方式兼容多种流程定义语言和流程活动场景,为“世界上”所有的业务流程定义提供一套通用API平台。
那么,无论是需要对jBPM原有流程定义语言进行扩展,或者重新实现一套专用的流程定义语言,都可以通过实现PVM指定的接口规范完成了。
JPDL:
可以这么说,jPDL(jBPM Process Define Language,jBPM流程定义语言)是jBPM4独有的、最重要的“资产”。
jPDL的设计目标是尽量的精简和尽可能地对开发者友好,即提供所有您期望从业务流程定义语言中得到的同时,也可以很“简练”地描述业务流程的定义和图形结构,最终使得业务分析师和流程开发者能使用“同一种语言说话”、极大地减少了他们之间的交流障碍。
工作流(系统)主要概念:
·流程定义:预先定义的业务流转逻辑
·流程实例:业务的一次实际流转过程
·参与者:任务的执行者
·活动(任务):组成流程定义的节点
·活动实例:组成流程实例的元素
·流转:从一个节点到另一个节点这一行为
·工作列表:当前需要办理的任务集合
·工作流引擎:工作流的核心组件,对流程实例、任务实例以及其状态进行管理
接下来我们学习的基本思路为:
1、首先展示一个基本的工作流程开发。
2、流程定义语言、Service API (这两部分的内容会交叉学习。学习这部分内容必须实际动手。)
第一部分:展示一个基本的工作流程开发
(学习内容:对jBPM4的开发流程有大致了解,能够认识到需要学习的内容模块,对jBPM有一个大体的认识。具体细节技术会在下面章节中学习。当然,如果你动手能力比较强,也可以先打随着一遍,对jBPM4有更清晰的认识。)
场景:我们开发一套请假流程,主要分为登录、发起请假、处理请假等。
helloworld开发流程:
第一步:部署环境
1、我们需要图形化的形式展示jBPM4的强大,所以首先下载tomcat http://tomcat.apache.org/download-70.cgi
2、然后需要下载jBPM4的相关jar:
(后面的内容都是基于jbpm-4.4,下载地址http://sourceforge.net/projects/jbpm/files/jBPM%204/)
3、安装eclipse插件gpd,插件目录jbpm-4.4\install\src\gpd(用于完成“描述工作流”)
4、创建web工程“hello_jbpm”
5、拷入jbpm-4.4\examples\src的配置文件到类路径
修改jbpm.hibernate.cfg.xml文件:
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hello_jbpm</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">123456</property> <!-- Drop and re-create the database schema on startup --> <property name="hibernate.hbm2ddl.auto">update</property> <property name="hibernate.format_sql">true</property>
6、数据库创建
jbpm-4.4\install\jdbc有各数据库脚本
7、加入jbpm的包,如果不知道如何哪些包,可以加入jbpm-4.4\lib内所有包(还有外部jbpm.jar),后面再进行增删
8、把apache-tomcat-7.0.34\webapps\examples\WEB-INF\lib下的两个人jar拷入apache-tomcat-7.0.34\lib
第二步:描述执行流程
(我们这里使用jpdl,jpdl为jBPM的重要资产,用于描述流程定义)
描述的方式非常简单,有拖拽工具。而且这已经是jBPM4中比较繁琐的部分了。
再次强调我们前面说过的,jBPM4就是由描述工作流和处理工作流组成。
更通俗的说,我们工作流无非就是流程执行,我们这里就是定义流程规则。
新建:leave.jpdl.xml
<?xml version="1.0" encoding="UTF-8"?> <process name="leave" xmlns="http://jbpm.org/4.4/jpdl"> <!-- 首先要发起一个流程。name为本流程名称,transition为流程转移 --> <start g="487,14,80,40" name="开始"> <transition to="请假"/> </start> <!-- 第一步用户填写请假单,并提交。assignee为任务执行者;form为本部执行地址 --> <task assignee="${userid}" form="/WEB-INF/leave/submit.jsp" g="473,90,80,40" name="请假"> <transition g="-19,-3" name="提交请假" to="经理审批"/> </task> <!-- 第二步经理审批,分为打回和转至下一步 --> <task candidate-groups="manager" form="leave/view" g="480,199,80,40" name="经理审批"> <transition g="621,161:-22,-16" name="经理打回" to="请假"/> <transition g="-26,-18" name="规则判断" to="规则判断"/> </task> <!-- 第三步进行规则判断,如果大于等于3天,需要主管审批;否则只需要经理审批即可完成。expr为EL,判断后的数据为transition的名称 --> <decision expr="#{day >= 3 ? '主管审批':'请假完成'}" g="498,284,80,40" name="规则判断"> <transition g="-18,-18" name="主管审批" to="主管审批"/> <transition g="-18,-22" name="请假完成" to="请假完成"/> </decision> <!-- 最后一步:主管审批 --> <task candidate-groups="department" form="leave/view" g="320,290,80,40" name="主管审批"> <transition g="-53,-22" name="请假完成" to="请假完成"/> <transition g="-27,-8" name="主管打回" to="请假"/> </task> <end g="495,411,80,40" name="请假完成"/> </process>这就是JPDL,一种流程描述,基于XML。非常简便,且可以借助GPD插件进行拖拽生成,后面你会发现这么简单的拖拽就神奇的完成了工作流的大部分工作。
保存后会生成一个配套的图片:
第三步:发布描述流程
也就是把我们第二步定义的流程图(jpdl),发布进jBPM4的容器。或者说把一个XML定义交给jBPM4,解析成对应的entity对象。
发布非常简单,我们这里会第一次接触到ProcessEngine,这相当于我们的顶级工厂类。它负责创建我们用于管理和执行流程的服务类。
package com.partner4java.hello.service; import java.util.List; import org.jbpm.api.Configuration; import org.jbpm.api.NewDeployment; import org.jbpm.api.ProcessDefinition; import org.jbpm.api.ProcessDefinitionQuery; import org.jbpm.api.ProcessEngine; import org.jbpm.api.RepositoryService; /** * jpdl发布管理类 * * @author partner4java * */ public class JpdlDeployManager { /** 流程引擎对象 -- 是jBPM4所有Service API之源 -- ProcessEngine是线程安全的,所以我们可以全局共享一个 */ public static ProcessEngine processEngine = Configuration.getProcessEngine(); /** 流程资源服务的接口。提供对流程定义的部署、查询、删除等操作 */ private RepositoryService repositoryService; /** * repositoryService获取NewDeployment,通过NewDeployment进行addResourceFromXXX操作( * 可以以多种方式获取资源),然后发布 */ private NewDeployment newDeployment; /** 发布流程查询类 */ private ProcessDefinitionQuery processDefinitionQuery; public JpdlDeployManager() { repositoryService = processEngine.getRepositoryService(); newDeployment = repositoryService.createDeployment(); processDefinitionQuery = repositoryService.createProcessDefinitionQuery(); } /** * 发布jpdl文件 * * @param resourceName * 资源路径,相对于类路径 * @return 发布id */ public String deployJpdl(String resourceName) { return newDeployment.addResourceFromClasspath(resourceName).deploy(); } /** * 查询所有发布定义 * * @return */ public List<ProcessDefinition> getAll() { return processDefinitionQuery.list(); } /** * 删除一个已发布的流程定义id * * @param deploymentId */ public void delete(String deploymentId) { // repositoryService.deleteDeployment(deploymentId); // 级联删除 repositoryService.deleteDeploymentCascade(deploymentId); } }进行测试、并进行发布:
public void testDeployJpdl() { jpdlDeployManager.deployJpdl("leave.jpdl.xml"); }
第四步:发起流程实例(其实还包括了提交表单)
当流程已经发布完成之后,顺理成章的是,我们就应该发起某个具体已发布的流程。
首先发起流程的Service:
/** * 请假流程处理业务类 * * @author partner4java * */ public class LeaveExecutionService { /** 流程执行服务的接口。提供启动流程实例、“执行”推进、设置流程变量等操作 */ private ExecutionService executionService; /** 人工任务服务的接口。提供对任务的创建、提交、查询、保存、删除等操作 */ private TaskService taskService; public LeaveExecutionService() { executionService = JpdlDeployManager.processEngine.getExecutionService(); taskService = JpdlDeployManager.processEngine.getTaskService(); } /** * 开启一个新流程 * * @param id */ public ProcessInstance doStart(String id) { Map<String, String> values = new HashedMap(); // 这里的userid是我们jpdl文件中定义的 values.put("userid", WebLocal.getLocalUserName()); return executionService.startProcessInstanceById(id, values); } public Task start(String id) { ProcessInstance processInstance = doStart(id); return taskService.createTaskQuery().processInstanceId(processInstance.getId()).uniqueResult(); }
1、设置一个登陆界面,然后写一个filter进行拦击判断(这和jBPM4无关,只是为了界面展示);
2、登录后,展示可以办理业务列表,也代办业务列表;
3、点击请假,进行开启一个新流程调用,然后跳转到填写表单界面,并提交。
·设置一个登陆界面,然后写一个filter进行拦击判断(这和jBPM4无关,只是为了界面展示):
begin&接下来一部分内容为辅助功能,主要为了界面化测试请假流程
首先我们写了一个登录验证filter:
public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filter) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; String username = (String) request.getSession().getAttribute("username"); if (username == null || "".equals(username.trim())) { toLogin(request, ((HttpServletResponse) resp)); } else { filter.doFilter(request, resp); } } /** * 跳转到登录界面 * * @throws IOException */ public static void toLogin(HttpServletRequest httpRequest, HttpServletResponse response) throws IOException { String path = httpRequest.getContextPath(); String basePath = httpRequest.getScheme() + "://" + httpRequest.getServerName() + ":" + httpRequest.getServerPort() + path + "/login.jsp"; response.sendRedirect(basePath); }
package com.partner4java.hello.web; import javax.servlet.http.HttpServletRequest; /** * web的当前线程存放的数据<br/> * 利用ThreadLocal * * @author partner4java * */ public class WebLocal { private static ThreadLocal<HttpServletRequest> localRequest = new ThreadLocal<HttpServletRequest>(); /** * 设置当前线程的HttpServletRequest * * @param httpServletRequest */ public static void setLocalServletRequest(HttpServletRequest httpServletRequest) { localRequest.set(httpServletRequest); } /** * 获取当前线程的HttpServletRequest * * @return */ public static HttpServletRequest getLocalServletRequest() { return localRequest.get(); } /** * 获取当前登录用户名称 * * @return 返回null并不一定没有登录,也有可能是没有放入当前线程的request */ public static String getLocalUserName() { if (getLocalServletRequest() != null) { return (String) getLocalServletRequest().getSession().getAttribute("username"); } return null; } }
public class LoginServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String logout = req.getParameter("logout"); if (logout != null && "true".endsWith(logout)) { req.getSession().removeAttribute("username"); LoginFilter.toLogin(req, resp); } else { String username = req.getParameter("username"); if (username != null && !"".equals(username)) { req.getSession().setAttribute("username", username); req.getRequestDispatcher("/leave/list").forward(req, resp); } else { LoginFilter.toLogin(req, resp); } } } }
登录:<br/> <form action="login"> 用户名:<input type="text" name="username"/> <input type="submit"/> </form>logout.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <a href="login?logout=true">退出登录</a><br/>现在,我们是首先进入登录地址:
http://localhost:8080/hello_jbpm/login.jsp
·登录后,展示可以办理业务列表,也代办业务列表:
随便输入一个用户,如partner4java提交,转到/leave/list
/leave/list界面为所有级别用户所有。
/leave/list对应的Servlet为:LeaveListServlet,主要工作分为两部分可发起业务列表,待处理业务列表。
首先我们需要继续完善LeaveExecutionService,添加查找用户所有代办任务的方法(可可发起业务列表需要方法上面JpdlDeployManager已经给出):
/** * 查找用户所有代办任务 * * @param userid * 用户id * @return */ public List<Task> getAll(String userid) { return taskService.findPersonalTasks(userid); }
/** * 展示列表 * * @author partner4java * */ public class LeaveListServlet extends HttpServlet { private static final long serialVersionUID = 5659443880126290392L; private static JpdlDeployManager jpdlDeployManager = new JpdlDeployManager(); private static LeaveExecutionService leaveExecutionService = new LeaveExecutionService(); @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 发布的信息列表 List<ProcessDefinition> processDefinitions = jpdlDeployManager.getAll(); // 个人任务列表 List<Task> tasks = leaveExecutionService.getAll(WebLocal.getLocalUserName()); req.setAttribute("processDefinitions", processDefinitions); req.setAttribute("tasks", tasks); req.getRequestDispatcher("/WEB-INF/leave/list.jsp").forward(req, resp); } }
<div align="right"> ${username }欢迎您:<jsp:include page="../../logout.jsp"></jsp:include></div> <br /> 可办理的业务: <br /> <c:forEach items="${processDefinitions }" var="processDefinition"> <a href="leave/start?id=${processDefinition.id }" target="_blank">${processDefinition.name }</a> </c:forEach> <br/> 代办的业务: <br/> <c:forEach items="${tasks }" var="task"> <a href="${task.formResourceName }?id=${task.id }" target="_blank">${task.name }</a> </c:forEach>
·点击请假(leave),进行开启一个新流程调用,然后跳转到填写表单界面,并提交:
展示做完之后就需要完成请假处理工作,也就是“leave/start”地址:
/** * 流程发起 -- 请假发起 * * @author partner4java * */ public class LeaveStartServlet extends HttpServlet { private static JpdlDeployManager jpdlDeployManager = new JpdlDeployManager(); private static LeaveExecutionService leaveExecutionService = new LeaveExecutionService(); // 这里其实我们可以做的更分离,使得所有启动都可以使用这个Servlet @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Task task = leaveExecutionService.start(req.getParameter("id")); // 任务id,下一步我们会用到 req.setAttribute("taskId", task.getId()); req.getRequestDispatcher(task.getFormResourceName()).forward(req, resp); } }
<div align="right"> ${username }欢迎您:<jsp:include page="../../logout.jsp"></jsp:include></div> <br /> 可办理的业务: <br /> ${username }请填写: <br/> <form action="leave/submit" method="post"> <input type="hidden" name="taskId" value="${taskId }"/> 申请人:<input type="text" name="username" value="${username }" readonly="readonly"/><br/> 申请时间:<input type="text" name="day"/><br/> 申请原因:<textarea rows="5" cols="15" name="context"></textarea> <br/><input type="submit"> </form>
界面如图:
那么接下来需要处理提交:
首先提交业务处理
/** * 提交请假单 * * @param leaveBean */ public void submit(LeaveBean leaveBean) { Map<String, Object> variables = new HashMap<String, Object>(); variables.put("username", leaveBean.getUsername()); variables.put("day", leaveBean.getDay()); variables.put("context", leaveBean.getContext()); // 完成本次任务 taskService.completeTask(leaveBean.getTaskId(), "提交请假", variables); }
/** * 表单提交处理 * * @author partner4java * */ public class LeaveSubmitServlet extends HttpServlet { private static final long serialVersionUID = 1139256518771618432L; private static JpdlDeployManager jpdlDeployManager = new JpdlDeployManager(); private static LeaveExecutionService leaveExecutionService = new LeaveExecutionService(); @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 如果我们用的框架,这一步就不需要操作了 LeaveBean leaveBean = new LeaveBean(request.getParameter("taskId"), request.getParameter("username"), new Integer(request.getParameter("day")), request.getParameter("context")); leaveExecutionService.submit(leaveBean); response.getOutputStream().print( "<script language='JavaScript' type='text/javascript'>window.close();</script>"); } }
第五步:流程处理
当提交请假单后,按照jpdl,我们的流程就会流转到manager组
为了测试,我们首先创建两个用户“龙哥”、“王总”。龙哥为manager(经理)组员,王总为department(主管)组员。
public void testAdd() { IdentityService identityService = JpdlDeployManager.processEngine.getIdentityService(); // 创建用户 identityService.createUser("龙哥", "changlong", "wang"); // 创建组 identityService.createGroup("manager"); // 添加关系 identityService.createMembership("龙哥", "manager"); // 创建用户 identityService.createUser("王总", "changlong", "wang"); // 创建组 identityService.createGroup("department"); // 添加关系 identityService.createMembership("王总", "department"); }
那么接下来我们完成点击经理审批(或主管审批)展示的界面:
/** * 代办业务详情展示界面 * * @author partner4java * */ public class LeaveViewServlet extends HttpServlet { private static JpdlDeployManager jpdlDeployManager = new JpdlDeployManager(); private static LeaveExecutionService leaveExecutionService = new LeaveExecutionService(); @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { LeaveBean leaveBean = leaveExecutionService.getLeaveBean(req.getParameter("id")); req.setAttribute("leaveBean", leaveBean); Set<String> outcomes = leaveExecutionService.getOutcomes(req.getParameter("id")); req.setAttribute("outcomes", outcomes); String processInstanceId = leaveExecutionService.getProcessInstanceId(req.getParameter("id")); req.setAttribute("processInstanceId", processInstanceId); req.getRequestDispatcher("/WEB-INF/leave/handle.jsp").forward(req, resp); } }
<div align="right"> ${username }欢迎您:<jsp:include page="../../logout.jsp"></jsp:include></div> <br /> 可办理的业务: <br /> ${username }请审批:<br/> 申请人:${leaveBean.username }<br/> 请假时间:${leaveBean.day }<br/> 请假原因:${leaveBean.context }<br/> <form action="leave/handle"> <input type="hidden" name="taskId" value="${leaveBean.taskId }"/> <c:forEach items="${outcomes }" var="outcome"> <input type="submit" name="way" value="${outcome }"/> </c:forEach> </form>界面:
处理展示已经完成,那么接下来就需要处理点击按钮:
处理按钮非常简单,只需要接收任务并处理任务:
/** * 处理流程 * * @param taskId * @param way * 处理路线 */ public void handle(String taskId, String way) { // 接受任务:当用户partner4java接受了任务之后,partner4java就会由任务的候选者变为任务的分配者。 // 同时,此任务会从所有候选者的任务列表中消失。它会出现在partner4java的已分配任务列表中 taskService.takeTask(taskId, WebLocal.getLocalUserName()); taskService.completeTask(taskId, way); }
public class LeaveHandleServlet extends HttpServlet { private static final long serialVersionUID = 7113040922967164826L; private static LeaveExecutionService leaveExecutionService = new LeaveExecutionService(); @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { leaveExecutionService.handle(request.getParameter("taskId"), request.getParameter("way")); response.getOutputStream().print( "<script language='JavaScript' type='text/javascript'>alert('ok');window.close();</script>"); } }
也就是说,我们后面只需要学习这俩Service和jpdl的书写。
helloworld相关类.png