目录
jBPM学习笔记
该文章不是一篇结构完整的文章,是学习过程的随时记录,所以有的地方可能是上下矛盾的,前面说了这样做,后面发现不行又把它否决了,这是一个学习的过程,希望对读者你有帮助
jBPM是什么?
http://www.jboss.org/jbossjbpm/
安装(jboss4.2.2.GA,jbpm-3.2.6.SP1,jbpm-4.0.CR1)
jBPM3提供了一个安装文件,运行java -jar jbpm-installer-3.2.6.SP1.jar,按照指示做便可
但我安装完成后,提示不能jbpmtest不能访问数据库,数据库的访问要自己配置:
- 下载jdbc驱动,放到deploy/lib目录
- deploy/jbpm下修改jbpm-mysql-ds.xml(我安装时选择的数据库是mysql),把里面的数据库连接信息修改正确就行,
jbpm默认使用的是数据源<xa-datasource>,访问时出现:
11:49:12,609 INFO [CachedConnectionManager] Closing a connection for you. Please close them yourself: org.jboss.resource.adapter.jdbc.WrappedConnection@14096e6
不知道是不是由于这个数据源的问题,所以我换成了<local-tx-datasource>,这个问题就没有了,关于数据源的问题,请看:
http://www.jboss.org/file-access/default/members/jbossas/freezone/docs/Server_Configuration_Guide/4/html/Connectors_on_JBoss-Configuring_JDBC_DataSources.html.
<datasources> <local-tx-datasource> <jndi-name>JbpmDS</jndi-name> <connection-url>jdbc:mysql://localhost:3306/jbpmdb</connection-url> <driver-class>com.mysql.jdbc.Driver</driver-class> <user-name>root</user-name> <password>root</password> <metadata> <type-mapping>mySQL</type-mapping> </metadata> </local-tx-datasource> </datasources> - 因为安装时不能成功访问数据库,所以数据库是没有建立成功的,手工建立一个数据库,到jbpm的安装路径下的database目录,运行相应的sql脚本就建立好数据库了。
- 但数据库中空的,需要再执行以下的sql:
INSERT INTO JBPM_ID_GROUP VALUES(1,'G','sales','organisation',NULL); INSERT INTO JBPM_ID_GROUP VALUES(2,'G','admin','security-role',NULL); INSERT INTO JBPM_ID_GROUP VALUES(3,'G','user','security-role',NULL); INSERT INTO JBPM_ID_GROUP VALUES(4,'G','hr','organisation',NULL); INSERT INTO JBPM_ID_GROUP VALUES(5,'G','manager','security-role',NULL); INSERT INTO JBPM_ID_USER VALUES(1,'U','user','user@sample.domain','user'); INSERT INTO JBPM_ID_USER VALUES(2,'U','manager','manager@sample.domain','manager'); INSERT INTO JBPM_ID_USER VALUES(3,'U','admin','admin@sample.domain','admin'); INSERT INTO JBPM_ID_USER VALUES(4,'U','shipper','shipper@sample.domain','shipper'); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(1,'M',NULL,NULL,2,4); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(2,'M',NULL,NULL,3,4); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(3,'M',NULL,NULL,4,4); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(4,'M',NULL,NULL,4,3); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(5,'M',NULL,NULL,1,3); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(6,'M',NULL,NULL,2,3); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(7,'M',NULL,NULL,3,3); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(8,'M',NULL,NULL,3,2); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(9,'M',NULL,NULL,2,2); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(10,'M',NULL,NULL,2,5); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(11,'M',NULL,'boss',2,1); INSERT INTO JBPM_ID_MEMBERSHIP VALUES(12,'M',NULL,NULL,1,1);
- deploy/jbpm/jbpm-service.sar/hibernate.cfg.xml把其中的数据库方言修改正确:org.hibernate.dialect.MySQLDialect
以上是jbpm3的安装,它提供了安装程序,要方便点,但我感觉jbpm4比jbpm3出现的大的变化,例如在jbpm3中jpdl是存放在一个processdefinition.xml文件中,gpd存放在一个单独的文件gpd.xml中,但在jbpm4中所有的都是存放在一个.jpdl.xml文件中。似乎结构变了。
jbpm4的安装
jbpm-4.0.CR1没有提供安装程序,只提供了ant脚本,而且提供的ant脚本是把jboss 5,jbpm及eclipse全部一起安装,对于我们已经有了jboss4.2.2GA的情况,它的脚本肯定不行,所以我们要修改一下它的ant脚本
- 修改jboss/build.xml文件:
- <property name=”jboss.version” value=”4.2.2.GA” /><!–我们使用的是4.2.2.GA–>
- <property name=”database” value=”mysql” /><!–jBPM默认使用hsqldb,我们修改为使用 mysql–>
- <target name=”demo.setup” depends=”install.jbpm.into.jboss” ><!– 我们需要在jboss中安装jBPM所以删除depends中的install.jboss, start.jboss–>
<ant antfile=”${jbpm.home}/gpd/build.xml” target=”install.eclipse” />删除这两项,我们不需要下载eclipse,
<ant antfile=”${jbpm.home}/gpd/build.xml” target=”start.eclipse” />- jBPM默认使用hsqldb,我们修改为使用mysql,到jbpm解压目录/db /jdbc目录中,修改mysql.properties文件(其中连接的数据库事先要建立),
- 到jbpm解压目录/db目录中,修改build.sql 文件: <property name=”database” value=”mysql” />
- 如果要安装example,也需要把修改example下面的build.xml,修改数据库及jboss版本
- 从命令行中进行到jboss目录,运行:ant demo.setup
- 由于我们设置的jboss版本是4.2.2.GA,ant执行完成后会在jBPM目录下生成一个jboss-4.2.2.GA目录,现在我们只需要把这只目录的内容拷贝到jboss的安装目录覆盖其中的目录即可。
- 由于jbpm的ant脚本只考虑了jboss5,所以对于jboss4,我们还需要手动拷贝:
1) 在jbpm/jbpm-service.sar下建立jbpm.deployer目录,结构如下:

2) 拷贝config.jboss4\deploy\jbpm\jbpm-service.sar\jboss-beans.xml文件到jbpm.deployer\jbpm.beans\META-INF中.
2) 拷贝config.jboss4\deploy\jbpm\jbpm-service.sar\META-INF\jboss-service.xml到jbpm.deployer\META-INF\中
3)把jbpm/lib目录中的jbpm-spi.jar,jbpm-jboss4.jar拷贝到jbpm.deployer下面 - 如果安装了例子,则在example/target中有两个文件:examples.bar和examples.jar,部署到jboss的部署目录里
更多的细节可以查看jbpm中的doc
安装下来,jbpm4与jbpm3改变还是很大的
至此,jbpm就安装完成,接下来就看是如何开发及部署jbpm的。如果在jboss启动报错:
— MBEANS THAT ARE THE ROOT CAUSE OF THE PROBLEM —
ObjectName: jboss.j2ee:service=EjbModule,module=jbpm-enterprise.jar
State: FAILED
Reason: org.jboss.deployment.DeploymentException: type-mapping is not initialized: java:/JbpmDS was not deployed or type-mapping was not configured.
同时你也确定jbpm/jbpm-mysql-ds.xml中的jndi-name确实是JbpmDS。那就把<type-mapping>MySQL</type-mapping>修改为<type-mapping>mySQL</type-mapping>。因为在default/conf/standardjbosscmp-jdbc.xml定义的就是:<name>mySQL</name>,区分大小写,所以要一至,可能笔误写成了Mysql,jbpm-3.2.6.SP1的文档中也是大写的,可能也是笔误
在eclipse中安装gpd参考jBPM文档,这不会有什么问题,唯一的问题可能是安装gdp时eclipse没有一些需要的插件,这可能是eclipse的版本问题,下载个for java ee的版本。
部署jbpm应用
我们先来看如何部署,这里以jbpm4为例,之前运行的ant demo.setup在example中生成了一个target目录,把其中的examples.jar(代码),examples.bar(jpdl定义)部署到deploy下面得到的提示是:Packages waiting for a deployer。jbpm是安装好的,也就是说JBPMDeployer是准备好的,那就有可能是不认识.bar文件,jbpm 用户文档上说,部署jbpm只需要所.jpdl.xml文件及代码打包成一个.jpbl的压缩文件就可以(但不知道例子为什么要把.jpdl.xml与代码分开),所以把example.bar修改成example.jpdl就可以部署了,到gwt-console就能看到部署的process:

然后我选择Custom-9,点击Process Instances,进去后是没有Instance的,可以点击Start开始执行一个process,这时候我想看控件台有什么反应,一看吓我一跳,看下图:

也为我两天来的学习找到点乐子
EJB与jBPM集成的例子
jBPM已经在jboss中配置好了,也成功部署了例子,现在要试试如何把jBPM与EJB3集合起来,使用jBPM定义流程,使用EJB3完成任务。那我们分成两个项目:定义jBPM的项目及ejb3项目,把它们分别部署,jbpm的例子中把java代码及jpdl.xml分开打开可能就是这样的考虑。ejb3作为提供业务服务,在没有jbpm的情况下它也能服务其它(如提供远程访问),jbpm项目只是定义business process,使用已有的ejb3提供的服务来完成。这样它们可能分别开发,分别部署,都是在jboss下,这很容易做到。
做为例子假想一个业务流程:订单请求,该订单请求需要salesman和admin都同意
先来建立一个ejb项目,EJB3项目建立一个简单的,如下图:
部分代码如下(只节选部分):
@Stateless public class AdminCheckoutFacadeBean implements CheckoutFacade { @Override public boolean agreeOrderFor(Integer userid) { System.out.println("------------假设admin除了1外,所有人都同意,传入的用户是:"+userid); return !userid.equals(1); } }
@Stateless public class AutoCheckoutFacadeBean implements CheckoutFacade { @EJB(beanName="OrderFacadeBean") OrderFacade bean; @Override public boolean agreeOrderFor(Integer userid) { //这里只是试验流程自动调用java(ejb)代码 System.out.println("------------订单请求的自动检测假设所有人都同意,传入的用户是:"+userid); //为了验证AutoCheckoutFacadeBean被调用时是作为一个POJO调用,还是一个EJB SLSB组件调用 //这里把依赖注入的session打印出来,如果有值,应该就是被作为一个SLSB调用的 //如果没有值,那么说明只是作为一个POJO被调用,虽然是在jboss环境中,但其中的EJB注册符号没有被处理 System.out.println("------------通过@EJB依赖注入的是:"+bean); //如果依赖注入不行,直接使用jndi试试 InitialContext ctx = null; try { ctx = new InitialContext(); bean = (OrderFacade) ctx.lookup("OrderFacadeBean/remote"); }catch (NamingException e) { e.printStackTrace(); } System.out.println("------------直接通过jndi找的是:"+bean); return true; } }
@Stateless public class StartJBPMFacadeBean implements StartJBPMFacade { @EJB(beanName="AdminCheckoutFacadeBean") CheckoutFacade adminCheck; /** * 启动jBPM,并返回process id,这里只是为了测试,后面会用到该id */ @Override public String start() { System.out.println("-------------(StartJBPMFacadeBean)do it-------------"); ProcessEngine processEngine; InitialContext ctx = null; try { ctx = new InitialContext(); processEngine = (ProcessEngine) ctx.lookup("java:/ProcessEngine"); RepositoryService repositoryService = processEngine.getRepositoryService(); ExecutionService executionService = processEngine.getExecutionService(); //部署jbpm long deploymentDbid = repositoryService.createDeployment() .addResourceFromClasspath("cn/li_zone/Order.jpdl.xml") .deploy(); int checkUserId = 20002;//just for test //variables中的map值可以在jpdl中通过#{userid}取到 Map variables = new HashMap(); variables.put("userid", checkUserId); //开始jpdl定义的过程,该过程会到wait for admin agree这一步停下来 //直到admin来处理,也就是要调用下面的adminCheckOrderRequest,过程才往前走 ProcessInstance processInstance = executionService.startProcessInstanceByKey("OrderRequire",variables); //检查自动AutoCheckFacadeBean的返回结果,一个方法的返回结果在jpdl中通过var属性定义 System.out.println("----------- AutoCheckFacadeBean.agreeOrderFor:" +executionService.getVariable(processInstance.getId(),"autocheckIsPass")); return processInstance.getId(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("just for test,something wrong"); } } @Override public void adminCheckOrderRequest(String processid) { //admin处理用户的order请求,这个处理前得让admin知道有这么一个事件你要去处理, //然后admin调用到该接口(比如通过gui上的操作) int userid = 20002;//just for test String result = /*业务处理*/adminCheck.agreeOrderFor(userid) ? "admin accept" : "admin reject";//该值是信号值 ProcessEngine processEngine; InitialContext ctx = null; try { ctx = new InitialContext(); processEngine = (ProcessEngine) ctx.lookup("java:/ProcessEngine"); ExecutionService executionService = processEngine.getExecutionService(); ProcessInstance processInstance = executionService.findProcessInstanceById(processid); String executionId = processInstance.findActiveExecutionIn("wait for admin agree").getId(); processInstance = executionService.signalExecutionById(executionId, result); System.out.println("----------"+result+",so the process is "+processInstance.getState()); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("just for test,something wrong"); } } }
public class TestCase { ... @Test public void orderRequest(){ //先用户请求 try{ System.out.println(remote.start()); }catch(Exception e){ e.printStackTrace(); } } @Test public void adminCheck(){ //jpdl中定义了要等admin确认 //所以这里测试admin确认过程 //并给process发送信号,使其根据admin处理结果走后面的流程 try{ remote.adminCheckOrderRequest("OrderRequire.10");//为了测试分开执行,两个test分开执行,所以这里手工把上一个测试结果拷贝过来 }catch(Exception e){ e.printStackTrace(); } } }
xml定义如下:
<?xml version=”1.0″ encoding=”UTF-8″?>
<process description=”order business process” name=”OrderRequire” xmlns=”http://jbpm.org/4.0/jpdl”>
<start g=”238,28,62,48″>
<transition g=”-84,-18″ name=”to order request” to=”order request”/>
</start>
<java g=”217,118,92,52″ method=”orderRequest” name=”order request” var=”userid”>
<arg><object expr=”#{userid}”/></arg>
<transition name=”to auto check” to=”auto check” g=”-70,-18″/>
</java>
<java g=”166,205,193,52″ method=”agreeOrderFor” name=”auto check” var=”autocheckIsPass”>
<arg><object expr=”#{userid}”/></arg>
<transition g=”-117,-18″ name=”to wait for admin agree” to=”wait for admin agree”/>
</java>
<state g=”182,310,161,52″ name=”wait for admin agree”>
<transition g=”-42,-18″ name=”admin accept” to=”end1″/>
<transition g=”-48,-18″ name=”admin reject” to=”error1″/>
</state>
<end g=”239,449,48,48″ name=”end1″/>
<end-error g=”478,312,48,48″ name=”error1″/>
</process>
测试
先运行orderRequest,模拟一个客户的业务请求,jboss控件台输出如下:

这说明:
- 流程根据jpdl的定义,走到wait for admin agree就停下来了。
- AutoCheckoutFacadeBean被当作POJO运行,而不是ejb的slsb运行,所以EJB注解不被处理,如果要使用其它ejb资源只有使用jndi去找了,不知道是不是什么地方可以设置
- <java var=”return”>该jpdl定义中的rturn将保存调用的java类的方法的返回值
接下来运行adminCheck,模拟admin的操作,该操作将接着处理上面流程,上面在等待admin作返回,该测试就作出相应的反应,调用后运行结果如下:

总结
- 到此,只一个简单的安装配置,及写了一个简单的例子并部署,试着与ejb集成一下
- 要真正把jBPM用起来,先要了解jBPM的各种概念,方法,也就是它的思想
- 同样的流程目的,使用不同的jpdl activity都可以做到,后面再慢慢了解jpdl。
- 前一个状态与后一状态没有直接的信息传递关系或者调用关系,如例子中的order request到auto check并不是说order request调用了auto check,虽然它们都定义的是两个java代码,流程不同状态间的过度关系是由jbpm的ExecutionService负责的,流程的每个状态表示了一个流程的关键点,在这些关系点是可能会执行一些事件,处理。或者等待决定走那上部
- jpdl定义了一个流程的可能有的走向,但具体实现情况流程怎么走,是由代码决定的,比如“wait for admin agree”,如果AdminCheckoutFacadeBean得到的结果是“admin accept”和“admin reject”将走不同的流程
- 流程不一定是顺序执行的,同一个流程也可以有几个过程是同步并发执行的
- 流程也可以异步跨时间空间的执行,比如例子中的“wait for admin agree”,流程走到这里就停下来,等待admin来处理
Comments
Comment from xijunhu2008
Time 2009年07月16日 at 3:03 下午
兄弟,请问你的jbpm4的安装是自己研究出来的吗?我用的是jboss5.0,按你方法安装了之后,进入http://localhost:8080/gwt-console后输入用户名和密码提交后,是一个白白的页面,什么也没有,而且鼠标一直是漏斗状,不知道什么原因?
Comment from 黎子
Time 2009年07月20日 at 10:45 上午
是的,但我用的是jboss4.2.2GA,所以要手动修改很多地方,如果是jboss5.0的话,应该没有这么麻烦,如果你的jboss5.0已经安装好的话,直接运行jbpm的ant脚本,把它生成的文件拷贝到你的jboss里面去。
或者不要修改jbpm的任何内容,直接运行它的ant脚本,它会直接下载jboss5.0和eclipse并把它们安装好,下载eclipse时可以中断它,然后进gwt-console看看行不行
另外要注意数据库的配置
你的情况我猜可能是jboss出现异常了,但gwt-console并不知道,它一直在等待,把jboss的日志修改成debug看看jboss的反应
祝你早日解决问题@_@




Comment from 过客
Time 2009年06月18日 at 5:07 下午
不错,帮我挺大忙的,呵呵