<!----><!----> <!---->
Spring 和 jbpm 整合还是挺复杂的,我把整合的过程以及遇到的一些问题和大家共同分享一下,有不当之处,希望大家指正。我的开发环境是xp3,我所用到的框架版本如下:
struts2.0.11并且降级支持JDK1.4
spring2.0,
spring-modules-jbpm31-0.6.jar
jbpm3.1.4
hibernate.3.2.3
附件就是源码,请参照里面的readme.txt进行相应的配置。
<!----><!----> <!---->
在 Spring 中配置 jbpm 所使用的 SessionFactory
Spring 其它该怎么配还是怎么配置,对于 jbpm 所依赖的 sessionFactory ,我是这么配置的:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource"> <ref bean="dataSource" /> </property> <property name="mappingLocations"> <list> <!-- jbpm's hbm.xml --> <value>classpath*:/org/jbpm/**/*.hbm.xml</value> <!-- other hbm.xml --> <value>classpath*:/com/resoft/system/domain/*.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop> <prop key="show_sql">true</prop> <prop key="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</prop> </props> </property> </bean>
<!----><!----> <!---->
我看有的喜欢将配置
<property name="mappingLocations"> <list> <!-- jbpm's 的hbm.xml --> <value>classpath*:/org/jbpm/**/*.hbm.xml</value> <!-- 其它地方自己配置的 hbm.xml --> <value>classpath*:/com/resoft/system/domain/*.hbm.xml</value> </list> </property>
<!----><!----> <!---->
配置成:
<property name="configLocations"> <list> <value>classpath:/hibernate.cfg.xml</value> ...... </list> </property>
<!----><!----> <!---->
其实也行,不过hibernate.cfg.xml 文件对于后续的 spring 和 jbpm 整合也没有多大用处,尽量争取统一在一处配置即可。下面还会有详细说明。
还有一点就是本 demo 是一个很简单的整合实例,所以其它的 PO 对象所对应的表也和 jbpm 放在一起。实际用的时候,可能需要配置多个数据源,多个 sessionFactory ,并用 JTA 来进行全局事务处理。
<!----><!----> <!---->
开始在 Spring 中配置 jbpm
请注意,现在才是配置的开始,继续在spring的xml中配置
<!-- 关于jbpmConfiguration的配置 --> <bean id="jbpmConfiguration" class="org.springmodules.workflow.jbpm31.LocalJbpmConfigurationFactoryBean"> <property name="sessionFactory" ref="sessionFactory" /> <property name="configuration" value="classpath:jbpm.cfg.xml" /> <!-- 第一处需要注意的地方 --> <property name="processDefinitions"> <list> <ref local="holidayWorkflow" /> </list> </property> <!-- 没什么好说的,是否需要重新建表 --> <property name="createSchema" value="${jbpmConfiguration.createSchema}" /> </bean> <bean id="holidayWorkflow" class="org.springmodules.workflow.jbpm31.definition.ProcessDefinitionFactoryBean"> <!-- 第二处需要注意的地方 --> <property name="definitionLocation" value="classpath:jbmp/workflow/holiday/holiday.zip" /> </bean> <bean id="holidayTemplate" class="org.springmodules.workflow.jbpm31.JbpmTemplate"> <constructor-arg index="0" ref="jbpmConfiguration" /> <!-- 第三处需要注意的地方 --> <constructor-arg index="1" ref="holidayWorkflow" /> </bean>
<!----><!----> <!---->
先看
<!-- 第一处需要注意的地方 --> <property name="processDefinitions"> <list> <ref local="holidayWorkflow" /> </list> </property>
<!----><!----> <!---->
这样配置后,每次重新启动的时候,都会重新发布 list 里的流程,开发的时候建议这么做。
再看
<!-- 第二处需要注意的地方 --> <property name="definitionLocation" value="classpath:jbmp/workflow/holiday/holiday.zip" />
<!----><!----> <!---->
以前整合过的人非常奇怪我这个地方怎么是一个 zip ,而不是什么 processdefinition.xml 文件之类的。最开始的时候,我下载了 javaeye 中某个人写的代码,里面说流程发布一定要通过插件发布,不能通过代码, 后来慢慢在自己摸索中发现,这是 springmodule 的一个不完善的地方。所谓的插件形式发布,我看了代码,就是调用一个执行上传 zip 包的 servlet 。然后再通过 jbpm 的 API 将 zip 包中的 gpd.xml , processdefinition.xml , processimage.jpg 解析发布,最后可以在页面使用 jbmp 自带的 jsp tag 标签 ( 可以随便修改 ) ,将 processimage.jpg 所在流程的正确位置显示出来。
好了,下载 spring-module-jbmp3.1 的源码打开
org.springmodules.workflow.jbpm31.definition. ProcessDefinitionFactoryBean 类,大概在第 56 行左右的地方,将
InputStream inputStream = null; try { inputStream = this.definitionLocation.getInputStream(); this.processDefinition = ProcessDefinition.parseXmlInputStream(inputStream); }
<!----><!----> <!---->
改成
ZipInputStream inputStream = null; try { inputStream = new ZipInputStream(definitionLocation.getURL().openStream()); this.processDefinition = ProcessDefinition.parseParZipInputStream(inputStream); }
<!----><!----> <!---->
这样,就可以注入 zip 包了。
最后看看
<!-- 第三处需要注意的地方 --> <constructor-arg index="1" ref="holidayWorkflow" />
<!----><!----> <!---->
这里的 holidayWorkflow 就是上面配置的一个 processDefinition 。有的人说此处可有可无。我看了 JbpmTemplate 的源码,也确实存在有一个参数的构造函数:
public JbpmTemplate(JbpmConfiguration jbpmConfiguration) { this.jbpmConfiguration = jbpmConfiguration; }
<!----><!----> <!---->
但是如果你想使用这个 JbpmTemplate ,如果某个方法涉及到要使用 processDefinition ,那么就会抛出空指针异常。比如在 JbpmTemplate 源码中,大概第 230 行的如下方法:
public List findProcessInstances() { return (List) execute(new JbpmCallback() { public Object doInJbpm(JbpmContext context) { return context.getGraphSession().findProcessInstances(processDefinition.getId());//这里就使用到了processDefinition } }); }
<!----><!----> <!---->
至于 jbpm 其它方面的配置可以参考 spring-module 的文档中第 9 章的 jBPM 3.1.x 。我这里简单说说。
比如我在 spring 中配置了如下 ApplyAssignmentHandler :
<bean id="applyAssignmentHandler" class="com.resoft.workflow.ApplyAssignmentHandler"> <property name="userService" ref="userService" /> </bean>
<!----><!----> <!---->
这个时候在我的 ApplyAssignmentHandler 就可以享受 spring 的 IoC 优势,帮我把我需要用的那个 userService 注入进去,供我调用:
public void assign(Assignable assignable, ExecutionContext executionContext) throws Exception { userService.find(hql); // 现在可以使用注入后的userService assignable.setActorId(Constants.USER);// 将任务分配给单个用户 }
<!----><!----> <!---->
但是可别忘记了还得在你的流程定义配置文件 processdefinition.xml 中,也配置上
<assignment class="org.springmodules.workflow.jbpm31.JbpmHandlerProxy" config-type="bean"> <factoryKey>jbpmConfiguration</factoryKey> <targetBean>applyAssignmentHandler</targetBean> </assignment>
<!----><!----><!----> <!---->
两边配置自然觉得还是有点麻烦,希望以后再简化争取一个地方配置就完事了。
关于 hibernate.cfg.xml 是否需要的详细分析
是时候谈谈 hibernate.cfg.xml 的时候了。
查看 jbpm-3.1.4 的源码。找到 org.jbpm.persistence.db.DbPersistenceServiceFactory 类,在大概 82 行左右的地方,有如下方法:
//得到jbpm的SessionFactory public synchronized SessionFactory getSessionFactory() { if (sessionFactory==null) { //与spring整合后,这里永远不会为null if (sessionFactoryJndiName!=null) { log.debug("looking up hibernate session factory in jndi '"+sessionFactoryJndiName+"'"); sessionFactory = (SessionFactory) JndiUtil.lookup(sessionFactoryJndiName, SessionFactory.class); } else { log.debug("building hibernate session factory"); sessionFactory = getConfiguration().buildSessionFactory(); //注意这一句中的getConfiguration() } } return sessionFactory; }
<!----><!----> <!---->
该方法在上方第 59 行左右,我们看到了 getConfiguration() 方法:
public synchronized Configuration getConfiguration() { if (configuration==null) { String hibernateCfgXmlResource = null; if (JbpmConfiguration.Configs.hasObject("resource.hibernate.cfg.xml")) { hibernateCfgXmlResource = JbpmConfiguration.Configs.getString("resource.hibernate.cfg.xml"); //取得jbpm.cfg.xml中配置的hibernate.cfg.xml } String hibernatePropertiesResource = null; if (JbpmConfiguration.Configs.hasObject("resource.hibernate.properties")) { hibernatePropertiesResource = JbpmConfiguration.Configs.getString("resource.hibernate.properties"); //取得jbpm.cfg.xml中配置的hibernate.properties } configuration = HibernateHelper.createConfiguration(hibernateCfgXmlResource, hibernatePropertiesResource); } return configuration; }
<!----><!----> <!---->
因为 spring 已经为注入好了 SessionFactory ,所以 if (sessionFactory==null) 永远为 false ,那这又意味着什么呢?
还记得 classpath 下的 jbpm.cfg.xml 吗 ? 里面就有关于 hibernate.cfg.xml 的配置:
<string name="resource.hibernate.cfg.xml" value="hibernate.cfg.xml" />
<!----><!----> <!---->
但通过上面的源码,咱们还发现其实 hibernate.properties 也可以类似的定义如下:
<string name="resource.hibernate.properties" value="hibernate.properties" />
<!----><!----> <!---->
只不过大家一般都是直接照着把 default.jbpm.cfg.xml 的内容直接复制出来,而且一般配置了 hibernate.cfg.xml 就足够了。
最后因为 if (sessionFactory==null) 永远为 false ,所以 getConfiguration() 方法自始至终都不会被调用到,就算你在 jbpm.cfg.xml 配置了 jbpm.cfg.xmlresource.hibernate.cfg.xml,resource.hibernate.properties 也是没有作用的。因此 jbpm 与 spring 整合后, hibernate.cfg.xml 或 hibernate.propties 两个文件可以不要。 ( 有其它用途除外 ) 。
一切似乎至此可以结束了,但我们还是忽悠了一个小的细节。
<!----><!----> <!---->
当我删除测试数据很多的数据库,想重新利用 jbpm 自动建库建表功能时 ( 只需要将下面的配置 value 设置为 true ) ,问题出现了。
<!-- 没什么好说的,是否需要重新建表 --> <property name="createSchema" value="true" />
<!----><!----> <!---->
控制台上出现:
Caused by: org.hibernate.HibernateException: /hibernate.cfg.xml not found
<!----><!----> <!---->
难道上面我分析的不对, hibernate.cfg.xml 始终不能少。带着疑问,我继续打着断点,终于依次看到下述代码(注意,以下的跟踪需要 spring-module , jbpm , hibernate 的源码支持 ):
org.springmodules.workflow.jbpm31.LocalJbpmConfigurationFactoryBean 的 150 行
org.jbpm.JbpmConfiguration 的 410 行
org.jbpm.persistence.db.DbPersistenceServiceFactory 的 108 行
org.jbpm.persistence.db.DbPersistenceServiceFactory 的 69 行
最后来到 org.jbpm.db.hibernate.HibernateHelper 的 83 行,看到如下代码:
if (cfgXmlResource!=null) { // use the configured file name log.debug("creating hibernate configuration resource '"+cfgXmlResource+"'"); configuration.configure(cfgXmlResource); } else { log.debug("using default hibernate configuration resource (hibernate.cfg.xml)"); configuration.configure(); }
<!----><!----> <!---->
因为没有配置 hibernate.cfg.xml( 在 jbpm.cfg.xml 中可以配置该文件路径 ) ,所以自然会调用 else 中的 configuration.configure() 。当我再次跟进去后,一切真相大白。在 org.hibernate.cfg.Configuration 类中的 1413 行出现了下面代码:
public Configuration configure() throws HibernateException { configure( "/hibernate.cfg.xml" ); return this; }
<!----><!----> <!---->
可见,如果你想依赖于 jbpm 的建库功能, hibernate.cfg.xml 不能少。
最后结论,如果你的 jbpm 建库不依赖于它的 API , hibernate.cfg.xml 可以不要,否则目前还是必须配置一下。
至此, spring 和 jbpm 整合结束。
文章太长了,结合struts2的应用部分下次再补发吧。