零全零美(www.zzgwt.com)
生活中的很多事情,并不像If...Else那么简单!
posts - 96,comments - 52,trackbacks - 0
       前面我有一篇《JBPM源码解读之:Fork》,大致分析了JBPM对于Fork的实现方式,其实Fork和Join是不可分割的一对,Fork实现分拆,Join实现汇集。先让我们看一下《JBPM 3.2.3 User Guide》中关于Join的描述:
 The default join assumes that all tokens that arrive in the join are children of the same parent. This situation is created when using the fork as mentioned above and when all tokens created by a fork arrive in the same join. A join will end every token that enters the join. Then the join will examine the parent-child relation of the token that enters the join. When all sibling tokens have arrived in the join, the parent token will be propagated over the (unique!) leaving transition. When there are still sibling tokens active, the join will behave as a wait state.
     下面就让我们从JBPM源码的角度分析一下Join是如何关闭每一个到达它的Token,是如何检查各个子Token与父Token的父子关系,又是如何重新激活父Token实现流程的继续流转,更重要的也是我写这篇文章的原因:我们如何能从Join默认的实现方式中得到启发,让我们能够更好的理解JBPM整个运转流程,更好的驾驭整个项目。

 我们来看Join类的成员变量:

 1    /**
 2     * specifies wether what type of hibernate lock should be acquired. null
 3     * value defaults to LockMode.Force
 4     */

 5    String parentLockMode;
 6
 7    /**
 8     * specifies if this joinhandler is a discriminator. a descriminator
 9     * reactivates the parent when the first concurrent token enters the join.
10     * 注:
11     */

12    boolean isDiscriminator = false;
13
14    /**
15     * a fixed set of concurrent tokens.
16     */

17    Collection tokenNames = null;
18
19    /**
20     * a script that calculates concurrent tokens at runtime.
21     */

22    Script script = null;
23
24    /**
25     * reactivate the parent if the n-th token arrives in the join.
26     */

27    int nOutOfM = -1;

      parentLockMode用于控制Hibernate的锁机制,这点我们暂且不去深究,有意思的是下面几个变量:isDiscriminator、tokenNames、script、nOutOfM共同决定了Join节点内被的具体行为,本文稍后会具体解说。

1public void read(Element element, JpdlXmlReader jpdlReader) {
2        String lock = element.attributeValue("lock");
3        if ((lock != null&& (lock.equalsIgnoreCase("pessimistic"))) {
4            parentLockMode = LockMode.UPGRADE.toString();
5        }

6    }

       Join的read方法很简单,只是起到了读取JPDL中对lock的配置,并没有关心其他成员变量的初始化,这也就直接说明了isDiscriminator、tokenNames、script、nOutOfM这几个变量均属于运行期,也就是我们没有办法在配置文件里头像Fork一样配置Script,就算配了Join也不认。
 execute(ExecutionContext executionContext)方法是每个继承自Node的类的核心方法,Join类也是在这个方法中实现了Join的控制机制。在解读这个方法之前必须应该明白的一件事是:Join的execute方法就像Fork的Node-Leave 事件一样是会执行多次的,同样这取决于与Join搭配的Fork具体产生了几个子Token。
 1      //取得Token
 2        Token token = executionContext.getToken();
 3        //得到Token的isAbleToReactivateParent属性
 4        boolean isAbleToReactivateParent = token.isAbleToReactivateParent();
 5
 6        //如果当前Token没有结束,则结束该Token,到这里Fork产生的子Token的生命周期也就结束了
 7        if (!token.hasEnded()) {
 8            token.end(false);
 9        }

10        //检查该Token是否具有激活父Token的能力,如果有方法继续,如果没有方法结束。
11        if (isAbleToReactivateParent) {
12            
13        }

  Join的这种处理方式,让我们可以方便的实现一种生活中经常用到的抄送机制,例如:在某个流程中有一个审批的环节,这个审批的默认执行人为李副处长,既定情况下,如果这位李副处长审批完毕,流程就应该继续,但是现在王正处长要求所有审批过的文件都要自己亲自过目,但是只是过目,王处长的这个“过目”的行为要求并不会影响流程的运行,意思就是说,王处长完全有可能在流程都已经结束过了才去过目。稍后我会另有文章具体介绍我的使用Fork+Join实现抄送的思路。
  继续,呵呵,接下我们分析的方法都是在if (isAbleToReactivateParent)块内的,刚才我们说如果isAbleToReactivateParent == false那么整个方法就结束了。
 1            // the token arrived in the join and can only reactivate
 2            // the parent once
 3            token.setAbleToReactivateParent(false);
 4            总感觉这句是多余的,因为好像一个子Token只有一次机会进入Join节点,然后他的生命周期也就结束了,再者在execute方法的后面有
 5                    Iterator iter = parentToken.getChildren().values().iterator();
 6                    while (iter.hasNext()) {
 7                        ((Token) iter.next()).setAbleToReactivateParent(false);
 8                    }

 9            略去关于处理Hibernate锁机制的代码,因为它并不直接影响这个流程的运作.
10            
11                boolean reactivateParent = true;
12
13                // if this is a discriminator
14                if (isDiscriminator) {
15                    // reactivate the parent when the first token arrives in the
16                    // join. this must be the first token arriving because
17                    // otherwise
18                    // the isAbleToReactivateParent() of this token should have
19                    // been false
20                    // above.
21                    reactivateParent = true;
22
23                    // if a fixed set of tokenNames is specified at design
24                    // time
25                }
 else if (tokenNames != null{
26                    // check reactivation on the basis of those tokenNames
27                    reactivateParent = mustParentBeReactivated(parentToken, tokenNames.iterator());
28
29                    // if a script is specified
30                }
 else if (script != null{
31
32                    // check if the script returns a collection or a boolean
33                    Object result = null;
34                    try {
35                        result = script.eval(token);
36                    }
 catch (Exception e) {
37                        this.raiseException(e, executionContext);
38                    }

39                    // if the result is a collection
40                    if (result instanceof Collection) {
41                        // it must be a collection of tokenNames
42                        Collection runtimeTokenNames = (Collection) result;
43                        reactivateParent = mustParentBeReactivated(parentToken, runtimeTokenNames.iterator());
44
45                        // if it's a boolean
46                    }
 else if (result instanceof Boolean) {
47                        // the boolean specifies if the parent needs to be
48                        // reactivated
49                        reactivateParent = ((Boolean) result).booleanValue();
50                    }

51
52                    // if a nOutOfM is specified
53                }
 else if (nOutOfM != -1{
54
55                    int n = 0;
56                    // wheck how many tokens already arrived in the join
57                    Iterator iter = parentToken.getChildren().values().iterator();
58                    while (iter.hasNext()) {
59                        Token concurrentToken = (Token) iter.next();
60                        if (this.equals(concurrentToken.getNode())) {
61                            n++;
62                        }

63                    }

64                    if (n < nOutOfM) {
65                        reactivateParent = false;
66                    }

67
68                    // if no configuration is specified..
69                }
 else {
70                    // the default behaviour is to check all concurrent tokens
71                    // and reactivate
72                    // the parent if the last token arrives in the join
73                    reactivateParent = mustParentBeReactivated(parentToken, parentToken.getChildren().keySet().iterator());
74                }

75
76                // if the parent token needs to be reactivated from this join
77                // node
78                if (reactivateParent) {
79
80                    // write to all child tokens that the parent is already
81                    // reactivated
82                    Iterator iter = parentToken.getChildren().values().iterator();
83                    while (iter.hasNext()) {
84                        ((Token) iter.next()).setAbleToReactivateParent(false);
85                    }

86
87                    // write to all child tokens that the parent is already
88                    // reactivated
89                    ExecutionContext parentContext = new ExecutionContext(parentToken);
90                    leave(parentContext);
91                }

92                

    读了这段逻辑很少,但是注释很多的代码相信已经明白了Join内部的核心了,有一种豁然开朗的感觉,JBPM的设计思想真的很迷人:几句简单的If、Else
 就实现了让人看来很神秘的功能。这个时候我们可以来分析一下他的几个成员变量的作用了。
   如果isDiscriminator为True,这个时候Join节点其实是起到一种选择器的作用:当第一个子Token到达Join之后,Join就会马上取消其他子Token执行自身execute方法的能力,而且流程会马上继续而不会再理会其他的子Token有没有到达Join或结束,因为根据我们上面的分析,isDiscriminator被设置为false的子token是不具备激活父Token的能力的。原来实现网上经常争论的用Join实现多选一是那么简单(呵呵)!
   下一个有意思的是nOutOfM,代码写的很明白,当子Token到达Join的时候n就加1,如果n<nOutOfM Join就一个处于等待状态,直到n > nOutOfM 流程马上继续,这就Join实现多选多的机制,真的很简单,当然如果我们把nOutOfM设置为1,那么他所起到的作用就跟isDiscriminator一样了。
   在说明其他两个变量之前,让我们先来看一下public boolean mustParentBeReactivated(Token parentToken, Iterator childTokenNameIterator)方法
 1    /**
 2     * 检查有没有子Token的isAbleToReactivateParent为真,如果有一个子Token的isAbleToReactivateParent为真,就返回false
 3     */

 4    public boolean mustParentBeReactivated(Token parentToken, Iterator childTokenNameIterator) {
 5        boolean reactivateParent = true;
 6        while ((childTokenNameIterator.hasNext()) && (reactivateParent)) {
 7            String concurrentTokenName = (String) childTokenNameIterator.next();
 8
 9            Token concurrentToken = parentToken.getChild(concurrentTokenName);
10
11            if (concurrentToken.isAbleToReactivateParent()) {
12                log.debug("join will not yet reactivate parent: found concurrent token '" + concurrentToken + "'");
13                reactivateParent = false;
14            }

15        }

16        return reactivateParent;
17    }


    isDiscriminator和nOutOfM实现了多选一和多选多,但是这两个变量起到的控制作用是死的,也就是我们并不能指定从Fork那里产生的子Token中哪几个到Join之后流程可继续。例如Fork创建了A、B、C、D、E五个Token,如果用前面那两个控制机制,这五个哪几个到Join之后流程会继续完全是不可控的,如果我们要实现必须是A、C、E到达Join之后流程才可继续,这样的需求用isDiscriminator和nOutOfM是无法实现的。别着急,tokenNames是专为解决这个问题而设置的,我们选定几个子Token塞给tokenNames,那么Join就会自动为我们做这些事情了。
 相比tokenNames Script为我们提供了更灵活的控制机制。如果Script返回Collection类型,那么Script起到了tokenNames的作用,如果返回Bolean类型,那么Script起到isDiscriminator的作用,源码上注释的很清楚,我就不在这罗嗦了。
     到这Join的代码我们也就读完了,如果上述四个成员变量都为默认值,那么Join也就按默认的行为执行,Join是JBPM源码中少数注释很全的类,这也说明这是JBPM开发组的得意之作。通过四个属性我们可以非常灵活的使用Join实现很多实用的效果。

          文章原创,转载请注明出处!

 

posted on 2008-11-14 23:55 零全零美 阅读(1901) 评论(2)  编辑  收藏 所属分类: jbpm

FeedBack:
# re: [原创]JBPM源码解读之:Join[未登录]
2008-11-15 11:09 | moon
不错工作之余来逛逛居家生活网 <a href="life126.com">life126.com</a>  回复  更多评论
  
# re: [原创]JBPM源码解读之:Join[未登录]
2008-11-15 11:10 | life126.com
好文章 ,工具之余来逛逛居家生活网  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: