(转)JBPM案例详解六

一、该版本引入<fork>和<join>节点的使用!!----可以实现会签需求的功能。这里和同一个任务分配给多个参与者实现的最终功能效果是基本一样!!
 
 **作用:到达<fork>节点后,<fork>节点会进行分支,也就是让流程同时流向<fork>指定的一个或几个节点,
     注意这里是同时流向,然后等待<fork>的分支节点审批通过后,那么流程会流向<join>节点,
     等到<fork>节点所有分支均审批通过后,也就是所有分支流程都到达<join>节点后,
     流程才会流向下一个节点。
    
 **实现机制:当流程流转到<fork>节点时,token指针会停留在<fork>节点的前一个节点,
     当流程到达<fork>节点指向的节点时,会分别产生上一个token的子节点,
     分别指向<fork>节点指向的节点,然后当这些分支节点都提交后会都转向<join>节点,
     当从<join>节点流转到下一个节点时,此时指向<fork>节点的这些子节点会销毁,
     也就是说子token的生命周期是从<fork>到<join>之间,当流向<join>指向的下一个节点后,
     此时的token是原token,也就是说原token在从进入<fork>到离开<join>之间没有移动,
     移动的是子token
    
    
 **实现:不需要特殊的更改,和之前一样就行。

 

 

 一、该版本引入"子流程"<process-state>和<sub-process>节点
 (**使用这一套的时候应该特别注意,详细看一下这个项目的流程定义文件和下边的注释!!)
 
 需求:比如当我们提交给人力资源部审批的时候,人力资源部又另有一个小流程,
   就是所有提交人力资源部的审批文件,都要先经过一个小助理审批,然后决定是否交由人力资源部经理审批
   所以该人力资源部内部的这个流程就是一个子流程。
  
 **注意:当从process-state节点进入子流程的时候,他会忽略掉子流程中的start-state开始节点,
     从源码可以发现,当进入子流程之后,会直接调用subProcessInstance.signal()方法,
     这样就导致流程直接流过了start-state开始节点,所以不要在子流程的start-state开始节点分配任务。
    
 实现时注意:对于我们的业务来说可以说成是子流程,但是在程序中子流程将作为一个新的流程出现,
   也就是子流程产生的流程对象和不流程的流程对象没有太大的关系,因为我们在父流程对象中绑定设定了很多参数变量值,
   也包括请假单对象的id,因为子流程在整个处理过程中和父流程处理的方式基本一样,但是子流程作为一个新的流程对象,
   该流程对象中并没有父流程对象中已经设置存在的那些变量参数值,如果想让子流程中也具有这些变量,那么必须做一个映射,
   只需做一个配置,也就是将父流程对象中指定的变量及其值复制到子流程对象中来,
   这样子流程就可以用这些设定在父流程对象中的变量及其值。
  
 具体实现:
   <process-state name="superStateName">
    <sub-process name="子流程定义文件的名称"></sub-process>
    <variable name="父流程中变量的名字" access="read,write" mapped-name="映射到子流程中的变量名称"></variable>
    <transition name="transitionName" to="子流程完全结束后会流向这里指定的节点"></transition>
   </process-state>
   说明:access="read,write"表示在子流程中可读也可写,所谓的可写就是在子流程对象中如果改变了变量的值,
    那么父流程对象中的相应的变量的值也会跟着改变,如果没有指定为可读write,
    那么改变子流程对象中的变量值父流程对象中的值是不会改变的,当子流程结束后那么子流程对象中的变量就会销毁。
  
  
  
 **注意:使用的时候各个节点的状态可能会有点儿出路,用的时候再具体问题具体分析!!
    
 实际应用是应小心的地方:
 
     1.因为测试结果是进入子流程的第一个节点后,状态中不显示子流程的节点名称,
    而是显示<process-state name="superStateName">中的name的值,
     2.这里注意一下,还有就是我们对请假单当遇到结束节点是让请假单的状态改为了“完成审批”,
    由于子流程中的结束节点也符合这个条件,所以当子流程结束后请假单也会显示审批结束,
    而其实子流程结束后会返回父流程中中接续执行,这里应该注意一下。
  
  
 二、对于<state>和<node>节点的说明:
   <state>节点就是一个节点,当流程流转到<state>节点的时候,流程被挂起,
     此时要我们手动调用signal方法,流程才会继续向下流转,
     该节点中不能加入<task>任务,即便是加入了也不会执行。
     所以该节点的用处就是可以在节点中加入<action>事件,
     事件中必须要调用signal方法,这样才能是流程继续向下一个节点流转,
     如果不调用,那么流程将会永远停留在该节点。
    
     作用:该节点的目的就是可以在<action>事件方法中除了调用signal方法外可以加入一些自己需求所用到的其他代码(例如发邮件等操作)。
    
   <node>节点,流程流转到该节点的时候不会停留,而是直接进入该节点然后紧接着离开该节点,流向下一个节点。
  
  
 三、对于<mail-node>的说明:是用来发邮件的节点,就是说当流程流转到这里的时候,
    JBPM会根据指定的配置信息进行发邮件,发完邮件后流程继续向下进行,对于JBPM对这个节点的开发有点儿多余,
    因为发邮件的工作完全可以交给<action>来完成。
   
    使用方式:只能有一个<transition>元素
      <mail-node name="nodeName" to="#{可用变量来指定接收邮件的地址}" subject="邮件主题" text="邮件内容">
       <transition name="transitionName" to="flowToName"></transition>
      </mail-node>
     
    新版本提供的另一种方式:这种方式,首先知道<mail>不是节点,该元素可以放到<task>内部,作为<task>的一部分
     <mail actors="#{收件人地址的变量}">
      <subject>邮件主题</subject>
      <text>邮件内容</text>
     </mail>
    
   另外在jbpm.cfg.xml配置文件中要加入对邮件服务的支持:
     <string name="resource.mail.properties" value="jbpm.mail.properties">
    
     其中properties的配置方式:
      mail.host=smtp.126.com
      mail.smtp.auth=true
      mail.from.address=yourmail@126.com
      mail.smtp.user=yourmail@126.com
      mail.smtp.password=yourpassword
    
     需要注意的是,有些服务器需要发邮件人和待认证用户一致才可以发邮件
    
   注意:该发邮件的节点和元素只做了解,因为实际应用中不用这套机制,用<action>来代替,我们一样能实现一样的功能!!
  
  
 四、对于<task>元素中的<controller>元素的说明,该元素可以为包含它的任务定义任务变量,
  也就是生命周期为任务的生命周期,<controller>里面可以通过<variable>来制定变量,
  这个指定变量的方法也是将流程变量映射到任务变量
  这和<process-state>节点中使用 <sub-process>时,为子流程映射变量差不多不过有点儿不同,
  不同就是,在<variable>中可以指定一个流程变量中没有的变量,
  这种情况下会同时也在流程变量中加入一个该变量指定的变量,至于access属性指定的值,和之前子流程中的意思相同。
  "具体可以查看文档"这个用到的不是很多
 
 五、JBPM的定时器timer的说明:(了解)
 
  1.在流程定义的节点中加入声明:这里以<state>节点为例
   <state name="nodeName">
    <timer name="timerName" duedate="0 seconds" repeat="10 seconds">
     <script>System.out.println("Here");</script>
    </timer>
   </state>
   **说明:该配置将在部署流程定义文件的时候存入JBPM提供的数据库表Timer
  
  2.运行时当然要有一个线程来不是的监控JBPM的时间服务,来衡量什么时候启动定时器timer。
    JBPM的机制就是由Timer Runner来扫描Timer表,然后执行符合调度条件的Timer逻辑
    所以还要配置一个servlet到web.xml配置文件:该servlet将会产生一个Timer Runner进行监控
     <servlet>
      <servlet-name>JobExecutorServlet</servlet-name>
      <servlet-class>org.jbpm.job.executor.JobExecutorServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
     </servlet>
     <servlet-mapping>
      ......
     </servlet-mapping>
    
 六、添加自定义的节点类型:即我们可以按照我们的实际需求定义符合我们自己的实际需求的节点类型,
     因为JBPM给我们提供的那些节点类型肯定不会适应所有的实际情况。(了解)
    
     1.在org/jbpm/graph/node/node.types.xml中添加节点信息
      <node-type element="archive-node" class="org.jbpm.graph.node.ArchiveNode"></node-type>
     
     2.编写自己的实现类(继承Node类):主要覆盖read()方法和execute()方法!

 

一、该版本将完成一个比较完整的系统
 
  1.修改上传界面,此次上传一个zip文件,包括流程定义文件、流程定义图片、流程定义文件坐标文件,后两个文件对动态查看流程的状态有效!
    所以处理上传的内容也有所变动,具体看deployProcessDefinition.jsp文件
   
    上传zip的处理:
      其实上传zip包后部署的时候,JBPM首先会在jbpm_moduledefinition表中保存一个FileDefinition对象
      (如果上传的不是zip格式的那么FileDefinition对象就为空),
      也就是说这个对象封装的是zip包中的三个文件,然后JBPM会从这个对象中将一个个的文件拆分出来,
      分别都保存到jbpm_bytearray表中,而三个文件中的数据内容并没有保存在jbpm_bytearray表中,
      而是保存到了jbpm_byteblock表中,这样就完成了对zip格式的文件部署!
      (当然也会在jbpm_processdefinition表中保存流程定义文件的内容)
   
    **注意:其实流程定义文件不是必须要存储到数据库的,默认是存到数据库,如果不想存到数据库,
      那么可以在jbpm.cfg.xml配置文件中加一个配置来指定将流程定义文件保存到哪个文件夹!!
      <string name="jbpm.files.dir" value="d:/" />
      通过value指定保存的目录名称!
 
  2.我们之前的每个界面都要先通过JbpmConfiguration.getInstance().createJbpmContext()来获得JbpmContext对象,
    而且要在最后调用JbpmContext.close()方法来关闭JbpmContext对象
   
    解决麻烦:
      为了方便JBPM为我们提供了一个Filter,对于指定的映射地址的请求,
      该Servlet会通过先通过JbpmConfiguration对象产生一个JbpmContext对象,
      当请求结束后JBPM会自动关闭刚刚创建产生的JbpmContext对象,
      这样就省去我们自己createJbpmContext()了,也省去了我们自己close()了。
     
    配置方法:加入到web.xml文件(可看源代码,很简单)
      <filter>
       <filter-name>JbpmContextFilter</filter-name>
       <filter-class>org.jbpm.web.JbpmContextFilter</filter-class>
      </servlet>
      <filter-mapping>
       <filter-name>JbpmContextFilter</filter-name>
       <url-pattern>*.jsp</url-pattern>
      </filter-mapping>
      说明:这里是对所有的.jsp的请求做处理,这个映射只是为了适应我的当前项目。
       也就是当请求所有的jsp的时候JBPM会先自动创建一个JbpmContext对象,
       当jsp响应结束(可认为jsp页面关闭时)后JBPM会自动关闭JbpmContext对象
   
    获得JBPM创建好的JbpmContext对象的方法:这样就得到了JBPM自己可以维护的JbpmContext对象!
      JbpmContext jbpmContext = JbpmConfiguration.getInstance().getCurrentJbpmContext();
 
  3.因为考虑到JbpmConfiguration这个对象比较耗费资源,所以在应用关闭的时候应该把JbpmConfiguration对象同时也要关闭
 
    解决方法:为此JBPM为我们提供了一个Servlet类,该类内部非常简单(可看源码),
        只有destroy()方法中加入了对JbpmConfiguration对象的close()方法。
        所以该servlet没有必要配置映射地址,即不须指定<servlet-mapping>,
        因为只需要应用启动时同时启动该Servlet,当应用关闭是会调用Servlet的destroy()方法,
        这样就同时关闭了JbpmConfiguration对象
   
    实现方法:配置到web.xml文件
      <servlet>
       <servlet-name>CloseJbpmConfigurationServlet</servlet-name>
       <servlet-class>org.jbpm.web.CloseJbpmConfigurationServlet</servlet-class>
       <load-on-startup>1</load-on-startup>
      </servlet>
     
  4.该项目中可能不止只有一个流程定义,所以要有一个界面来显示所有不同的流程定义类别,
    也就是相当于我们办理某个流程的时候要先选择适合我们需要的流程。见listLatestProcessDefinitions.jsp文件
    通过JbpmContext.getGraphSession().findLatestProcessDefinitions()方法得到所有的不同的流程定义文件的最新版本!
   
  5.导出流程定义的功能:当显示指定的流程定义的所有版本的时候,会有一个导出的操作。此操作会要一个Servlet的支持!
    也就是当点击导出的时候去请求指定的Servlet
   
    Servlet的作用:根据流程定义的id值把流程定义取出来,然后取出每个文件(共三个),然后打包,最后下载
   
    注意:详情见ExportProcessDefinition.java和web.xml中的配置
        记住要设置响应给浏览器的内容类型response.setContentType("application/x-zip-compressed");
        还要记住设置使用下载对话框的头信息response.setHeader("Content-Disposition", "attachment;filename=\"" + zipFileName +"\"");
   
  6.加一个查看流程图(状态)的功能:还是在listProcessDefinitionVersions.jsp中加一个链接,然后请求一个Servlet。
    所以要添加一个GetProcessImageServlet.java
   
    Servlet的作用:和ExportProcessDefinition的功能差不多,只不过这里是只拿出流程定义图片,然后输出到浏览器
   
    注意:记得要设置响应给浏览器的内容类型response.setContentType("image/jpeg");
   
  7.增加删除流程定义功能,还是在listProcessDefinitionVersions.jsp中加一个链接,
    请求removeProcessDefinition.jsp页面
   
    注意:
   这里如果删除了指定的流程定义记录那么代表着JBPM也会同时删除已经通过该流程定义文件启动的所有的流程实例对象,
   所以该操作应该慎重操作,以免造成不必要的麻烦后果,按理说是如果存在以该流程定义文件启动的流程(流程对象),
   那么该流程定义文件记录就不能删除!我们可以加入一些判断来实现。
   
  8.我们现在的查看流程定义是查看所有的最新的流程定义列表,我们也可以通过一个流程定义名称来查看指定的流程定义信息,
    我们可以自己写查询语句到JBPM提供的数据库中查询。
    例如"from ProcessDefinition where name = ?"等,项目中没有做,不过我们可以自己再实现一下!
   
  9.根据流程定义查询出所有的已经存在的对应的流程实例对象列表!!还是在listProcessDefinitionVersions.jsp中加一个链接,
    具体见listProcessInstances.jsp
   
  **10.流程跟踪的实现:一定要仔细阅读ProcessImageTag、jbpm.tld和ProcessImageServlet这三个文件中的内容!!
 
     JBPM提供的支持源文件:
       可到jbpm-starters-kit-3.1.2.zip包中的文件夹中拷贝源文件到项目中,用来代替我们自己的!我们可以自己修改!
        - 从jbpm\src\java.webapp\org\jbpm\webapp\tag中拷贝ProcessImageTag.java文件
        - 从jbpm\src\resources\jbpm.war\WEB-INF中拷贝对ProcessImageTag标签类的支持配置文件jbpm.tld文件到WEB-INF目录
        - 从jbpm-starters-kit-3.1.2\jbpm\src\java.webapp\org\jbpm\webapp\servlet中拷贝ProcessImageServlet.java文件
 
     功能解释:
       就是对于没有结束的流程实例,通过流程跟踪可以查看当前流程实例的流程流转到了那个节点,
       通过流程定义图片和流程定义坐标文件实现,我们可以在图上清晰的查看到流程实例的流程状态,即实现了流程跟踪!!
      
     具体实现:
       <1>首先将拷贝的Servlet配置到项目的web.xml配置文件中,以便Servlet起作用
        <servlet>
         <servlet-name>ProcessImageServlet</servlet-name>
         <servlet-class>自己的包名.ProcessImageServlet</servlet-class>
        </servlet>
        <servlet-mapping>
         <servlet-name>ProcessImageServlet</servlet-name>
         <!--该映射地址不能变,因为这是JBPM实现时的配置,当然如果知道怎么回事可以自己修改
         不过应该还要修改ProcessImageTag和jbpm.tld文件-->
         <url-pattern>/processimage</url-pattern>
        </servlet-mapping>
       
       <2>页面中首先用<taglib>导入标签uri------taskInstanceId是当前任务就是要用红框框起来的任务所在节点!!
          然后使用<jbpm:processImage task="${taskBean.taskInstanceId}"/>就会把流程图显示在当前位置

     实现是会遇到的问题:
       <1>因为JBPM自己实现时使用了JbpmContextFilter来创建和关闭JbpmContext对象,
          所以ProcessImageTag和ProcessImageServlet中得到JbpmContext对象的方式
          都是调用的JbpmConfiguration.getInstance().getCurrentJbpmContext()方法。
         
          问题:如果我们使用了Spring和JBPM集成,那么这两个文件获得JbpmContext对象的方式都要进行修改!
         
          解决方案:将JbpmContext的获取方式做一下修改就可以!改成从Spring中获得!
         
       <2>乱码问题:因为流程定义文件不能很好的支持中文,尤其是gpd.xml文件,这个是流程定义坐标文件,
            如果流程定义文件中使用了中文,那么跟踪定位时可能会出错也可能会出乱码!
           
          例如:如果你的流程定义中用中文字符,数据库字符集为utf-8,
              并且数据库中也确实为正确的utf-8内容,也可能会乱码错误。出乱码的文件更可能是gpd.xml文件
             
       分析:
          A.可能会出现空指针异常,是因为上下文的Element root中的字符为乱码。
         - 出错的行为:result[0] = Integer.valueOf(node.attribute("x").getValue());
         - 解决方案:将值用utf-8转码!
           源码:Element rootDiagramElement = DocumentHelper.parseText(new String(gpdBytes)).getRootElement();
           修改:Element rootDiagramElement = DocumentHelper.parseText(new String(gpdBytes, "utf-8")).getRootElement();
       
       B.要注意ProcessImageTag.java文件中有一个请求ProcessImageServlet的操作并且传过去一个definitionId参数值,
        我们要注意的就是该请求是"相对路径"还是"绝对路径",这要和我们的需求的具体情况核对后然后才能修改!!
       
        注意:ProcessImageTag是一个java文件,那么从他中发送请求的相对路径又是相对与谁说的呢?
          这里我们要特别注意,因为这是一个标签类,所以肯定是在一个jsp页面中使用该标签类对应的标签,
          当标签运行的时候,该类中的代码才会运行,
          所以相当于ProcessImageTag类中的代码是在那个使用标签的jsp页面中运行一样,
          并且运行造成的结果也将会对那个使用标签的jsp页面有效,
          所以所谓的相对路径就是那个使用对应该标签类的标签的jsp页面的相对路径,
          即相对路径就是那个使用标签的jsp页面所在的路径!!(注意理解)
      
   
  11.可以加入显示任务实例对象的列表(类似显示流程实例对象列表),也就是再加一个页面,
     来根据指定的“流程名称”、“任务名字”或者“任务的参与者即actor-id”等值进行查询,当然还是到jbpm_taskinstance表中查询
     (具体我没有实现,因为不太复杂,这里提供个思路,以后用到的话应该很容易能够实现)
    
  12.我们可以通过对任务记录的create和start和end字段的值进行有价值的利用,就是我们可以对登陆人员添加"代办任务列表"和"代签收任务列表"
     所谓代办任务列表就是end字段的值为空的任务记录,代签收任务列表的意思就是create字段的值已经生成,
     而start字段中的值却没有,当点击签收的时候此时向start字段加入值也就是调用taskInstance的start()方法。
    
     具体获得数据列表的方法:我们可以通过Hibernate的hql语句自己定制需求,到JBPM提供我们的数据库表中查得能满足我们需求的记录列表
     (该项目中目前没有实现该功能,如果实现并不难)
   
  13.如果想看更具体的内部实现可以常识看源代码:
  
    我看过的类有:
       JbpmConfiguration
       JbpmContext
       ProcessDefinition
       ProcessInstance
       Services
       Service
       TaskInstance
       GraphSession
       TaskMgmtSession
       Node
       Token
       ExecutionContext
       等等……
      
    经过这十一个小项目,大家应该基本能够理解JBPM的内核实现及原理!如有发现项目中的错误或不恰当的地方欢迎留言批评。

 

posted on 2011-07-01 10:21  唐朝  阅读(833)  评论(0编辑  收藏  举报