jBPM,全称是Java Business Process Management,是一种基于J2EE的轻量级工作流管理系统。jBPM是公开源代码项目,它使用要遵循Apache License。jBPM在2004年10月18日,发布了2.0版本,并在同一天加入了JBoss,成为了JBoss企业中间件平台的一个组成部分,它的名称也改成JBoss jBPM。随着jBPM加入JBoss组织,jBPM也将进入一个全新的发展时代,它的前景是十分光明的。
jBPM最大的特色就是它的商务逻辑定义没有采用目前的一些规范,如WfMC's XPDL, BPML, ebXML, BPEL4WS等,而是采用了它自己定义的JBoss jBPM Process definition language (jPdl)。jPdl认为一个商务流程可以被看作是一个UML状态图。jPdl就是详细定义了这个状态图的每个部分,如起始、结束状态,状态之间的转换等。
jBPM的另一个特色是它使用Hibernate来管理它的数据库。Hibernate是目前Java领域最好的一种数据持久层解决方案。通过Hibernate,jBPM将数据的管理职能分离出去,自己专注于商务逻辑的处理。
使用jBPM开发工作流的一般流程如下:
1)jBPM的运行需要数据库的支持,因此系统设计时要选定所用数据库。只要是Hibernate支持的数据库,jBPM就支持。数据库的初始化可以由jBPM自动完成,也可以通过ant generate.ddl任务生成SQL语句,在jBPM外部自己创建所需的表。
2)使用jPdl定义工作流,生成processdinination.xml文件。可以采用GUI工具gpdl,但目前只支持jBPM1.0,而且bug很多。XML的DTD定义文件在jBPM下载包中。
3)Ant create.pde生成pde包的工作目录。将processdinination.xml文件和其它需要的文件放在指定的目录下,使用ant build.precess.archives生成pde包。pde包的格式采用jar。
4)更改pde工作目录/src/config/jbpm.properties的相关属性,主要是设定相关的数据库连接信息。注意要将数据库的JDBC驱动放在pde工作目录的lib目录下。
5)Ant deploy.process.archives将刚才生成的pde部署到数据库。实际上就是向数据库插入一些相关数据。
6)利用jBPM API函数开发相应的工作流程。
对jBPM来讲,工作流由一些节点和用户定义的Hander组成。一个工作流从start-state开始,中间经过若干个节点(state、fork、milestone、process-state、decision和join),最后结束于end-state。节点之间通过transient来连接,指明当前节点下面要经过的节点。经过节点时,jBPM会触发相应的Hander来完成用户指定的工作。Hander包括各种事件的ActionHander;指定节点指定操作Actor的AssignmentHandler;定义节点工作的Hander,包括fork节点ForkHander,Join节点JoinHander,decision节点的DecisionHander,process-state节点的ProcessInvocationHandler。jBPM对Hander都有缺省的实现,如果用户没有指定相应Hander,jBPM就使用缺省Hander。
定义工作流程的时候,可以根据实际业务流程,先画UML状态图,然后根据活动图和jpdl编写processdefinition.xml-jBPM工作流定义文件。状态图的节点可以对应到jpdl的相应节点,比如状态对应于state、转换(分叉)对于fork。流程定义文件设计的时候,可以将一些业务处理放在Hander处理。
jBPM节点实际上是XPDL规范中Activity的细化,是一种特殊的Activity。start-state与end-state节点,是由jBPM负责处理,jBPM从start-state节点开始工作流程,在end-state结束工作流程。
state节点要外部的参与才能进入到下一个状态,比如请假流程中需要经理批准的步骤。
Join相当于activity transition的join,fork相当于activity transition的fork,但是采用怎样join和fork,用户可以自己定义。缺省的fork相当于XPDL的AND FORK,每个分叉都走;缺省的join相当于XPDL的AND JOIN,所有分叉都到达后,才进入下一节点。
Decision相当于编程语言中的判断,由DecisionHander决定下一个要经过的节点。通过自己定义DecisionHander,用户可以借助decision节点实现自动节点,即流程到这里,执行一定操作,自动进入下一个节点。
Process-state相当于subflow activity,ProcessInvocationHandler负责处理具体调用子流程的过程,比如子流程名称,传递的参数等。jBPM没有规定子流程的调用过程,只是提供了一个DefaultProcessInvocationHandler,给出了一个调要的范例。
Milestone是一种特殊的节点,用于同步两个执行路径,比如执行到milestone节点后,jBPM就等待,等到另外一条路径上某个节点发出可以继续的信号后,jBPM才进入milestone的下一个节点。节点通过MilestoneReachedActionHandler来发消息。
工作流中用到的所有变量通过Variable来说明,变量类型通过type来定义。类型定义必须要继承org.jbpm.delegation.Serializer接口。变量是作为字符串存储在数据库中的,因此类型要提供与字符串类型相互转换的方法。jBPM变量的作用域是工作流本身。
流程执行到某个节点的时候,会触发节点的相应事件。事件类型包括process-start、process-end、state-enter、state-leave、state-after-assignment、milestone-enter、milestone-leave、decision-enter、decision-leave、fork-enter、fork-every-leave、join-every-enter、join-leave、transition。事件具体执行的操作,用户通过继承ActionHander接口来指定。
1、process definition
一个process definition代表了一个正式的业务流程,它以一个流程图为基础。这个流程图由许多node和transition组成。每个node在这个流程图里都有着各自特殊的类型,这些不同的类型决定了node在运行时的不同行为。一个process definition只有一个start state。
2、token
一个token代表了一条执行路径,它包含了这条执行路径的当前的执行状态(current state)。
3、process instance
一个process instance(流程实例)即一个process definition(流程定义)的流程执行实例。一个process definition可以对应多个process instance。当一个process instance被创建的时候,一个主执行路径token同时被创建,这个token叫做root token,它指向流程定义的start state(processDefinition.getStartState()==token.getNode())。
4、signal
一个signal发送给token通知token继续流程的执行。如果signal没有指定transition,token将沿缺省的transition离开当前状态,如果signal指定transition,token将沿指定的transition离开当前的状态。看源代码可以看到发给process instance的signal其实都是发送给了root token。
5、Actions
jbpm提供了灵活的action,当流程执行,token进入node和transition时,会触发相应的一些event(事件)。在这些event上附上我们自己写的action,就会带动action的执行。action里是我们自己的相关java操作代码,非常方便。注意的是event(事件)是内置的,无法扩展。另外,action也可以直接挂在node上,而不依赖于event(事件)的触发,这个很重要!
一个Task instance(任务实例)可以被分配给一个actorId (java.lang.String)。所有的Task instance都被保存在数据库中的表jbpm_taskinstance里。当你想得到特定用户的任务清单时,你就可以通过一个与用户关联的actorId来查询这张表。
一个流程定义有一个TaskMgmtDefinition;一个TaskMgmtDefinition对应多个swimlane,同时对应多个task;一个swimlane有多个task,可以 TaskMgmtDefinition中通过task的名称直接获取相应的task;
swimlane对象有四个属性,分别是name(名字)、assignmentDelegation(分配代理类)、taskMgmtDefinition、tasks(Set对应多个task),可以增加task
task对象主要的属性:taskMgmtDefinition、swimlane、assignmentDelegation、taskNode,需要注意的是swimlane和assignmentDelegation中间只是可以一个属性有值,因为它们都和任务的分配有关系。
一个流程实例有一个TaskMgmtInstance;一个TaskMgmtInstance对应多个swimlaneInstance,同时对应多个taskInstance;一个swimlaneInstance有多个taskInstance,可以从TaskMgmtInstance中直接获取相应的taskInstance;
swimlaneInstance对象主要有五个属性,分别是name、actorId、pooledActors(Set)、swimlane、taskMgmtInstance。
taskInstance对象的主要属性:name、actorId、task、swimlaneInstance、taskMgmtInstance、pooledActors。
当对任务进行分配时,一般需要实现AssignmentHandler这个接口,这个接口的方法只有一个:
void assign( Assignable assignable, ExecutionContext executionContext ) throws Exception;
一个典型的实现(把名字是'change nappy'的任务交给NappyAssignmentHandler这个类来分配)
<task name='change nappy'>
<assignment class='org.jbpm.tutorial.taskmgmt.NappyAssignmentHandler' />
</task>
NappyAssignmentHandler类:
public void assign(Assignable assignable, ExecutionContext executionContext) {
assignable.setActorId("papa");
}
同样,Assignable只是一个接口,它有两个方法:setActorId()和setPooledActors(),Assignable的具体实现类也是两个
swimlaneInstancehe和taskInstance。这样就不不难理解整个任务分配流程了:
1、流程进入TaskNode节点,执行TaskNode类的execute()方法,该方法首先获得TaskMgmtInstance实例,然后通过它来创建TaskInstance。taskMgmtInstance.createTaskInstance(task, executionContext);
2、在上面的createTaskInstance(task, executionContext)里,该方法调用了taskInstance.assign(executionContext)对taskInstance进行分配。
3、在assign(executionContext)方法里,首先会判断task属性里是否存在swimlane,如果有的话,这个taskInstance就会分配给swimlane指定的ActorId或PooledActors;如果不存在,再去找task属性里assignmentDelegation(分配代理类)通过代理类(即我们自己写的实现AssignmentHandler这个接口的类)指定ActorId或PooledActors。
·State中定义参与者,state是流程的主要环节,必须要人机参与处理,调用ExecutionService . endOfState ()来结束活动,使jBPM引擎流转。action是用来扩展的,action的定义如下
<!ELEMENT action ( delegation ) >
<!ATTLIST action event-type (process-start|process-end|state-enter|state-leave|state-after-assignment|milestone-enter|milestone-leave|decision-enter|decision-leave|process-state-enter|process-state-leave|fork-enter|fork-every-leave|join-every-enter|join-leave|transition) #IMPLIED>
<!ELEMENT delegation ( #PCDATA ) >
<!ATTLIST delegation class CDATA #REQUIRED>
state的Action支持state-enter,state-leave和state-after-assignment事件,可以对每个事件处理不同业务。实现Action必须实现接口org.jbpm.delegation. ActionHandler.
process-state, decision, fork, join跟Action一样都可以支持事件机制
process,fork,decision,join是jBPM引擎自动完成的,完成执行的操作就是节点的delegation里面注册的类.如果没有就是用系统默认的。
工作流引擎与应用的接口原理
应用调用ExecutionService. StartProcessInstance()启动流程,引擎产生任务。等待任务的所有者调用ExecutionService. endOfState ()指令来完成任务。当endOfState指令被调用后,引擎就进行运算产生的新的任务,然后继续等待endOfState指令的调用直到流程结束。
引擎只对State产生任务,对其他的节点比如:process-state,fork,decision,join都不产生任务。只对他们进行运算,执行每个节点的Delegation和Action.
jbpm中最重要的概念,应该是令牌(Token)和信令(Signal)技术,这个在计算机网络中到处可见
的技术,在工作流引擎中也大放异彩.
我们看如下代码:
//pd是process definition,pi是process instance
ProcessInstance pi = new ProcessInstance( pd );
//启动流程
pi.start();
//得到根令牌
Token token = pi.getRootToken();
//发信令
token.signal();
Token的signal方法也可以传入transition参数,这个方法把信令发送给Token,这样,令牌将被激活,并沿指定的transition离开当前的状态(如果没有指定transition,将沿缺省的transition离开当前状态).
jbpm是怎么实现的呢?其实很简单:
1)Token记录了当前的状态,只有当前的状态(或称节点)拥有该令牌
2)向TOKEN发signal后,当前状态收到该signal
3)当前状态把令牌传给signal中指定的transition
4)transition收到令牌后,不强占,马上把令牌传给下个状态.
5)根据令牌的位置,流程的状态已经发生改变
JBPM数据库中每一个表都有一个主键-ID(Long类型)。这个ID是可以由用户自己产生,也可以由JBPM产生。这个数值在所有表的ID中是唯一的。用户通过设定jbpm.id.generator属性来设定id产生的类。
JBPM中ID产生的基本原理是:用表JBPM_SEQUENCEBLOCK存储当前可用的ID值,JBPM需要使用ID时,从数据库中取得这个ID,然后增加一定的数量作为当前可用ID值,并存储在数据库中。JBPM的ID可以用于服务器集群的情况。hibernate中产生ID的函数IncrementGenerator,原理与JBPM产生ID相似,但不能应用到集群的情况。
JBPM_SEQUENCEBLOCK表有两个字段ID和NextID,前者为主键,表示当前节点在集群中的ID号码,后者为当前节点可以使用数据段的最小数。表由类org.jbpm.persistence.hibernate.SequenceBolck代表,org.jbpm.persistence.hibernate.SequenceBolckIdGenerator维护。
SequenceBolckIdGenerator主要函数:
(1)public SequenceBlockIdGenerator(JbpmConfiguration jbpmConfiguration)
初始化,包括初始化nodeId和blockSize变量。nodeId指明当前的节点在集群中的Id号(0-65535),用户可以通过jbpm.id.nodeId设定,缺省值是0。BlockSize指明当前节点使用长整形数段的一个数据块大小,用户可以通过jbpm.id.bolckSize设定,缺省值是100。
(2)public long getNextId() //得到当前可用ID,JBPM使用它得到ID
{
return getNextId(sessionFactory, nodeId, blockSize);
}
(3)public static synchronized long getNextId(SessionFactory sessionFactory,
long nodeId, long blockSize)
最重要的函数,用于计算当前可用ID。
基本思路:将1-Long.MaxValue范围内数,按大小blockSize*MaxNodes分段,然后再将每一段按blockSize大小分成MaxNodes块,各节点按照节点号分块使用,如节点号为0的只能使用第一块,节点号为1的使用第二块,依次类推。MaxNodes为当前集群中的节点总数,为65535。
取值算法:
(1)取出节点当前可以使用数块的首数(块的最小数);
(2)首数+blockSize×NodeId(节点号码,0-65535)为当前可以使用的Id(nextId);
(3)nextId+blockSize-1为当前可以使用的最后一个Id(lastId)
(4)调用getNextId()函数时,如果nextId<=lastId,则返回nextId,然后将nextId++;否则取出下一数据块进行1-3的计算。
说明:
(1)读取一次数据库,可以使用blockSize个Id,不用每次使用ID都读取一次数据库。
(2)表的NextID字段存储的是当前可使用块而不是使用过块的首数。每个节点使用块的首数按节点号码分别存储。
(3)对于每一个数块,每个节点只使用其中一部分,范围是(首数+blockSize×NodeId)-(首数+blockSize×(NodeId+1)-1),从而保证了每个节点使用的ID值在数据库中是唯一的。
为了保证产生ID的唯一性,JBPM要求对表JBPM_SEQUENCEBLOCK进行操作的数据库Session必须是独立的,不能用于其它的数据库操作。如果JBPM使用数据库连接池,则必须要设定jbpm.id.generator.configuration属性,提供进行数据库直接连接的hibernate配置文件。默认情况下,JBPM使用一个全局静态Session进行除JBPM_SEQUENCEBLOCK以外表的操作,而对JBPM_SEQUENCEBLOCK表进行操作的Session是在类SequenceBolckIdGenerator初始化时临时建立的。.
acceptToken是JBPM工作流引擎中的核心函数,它在每个节点中都有具体的实现。这个函数主要决定该节点如何处理到达该节点的令牌。理解了acceptToken,JBPM工作流的运行过程就懂得了一半。acctpToken在各节点类中具体实现的功能如下所示:
一、TransitionImpl
1)触发Transition事件;
2)将到达节点作为当前执行上下文节点;
3)达到节点接受令牌(执行节点的acceptToken)。
二、DecisionImpl
1)触发DECISION_ENTER事件;
2)调用DecisionHandler得到要执行的转换(Transition);
3)触发DECISION_LEAVE事件;
4)转换接受令牌(执行节点的acceptToken)。
三、Fork
1)触发FORK_ENTER事件;
2)将运行上下文中令牌的State和ActorId置null;
3)调用ForkHander;
缺省的ForkHander,对于每一个它的转换按顺序执行以下操作:
l触发FORK_EVERY_LEAVE事件;
l创建令牌,上下文中的令牌与新建令牌为父子关系;
l子令牌数据库持久化;
l将上下文中的令牌置为子令牌,上下文的当前节点备份;
l当前转换接受令牌;
l将上下文中的令牌置为父令牌,节点还原;
l子令牌reactiveJoin属性设定为将父令牌中的correspondingJoin。
四、Join
1)如果当前上下文中的令牌没有结束,结束令牌(包括该令牌的所有子令牌);
2)触发JOIN_EVERY_ENTER事件;
3)清楚上下文中的临时变量;
4)将令牌、当前Join节点置为当前运行上下文中的令牌和节点;
5)执行JoinHander。
缺省的JoinHander在所有兄弟令牌都到达该Join节点后,激活父令牌,执行Join的转换。父令牌激活时会触发JOIN_LEAVE事件。
五、StateImpl、StartStateImpl
1)将上下文中令牌的状态设定为当前状态;
2)触发STATE_ENTER事件;
3)如果状态设定了swimlane属性,从swimlane中取出actorId值设定为上下文中令牌的actorId值;
4)如果State要求必须assignment但actorId为null,则报错;
5)记录StateLog在令牌中;
6)触发STATE_AFTER_ASSIGNMENT事件。
六、EndStateImpl
1)将lastLog置空;
2)上下文中的令牌置为当前令牌;
3)结束令牌(会触发PROCESS_END事件)。
七、MilestoneImpl
1)将上下文中令牌的状态设定为当前状态;
2)触发MILESTONE_ENTER事件;
3)从上下文中取出名称为Milestone名称的变量;
4)如果变量存在(不为null),触发MILESTONE_LEAVE事件,执行Milestone转换(进入下一个状态);否则,将令牌中的actorId值置空,在令牌中记录日志(等待)。
八、ProcessStateImpl
1)在上下文令牌中记录状态日志;
2)取得ProcessInvocationHandler,启动子工作流程;
3)工作流启动成功后,将上下文中令牌actorId置空,当前状态设定为令牌当前状态,令牌子进程实例设定为已经启动成功的子工作流程;否则报错。
ExecutionServiceImpl是JBPM的核心类,通过它用户可以启动工作流、使工作流进入下一个节点等等。
一、InvocationLog startProcessInstance(Long definitionId, Map variables,
String transitionName)
功能:启动一个工作流示例。
1)根据definitionId得到工作流定义并初始化一个流程实例;
2)得到示例的根令牌,并在令牌中记录InvocationLog;
3)流程实例持久化;
4)初始化执行上下文;
5)如果根令牌分配了actor,则将actor存储在上下文中;
6)触发PROCESS_START事件;
7)找到开始状态的转换,让其接收令牌。
<?XML:namespace prefix = o ns = "urn:schemas-microsoft-com:Office:office" />
二、InvocationLog endOfState(Long tokenId, Map variables,
String transitionName)
功能:使工作流进入下一个状态。
1)根据tokenId从数据库得到令牌;
2)检查令牌是否满足认证要求;
3)创建记录InvocationLog;
4)创建运行上下文,并初始化上下文变量;
5)触发STATE_LEAVE事件;
6)令牌状态的转换接受令牌
SPRING+HIBERNATE3+JBPM+OC4J(初步实现整合)
整合SPRING与JBPM核心就是需要将JBPM的BEAN由SPRING来管理利用IOC来展现SPRING框架的优势!
这2天在研究怎样整合两者!在整合过程中也出现了不少意想不到的问题!在这里记录下来希望可以未对此感兴趣的朋友提供些帮助!
SPRING+JBPM
1)下载SPRING-JBPM MODULE
这个模块是SPRING支持JBPM使用的必要模块!现在还没有放入SPRING的发行版本中!不过通过她基本可以实现了对JBPM的操作!代码需要从SPRING的CVS下载
地址是:pserver:[email protected]:/cvsroot/springframework
从中获取SPRING-JBPM MODULE即可.
2)SPRING加载JBPM资源
SPRING-CONFIG.XML(部分)
数据源
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean" destroy-method="close">
<property name="jndiName" value="jdbc/jbpm"/>
</bean>
HIBERNATE SESSION FACTORY
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref local="dataSource" />
</property>
注意这里由于我使用的JBPM是以JAR的形式存在于CLASSPATH中的所以当加载其中的HIBERNATE实体描述文件的时候需要使用下面的写法
<property name="mappingJarLocations">
<list>
<value>WEB-INF/lib/jbpm-3.0.1.jar</value>
<value>WEB-INF/lib/jbpm-identity-3.0.1.jar</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
为了使HIBERNATE3查询的时候使用正确的语法需要指明查询工厂类,如果不指定回出现
ClassNotFoundException: org.hibernate.hql.ast.HqlToken的错误!
<prop key="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory"/>
</property>
</bean>
下面红色的部分是SPRING管理JBPM的必要配置!里面的JBPMTEMPLATE是很重要的类
SPRING中使用JBPM基本都是通过她来完成的!
<bean id="hibernateConfiguration" factory-bean="&sessionFactory" factory-method="getConfiguration"/>
<bean id="jbpmSessionFactory" class="org.springframework.workflow.jbpm.LocalJbpmSessionFactoryBean">
<property name="hibernateSessionFactory" ref="sessionFactory"/>
<property name="hibernateConfiguration" ref="hibernateConfiguration"/>
</bean>
<bean id="jbpmTemplate" class="org.springframework.workflow.jbpm.JbpmTemplate">
<property name="jbpmSessionFactory" ref="jbpmSessionFactory"/>
</bean>
这是JBPM的管理既纳入到了SPRING中!
3)使用SPRING操作JBPM
在我们的类中需要调用JBPM的功能时我们只需要将JBPMTEMPATE类注入到我们的类中
在jbpmTemplate中实现了不多的几个方法,有很多JBPM的方法没有实现不过没关系我们可以使用SPRING中最常见的CALLBACK机制来实现我们的需要,下面举出几个例子:
A)保存流程定义方法
利用SPRING将jbpmTemplate注入进我们的类中
private JbpmTemplate jbpmTemplate = null;
public void setJbpmTemplate(JbpmTemplate jbpmTemplate) {
this.jbpmTemplate = jbpmTemplate;
}
/**
*保存流程定义
* @param xmlStr流程定义的JPDL的XML字符串形式
*/
public void saveWorkFlowDefinition(String xmlStr){
log.debug("saveWorkFlowDefinition start");
final ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(xmlStr);
//call spring jbpm call back for save processdefinition
jbpmTemplate.execute(new JbpmCallback(){
public Object doInJbpm(JbpmSession jbpmSession){
jbpmSession.beginTransaction();
GraphSession gSession = jbpmSession.getGraphSession();
gSession.saveProcessDefinition(processDefinition);
jbpmSession.commitTransaction();//这里我们只需要提交事物即可!关闭SESSION的工作JBPMTEMPLATE回帮助我们完成!
return processDefinition;
}});
log.debug("saveWorkFlowDefinition end");
}
B)取得所有的流程定义:
/**
*取得所有的工作流定义
* @return工作流定义列表
*/
public List getAllWorkFlow(){
log.debug("getAllWorkFlow start");
List list = (List)jbpmTemplate.execute(new JbpmCallback(){
public Object doInJbpm(JbpmSession jbpmSession){
GraphSession graphSession = jbpmSession.getGraphSession();
log.debug("getAllWorkFlow end");
List tempList = graphSession.findAllProcessDefinitions();
return tempList;
}});
log.debug("getAllWorkFlow end");
return list;
}
工作流的各种标准里面通常强调的是流程本身的定义,对任务分派这块通常没有提及,而这块又是工作流应用必不可少的。
JBPM实现两种任务分派机制:
1 Client based assignment
基本思想就是用户自己很清楚整个流程由哪些人来执行。流程定义文件中只指定流程执行的节点步骤。而在用户接口的每一步执行前,让用户手工输入下一步节点的执行人。这个有点类似通达OA的自由流程。
2 Process based assignment
就是在流程定义中规定好某个节点由谁来执行。有很多不同的asignHandler,例如直接给出用户名,或者指定为上一个流程执行的用户名,或者使用某种表达式来给出用户。
额外补充:关于将一个任务分派给一个组
具体场景:比如说呼叫中心接到一个保修任务,报务员把任务分给维修部,维修部有很多人都可以处理这个业务,所以流程执行的时候,我们不知道谁将最终执行.只能是当前手头空闲的人来执行。
这种情况的处理方法是,建立一个包含用户和用户所在的组的两个任务列表
个人任务列表 1 do...[action] 2 do..[action] |
组任务列表 1do..[take] |
当一个任务被分派给一个组的时候,该任务出现在该组所有成员的组任务列表里面。当前可以执行这个任务的
用户,点击take,把这个任务从组任务列表里面取到自己的任务列表里面。现在该任务就归这个人所有,其他人的组任务列表里面不再显示该任务。这里完成了一个reassignment。
注意:对于actor,在JBPM里面people,groups,systems都是通过一个actorId来标示的。
Jboss jBPM中swimlane、actor和role概念的理解
jbpm没有role的概念。actor,我认为就是一个具体人所具有的所有角色的集合,是roles,不是role。一个actor可以具有多个role。比如请假流程中,如果部门经理请假,他发起请假流程的actor就是包含请假者和部门经理两个角色。swimlnae就是判断当前actor在节点中扮演什么角色。比如在请假流程的部门经理批准节点,swimlane就会判断当前开启服务的actor是否是部门经理,如果是就交由他处理当前节点。
欢迎讨论。
利用JBPM开发一个工作流应用,相对于使用shark是比较简单直观的。我们之前提到过,一个工作流管理系统最基本的组件包括流程定义组件,流程执行组件和流程客户端组件。下面从这三个方面看一下JBPM对开发工组流应用的支持。
JBPM没有采用WfMC提出的流程定义语言XPDL,而是自己开发了一种称为JPDL的语言来定义流程。因此,在开发一个应用时我们最终需要生成一个符合该XML schema的文件processdefinition.xml来表示定义好的流程。它可以manually获得,也可以使用可视化的定义工具自动生成。
JBPM专门提供了一个开发流程的环境,称为process development environment(pde)。可以在jbpm根目录下执行命令ant create.pde来生成pde工作目录。生成的包结构如下所示:
Build.xml文件用于配置一个流程,即利用ant工具解析processdefinition.xml文件,并将运行流程时所需的相关信息存储到数据库中。
Lib存放了开发和配置流程所需的全部库文件,包括数据库的jdbc驱动。
Src包括开发和配置流程的全部源文件和所需资源。
Src/config只有两个文件,jbpm.properties和log4j.properties。其中,jbpm.propertie文件包含了数据库的配置信息。因此,更换数据库或数据库的jdbc驱动都需要修改这里。
Scr/java存放java源文件。
Src/process存放工作流定义文件(processdefinition.xml)和相关的资源文件,如图片和form文件。
Src/test存放测试代码。
Target/classes是src/java中源码的输出目录,存放编译后的class文件。
Target/par存放用命令ant build.process.archives生成的par包。Par包实际上就是把src/process中的流程定义及相关文件打包成zip形式存储。
Target/test-classes存放测试程序的编译结果。
Target/test-report存放测试报告。
JBPM把负责执行流程的类库打包成jbpm.core.jar,它也是JBPM工作流引擎的核心。在开发一个工作流应用时,只需将该jar文件放到相应的lib目录下面。而开发人员就可以专注于开发一个流程模型,完成对流程的定义,而无需过多考虑流程执行的细节。
客户端组件的开发,需要一个应用服务器作为servlet container,我们这里选择了tomcat。JBPM将开发一个webapp所需的类打包成jbpm.web.jar,包括自定义的tag和Struts用到的类等等。开发应用时,将该jar放到WEB-INF/lib下面就可以了。
一个基于工作流的webapp应用的开发和普通webapp的开发方式很相似。包结构也保持一贯作风:
所以,我们需要做的主要工作是完成客户端应用所需的jsp页面的开发,可以选用Struts来进行开发。需要注意的是,我们要对lib目录下的jbpm.properties文件进行配置,主要是对数据库jdbc驱动的配置。
JBPM可以支持多种数据库系统,包括MSSQL,,Mysql,Oracle,hdbsql等。它提供了很灵活的配置方式,只需要修改jbpm.properties文件,同时将相应的JDBC Driver拷贝到lib目录就可以了。利用ant的generate.ddl命令,JBPM还支持自动生成用于创建系统表的sql脚本,而且可以生成对应于各种数据库系统的脚本,非常便于数据库系统的更换和系统数据库的创建。
环境配置如下:
JBPM 3.0
Ant1.6.2
Tomcat5.0.27
SQL Server 2000
SQL Server 2000 Driver for JDBC
仍以之前演示过的request a payraise为例(见下图),介绍一下利用JBPM开发一个工作流应用需要完成的工作。
1)首先,我们需要完成流程的定义。这不仅包括定义processdefinition.xml,还要对流程执行时使用到的其他资源进行定义。比如,在web应用中用到的图片,form等。还需要配置form.xml,该文件确定了流程中不同状态和form的关系,如request a payraise状态下,需要如下图所示的form:
补充Processdefinition.xml示例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE process-definition PUBLIC"-//jBpm/jBpm Mapping DTD 2.0//EN" "http://jbpm.org/dtd/processdefinition-2.0.dtd">
<process-definition name="pay raise process">
<!-- SWIMLANES -->
<swimlane name="requester" />
<swimlane name="boss">
<delegation class="org.jbpm.delegation.assignment.ActorAssignmentHandler">cg</delegation>
</swimlane>
<swimlane name="erp operator">
<delegation class="org.jbpm.delegation.assignment.ActorAssignmentHandler">pf</delegation>
</swimlane>
<!-- START-STATE -->
<start-state name="request a payraise" swimlane="requester">
<transition to="evaluating"/>
</start-state>
<!-- NODES -->
<state name="evaluating">
<assignment swimlane="boss" />
<transition name="approve" to="fork"/>
<transition name="disapprove" to="done"/>
</state>
<fork name="fork">
<transition to="updating erp asynchronously" />
<transition to="treating collegues on cake and pie" />
</fork>
<state name="updating erp asynchronously">
<assignment swimlane="erp operator" />
<transition to="join" />
</state>
<state name="treating collegues on cake and pie">
<assignment swimlane="requester" />
<transition to="join" />
</state>
<join name="join">
<transition to="done" />
</join>
<!-- END-STATE -->
<end-state name="done" />
</process-definition>
2)有了processdefinition.xml文件,我们就可以配置流程。即使用ant命令,解析这个文件并且将执行时需要的信息存储在数据库中。如下图:
3)我们无需关心流程将怎么执行,完全交给jbpm.core.jar就好。
4)剩下的另外一项比较繁重的工作就是开发一个用于该流程的客户端应用。前面已经提到过,我们需要做的主要工作是完成客户端应用所需的jsp页面的开发。包括登录页面,查看自己的tasklist页面,执行task的页面等。除此之外,还可以通过日志来完成流程的监控和管理界面。这些都由应用的需求来决定。
Decision是jbpm中非常重要的一种Node,在我们的一般的工作流系统中使用的也很频繁,
本文谈谈它的使用.
1:如果客户端能够比较容易的判断decision后应该到哪个transition,我们只需要把
transition的名称作为signal发给Token就可以了,此时,引擎将驱动流程转向该
transition.
2:如果是直接使用Decision,则它是基于BeanShell的.
BeanShell脚本必须把选择的转移的名称赋给"transitionName"变量.
脚本中可以使用的变量有两类:
1)token
2)环境变量
3:变量可以从两个地方来声明:
1)环境变量
2)脚本的开始
如果环境变量中没有声明,则从脚本中读取,前者的性能要好一些.
jbpm自己给的一个beanshell的使用例子如下:
* transitionName = "left";
* if ( ( spottedOnTheRight.equals( "woman" ) )
* && ( beauty.equals( "in the eye of the beholder" ) ) ) {
* transitionName = "right";
* }
在jbpm3中,采用反射实现了从配置文件中构造一个新的对象的功能,
使用方法如下:
String configuration =
"<s>hello</s>" +
"<stringConstructor>" +
" i want yoghurt" +
"</stringConstructor>" +
"<structuredElement>" +
" <surfboard length=/"270/" />" +
" <mast length=/"475/" />" +
" <boom length=/"160/" />" +
" <sail size=/"5.7/" />" +
"</structuredElement>" +
"<memberWithoutSetter>hello</memberWithoutSetter>";
Mix mix = (Mix) beanInstantiator.instantiate(Mix.class, configuration);
assertEquals( "hello", mix.s );
它的实现方法很简单,就是使用java.lang.reflect.*包,构造对象,然后根据解析的文件
为对象的属性赋值
其实,我们把它的实现稍做扩展,就可以实现我们自己的ioc:
Mix mix=(Mix)MyIoc.getbean("theMix");
和
Mix mix = (Mix) beanInstantiator.instantiate(Mix.class, configuration);
比较,显得更简单一些,呵呵.
jbpm解析流程定义有三种方式:
1)par包
static ProcessDefinition auctionProcess =
ProcessArchive.parse("org/jbpm/tdd/auction.par");
注意,必须在classes的org/jbpm/tdd/目录下有一个auction.par文件
2)xml文件方式
static ProcessDefinition auctionProcess =
JpdlXmlReader.parseFromResource("org/jbpm/tdd/auction.xml");
注意,必须在classes的org/jbpm/tdd/目录下有一个auction.xml文件
3)文本方式
static ProcessDefinition auctionProcess = JpdlXmlReader.parse(
"<process-definition>" +
" <start-state name='start'>" +
" <transition to='auction'/>" +
" </start-state>" +
" <state name='auction'>" +
" <transition to='end'/>" +
" </state>" +
" <end-state name='end'/>" +
"</process-definition>");
这种方式的本质和xml文件解析方式是一样的.
swimlane ,actorid,role在实际项目的演绎:
工作流程中的某些状态处于一个swimlane(泳道)中,参与工作流程的具体人如果具备涉足这个泳道的能力,就可以对这些状态进行处理。如何在计算机系统表示个体的能力度?
在工作流系统中通过组织建模,从组织模型数据中取得一定属性赋给个体,比如通过role角色、职务(或职务)来表示个体具有的能力度。在工作流程定义中,很少会把具体的个体定义进流程审批环节中。大多数系统会定义一个角色(静态和动态)、职位,只要满足这些条件个体都可以处理这些环节(也即具备这些能力个体进入系统后,系统会把他可以处理的工作全部列出来)。
通过swimlane这种抽象的能力度对象,把定义的swimlane赋给某些审批环节,系统
在实际运行中,根据swimlane的代理类计算出流程参与者(actorid),需注意一点是这个参与者和系统用户登录id有所区别,他可以是用户登录id,也可以是role或position。jbpm在这点设计考虑很周到,仅提供流程引擎,不绑定任何组织模型。用户可以根据各自的组织定义自己流程。
如示例:
public class ChiefHandle implements AssignmentHandler {
public ChiefHandle() {
}
public String selectActor(AssignmentContext parm1) {
String actorid=parm1.getPreviousActorId();//比如可以通过前一步的actorid计算出流程参与者
System.out.println("---previous actor id="+actorid);
//userid是在流程发启时,赋的用户登录名,根据登录名可以从组织模型找到相关的数据
//比如要求上级领导等
String firstactorid=(String)parm1.getVariable("userid");
System.out.println("---root actor id="+firstactorid);
if(actorid.equals("US010101"))
return "POS0102";//返回职位代码或登录id等
else
return "POS0103";
}
}
所以用户登录系统中,工作列表不仅仅是根据登录id查找,而且需根据此用户具备的角色或职位等
相关信息查找。一个切实可行办法。比如怎样定义:处理上一个状态环节的个体的同事这样的角色等,是否可以通过登录用户的单位代码、部门或项目组代码(selectActor)?
我们知道,流程定义中的Variable是要持久化的,比如可能存放到数据库中,那么,类型怎么处理呢?
我们看看jbpm3的方法:
jbpm3定义了自己的类JbpmType,该类有两个属性:
1)public Class variableInstanceClass = null;
它表示该Variable对应于引擎的类型
2)public Converter converter = null;
他表示从引擎的类型与JAVA类型间的转换器
处理时,引擎先到类路径的jbpm.type.properties文件中查找,如果没有,再到
org/jbpm/db/hibernate/jbpm.types.properties文件中查找,该文件是编辑好了的,如下:
# each line contains 2 or 3 class names (transformer-class is optional) separated by a space :
# variable-class <space> transformer-class <space> variableinstance-class
# or
# variable-class <space> variableinstance-class
### stored in a text-field in the database###
java.lang.String org.jbpm.context.exe.variableinstance.StringInstance
java.lang.Boolean org.jbpm.context.exe.converter.BooleanToStringConverter org.jbpm.context.exe.variableinstance.StringInstance
java.lang.Character org.jbpm.context.exe.converter.CharacterToStringConverter org.jbpm.context.exe.variableinstance.StringInstance
java.lang.Float org.jbpm.context.exe.converter.FloatToStringConverter org.jbpm.context.exe.variableinstance.StringInstance
java.lang.Double org.jbpm.context.exe.converter.DoubleToStringConverter org.jbpm.context.exe.variableinstance.StringInstance
java.io.Serializable org.jbpm.context.exe.converter.SerializableToStringConverter org.jbpm.context.exe.variableinstance.StringInstance
### stored in a number-field in the database###
java.lang.Long org.jbpm.context.exe.variableinstance.LongInstance
java.lang.Byte org.jbpm.context.exe.converter.ByteToLongConverter org.jbpm.context.exe.variableinstance.LongInstance
java.lang.Short org.jbpm.context.exe.converter.ShortToLongConverter org.jbpm.context.exe.variableinstance.LongInstance
java.lang.Integer org.jbpm.context.exe.converter.IntegerToLongConverter org.jbpm.context.exe.variableinstance.LongInstance
java.util.Date org.jbpm.context.exe.converter.DateToLongConverter org.jbpm.context.exe.variableinstance.LongInstance
第一列表示JAVA类型,第三列表示引擎中的类型,而中间的列表示转换器定义
转换器的概念其实非常简单,比如,对于布尔值true,在程序中是一个java.lang.Boolean的类型
的值,但在jbpm3数据库中,它是一个串"T"或者"F",那么就需要一个转换器
org.jbpm.context.exe.converter.BooleanToStringConverter来进行转换.
一个流程定义有一个TaskMgmtDefinition;一个TaskMgmtDefinition对应多个Actor,同时对应多个Task;
一个Actor有多个Task,可以从TaskMgmtDefinition中通过task的名称直接获取相应的task;
一个流程实例有一个TaskMgmtInstance;一个TaskMgmtInstance对应多个actorInstance,同时对应多个taskInstance;
一个actorInstance有多个taskInstance,可以从TaskMgmtInstance中直接获取相应的taskInstance;
一个TaskNode对例应多个Task
对于这样的流程定义:
<task-node name='a'>
<task name='laundry' />
<task name='dishes' />
<task name='change nappy' />
<transition to='b' />
</task-node>
只有当节点中的三个任务都完成后,流程才进入后面的节点
对于这样的流程定义:
<task-node name='a' signal='first'>>
<task name='laundry' />
<task name='dishes' />
<task name='change nappy' />
<transition to='b' />
</task-node>
当第一个任务完成后,token就指向后面的节点
对于这样的流程定义:
<task-node name='a' signal='never'>>
<task name='laundry' />
<task name='dishes' />
<task name='change nappy' />
<transition to='b' />
</task-node>
三个任务都完成后,token仍然不会指向后面的节点;需要自己手动调用processInstance.signal()
才会驱动流程到下面的节点
对于这样的流程定义:
<task-node name='a' signal='unsynchronized'>>
<task name='laundry' />
<task name='dishes' />
<task name='change nappy' />
<transition to='b' />
</task-node>
token不会在本节点停留,而是直接到后面的节点
jbpm用join/fork/merge/decision也可以实现同样的功能,不过它们的实现办法不同,一个是通过
NODE来实现,一个是通过TASK来实现