说说如何在项目中引入 jBPM4 工作流框架以及遇到的坑儿
由于各种原因,我们需要在项目中引入 jBPM4 工作流框架,遇到了不少问题,今记录如下O(∩_∩)O
1 引入步骤
1.1 加入依赖包
- 非 Maven 项目,在 lib 包中加入 jbpm.jar。
- Maven 项目,加入以下配置:
<dependency>
<groupId>org.jbpm</groupId>
<artifactId>jbpm</artifactId>
<version>${jbpm.version}</version>
</dependency>
如果私服上没有,可以自行作为第三方库上传到私服后,再配置 pom.xml。
1.2 集成到 Spring
<!-- jbpm 集成进 Spring -->
<bean id="springHelper" class="org.jbpm.pvm.internal.processengine.SpringHelper"
lazy-init="default" autowire="default">
<property name="jbpmCfg">
<value>jbpm.cfg.xml</value>
</property>
</bean>
<!-- 工作流引擎-->
<bean id="processEngine" factory-bean="springHelper"
factory-method="createProcessEngine"/>
名为 springHelper 的 Bean 中可以配置一个 jbpmCfg 参数,用于自定义 jbpm 配置文件。该文件名为 jbpm.cfg.xml,默认放置在 classpath 路径下。
1.3 配置 Hibernate
因为 jBPM4 使用的是 Hibernate 进行持久化操作,所以我们必须在此配置 jBPM4 持久化映射文件:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.temp.use_jdbc_metadata_defaults">${hibernate.temp.use_jdbc_metadata_defaults}</prop>
</props>
</property>
...
<!-- 持久化 jBPM4 实体类-->
<property name="mappingResources">
<list>
<value>jbpm.repository.hbm.xml</value>
<value>jbpm.execution.hbm.xml</value>
<value>jbpm.history.hbm.xml</value>
<value>jbpm.task.hbm.xml</value>
<value>jbpm.identity.hbm.xml</value>
</list>
</property>
</bean>
一般来说,通过以上步骤就可以通过注入,获取到 jBPM4 的 processEngine 引擎啦O(∩_∩)O哈哈~
1.4 执行脚本
在下载的 jbpm-4.4 包中,打开 install\jdbc\ 文件夹,依据实际的数据库类型,选择相应的脚本,初始化 jBPM 库表:
2 兼容 Hibernate4+
jBPM4 默认适配 Hibernate3,所以如果框架使用的是高版本的 Hibernate,那么就必须修改 jBPM4 的源代码做适配。
下面以 Hibernate4 为例,我们需要修改 jBPM4 这 SpringProcessEngine 与 HibernateSessionDescriptor 两个类。
修改后的 jBPM4 源代码如下:
1、SpringProcessEngine.java
public class SpringProcessEngine extends ProcessEngineImpl {
private static final Log log = Log.getLog(SpringProcessEngine.class.getName());
private static final long serialVersionUID = 1L;
private ApplicationContext applicationContext;
public static ProcessEngine create(ConfigurationImpl configuration) {
SpringProcessEngine springProcessEngine = null;
ApplicationContext applicationContext = null;
if (configuration.isInstantiatedFromSpring()) {
applicationContext = (ApplicationContext) configuration.getApplicationContext();
springProcessEngine = new SpringProcessEngine();
springProcessEngine.applicationContext = applicationContext;
springProcessEngine.initializeProcessEngine(configuration);
LocalSessionFactoryBean localSessionFactoryBean = springProcessEngine.get(LocalSessionFactoryBean.class);
Configuration hibernateConfiguration = localSessionFactoryBean.getConfiguration();
springProcessEngine.processEngineWireContext
.getWireDefinition()
.addDescriptor(new ProvidedObjectDescriptor(hibernateConfiguration, true));
springProcessEngine.checkDb(configuration);
} else {
String springCfg = (String) configuration.getProcessEngineWireContext().get("spring.cfg");
if (springCfg==null) {
springCfg = "applicationContext.xml";
}
applicationContext = new ClassPathXmlApplicationContext(springCfg);
springProcessEngine = (SpringProcessEngine) applicationContext.getBean
("jbpmProcessEngine");
}
return springProcessEngine;
}
public EnvironmentImpl openEnvironment() {
PvmEnvironment environment = new PvmEnvironment(this);
if (log.isTraceEnabled())
log.trace("opening jbpm-spring" + environment);
environment.setContext(new SpringContext(applicationContext));
installAuthenticatedUserId(environment);
installProcessEngineContext(environment);
installTransactionContext(environment);
return environment;
}
@SuppressWarnings("unchecked")
@Override
public <T> T get(Class<T> type) {
T candidateComponent = super.get(type);
if (candidateComponent != null) {
return candidateComponent;
}
String[] names = applicationContext.getBeanNamesForType(type);
if (names.length >= 1) {
if (names.length > 1 && log.isWarnEnabled()) {
log.warn("Multiple beans for type " + type + " found. Returning the first result.");
}
return (T) applicationContext.getBean(names[0]);
}
return null;
}
@Override
public Object get(String key) {
if (applicationContext.containsBean(key)) {
return applicationContext.getBean(key);
}
return super.get(key);
}
}
2、HibernateSessionDescriptor.java
public class HibernateSessionDescriptor extends AbstractDescriptor {
private static final long serialVersionUID = 1L;
private static final Log log = Log.getLog(HibernateSessionDescriptor.class.getName());
protected String factoryName;
protected boolean useCurrent = false;
protected boolean tx = true;
protected boolean close = true;
protected String standardTransactionName;
protected String connectionName;
public Object construct(WireContext wireContext) {
EnvironmentImpl environment = EnvironmentImpl.getCurrent();
if (environment == null) {
throw new WireException("no environment");
}
// get the hibernate-session-factory
SessionFactory sessionFactory = null;
if (factoryName != null) {
sessionFactory = (SessionFactory) wireContext.get(factoryName);
} else {
sessionFactory = environment.get(SessionFactory.class);
}
if (sessionFactory == null) {
throw new WireException("couldn't find hibernate-session-factory " + (factoryName != null ? "'" + factoryName + "'" : "by type ") + "to open a hibernate-session");
}
// open the hibernate-session
Session session = null;
if (useCurrent) {
if (log.isTraceEnabled()) log.trace("getting current hibernate session");
session = sessionFactory.getCurrentSession();
} else if (connectionName != null) {
Connection connection = (Connection) wireContext.get(connectionName);
if (log.isTraceEnabled())
log.trace("creating hibernate session with connection " + connection);
session = (Session)sessionFactory.openStatelessSession(connection);
} else {
if (log.isTraceEnabled()) log.trace("creating hibernate session");
session = sessionFactory.openSession();
}
StandardTransaction standardTransaction = environment.get(StandardTransaction.class);
if (standardTransaction != null) {
HibernateSessionResource hibernateSessionResource = new HibernateSessionResource(session);
standardTransaction.enlistResource(hibernateSessionResource);
}
return session;
}
public Class<?> getType(WireDefinition wireDefinition) {
return SessionImpl.class;
}
public void setFactoryName(String factoryName) {
this.factoryName = factoryName;
}
public void setTx(boolean tx) {
this.tx = tx;
}
public void setStandardTransactionName(String standardTransactionName) {
this.standardTransactionName = standardTransactionName;
}
public void setConnectionName(String connectionName) {
this.connectionName = connectionName;
}
public void setUseCurrent(boolean useCurrent) {
this.useCurrent = useCurrent;
}
public void setClose(boolean close) {
this.close = close;
}
}
3 兼容 Activiti5+
你没有看错,有的项目就是这么奇葩,已经有 Activiti5 咯,还需要集成进 jBPM4……
这两套框架都是同一个架构师 Tom Baeyens 负责的,可谓是一脉相承,所以一些基本 Bean 的命名都是相同的,比如流程引擎 Bean 都叫做 processEngine。因此如果直接按照上述配置,就会出现 Spring Bean 命名冲突的问题。
1、重命名 jBPM 工作流引擎 Bean
<!-- 工作流引擎-->
<bean id="jbpmProcessEngine" factory-bean="jbpmSpringHelper"
factory-method="createProcessEngine"/>
2、在 注入时使用该名称(比如这里取名为 jbpmProcessEngine)
4 非 Spring 环境
是的,有的项目非常老,连 Spring 框架都没有用,纳尼……
可以写一个工具类,把流程引擎对象作为常量返回:
public class WorkflowUtils {
//工作流引擎
private static ProcessEngine PROCESS_ENGINE;
//配置文件前缀
public static final String SPRING_CONFIG_PREFIX = "classpath:resources/";
/**
* 获取工作流引擎
*
* @return
*/
public static ProcessEngine getProcessEngine() {
if (PROCESS_ENGINE == null) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext
(SPRING_CONFIG_PREFIX + "spring-hibernate.xml",
SPRING_CONFIG_PREFIX + "spring-jbpm" +
".xml");
PROCESS_ENGINE = (ProcessEngine) applicationContext.getBean
("jbpmProcessEngine");
}
return PROCESS_ENGINE;
}
}
在此,我们利用 ApplicationContext 加载与 jBPM4 相关的配置文件,然后初始化 ProcessEngine,并设置为常量。这样,以后直接使用这个常量引擎对象就可以啦O(∩_∩)O哈哈~
只要有耐心、细心和恒心,没有我们程序员解决不了的事儿O(∩_∩)O哈哈~