零全零美(www.zzgwt.com)
          生活中的很多事情,并不像If...Else那么簡(jiǎn)單!
          posts - 96,comments - 52,trackbacks - 0
                 前面我有一篇《JBPM源碼解讀之:Fork》,大致分析了JBPM對(duì)于Fork的實(shí)現(xiàn)方式,其實(shí)Fork和Join是不可分割的一對(duì),F(xiàn)ork實(shí)現(xiàn)分拆,Join實(shí)現(xiàn)匯集。先讓我們看一下《JBPM 3.2.3 User Guide》中關(guān)于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是如何關(guān)閉每一個(gè)到達(dá)它的Token,是如何檢查各個(gè)子Token與父Token的父子關(guān)系,又是如何重新激活父Token實(shí)現(xiàn)流程的繼續(xù)流轉(zhuǎn),更重要的也是我寫(xiě)這篇文章的原因:我們?nèi)绾文軓腏oin默認(rèn)的實(shí)現(xiàn)方式中得到啟發(fā),讓我們能夠更好的理解JBPM整個(gè)運(yùn)轉(zhuǎn)流程,更好的駕馭整個(gè)項(xiàng)目。

           我們來(lái)看Join類(lèi)的成員變量:

           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的鎖機(jī)制,這點(diǎn)我們暫且不去深究,有意思的是下面幾個(gè)變量:isDiscriminator、tokenNames、script、nOutOfM共同決定了Join節(jié)點(diǎn)內(nèi)被的具體行為,本文稍后會(huì)具體解說(shuō)。

          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方法很簡(jiǎn)單,只是起到了讀取JPDL中對(duì)lock的配置,并沒(méi)有關(guān)心其他成員變量的初始化,這也就直接說(shuō)明了isDiscriminator、tokenNames、script、nOutOfM這幾個(gè)變量均屬于運(yùn)行期,也就是我們沒(méi)有辦法在配置文件里頭像Fork一樣配置Script,就算配了Join也不認(rèn)。
           execute(ExecutionContext executionContext)方法是每個(gè)繼承自Node的類(lèi)的核心方法,Join類(lèi)也是在這個(gè)方法中實(shí)現(xiàn)了Join的控制機(jī)制。在解讀這個(gè)方法之前必須應(yīng)該明白的一件事是:Join的execute方法就像Fork的Node-Leave 事件一樣是會(huì)執(zhí)行多次的,同樣這取決于與Join搭配的Fork具體產(chǎn)生了幾個(gè)子Token。
           1      //取得Token
           2        Token token = executionContext.getToken();
           3        //得到Token的isAbleToReactivateParent屬性
           4        boolean isAbleToReactivateParent = token.isAbleToReactivateParent();
           5
           6        //如果當(dāng)前Token沒(méi)有結(jié)束,則結(jié)束該Token,到這里Fork產(chǎn)生的子Token的生命周期也就結(jié)束了
           7        if (!token.hasEnded()) {
           8            token.end(false);
           9        }

          10        //檢查該Token是否具有激活父Token的能力,如果有方法繼續(xù),如果沒(méi)有方法結(jié)束。
          11        if (isAbleToReactivateParent) {
          12            
          13        }

            Join的這種處理方式,讓我們可以方便的實(shí)現(xiàn)一種生活中經(jīng)常用到的抄送機(jī)制,例如:在某個(gè)流程中有一個(gè)審批的環(huán)節(jié),這個(gè)審批的默認(rèn)執(zhí)行人為李副處長(zhǎng),既定情況下,如果這位李副處長(zhǎng)審批完畢,流程就應(yīng)該繼續(xù),但是現(xiàn)在王正處長(zhǎng)要求所有審批過(guò)的文件都要自己親自過(guò)目,但是只是過(guò)目,王處長(zhǎng)的這個(gè)“過(guò)目”的行為要求并不會(huì)影響流程的運(yùn)行,意思就是說(shuō),王處長(zhǎng)完全有可能在流程都已經(jīng)結(jié)束過(guò)了才去過(guò)目。稍后我會(huì)另有文章具體介紹我的使用Fork+Join實(shí)現(xiàn)抄送的思路。
            繼續(xù),呵呵,接下我們分析的方法都是在if (isAbleToReactivateParent)塊內(nèi)的,剛才我們說(shuō)如果isAbleToReactivateParent == false那么整個(gè)方法就結(jié)束了。
           1            // the token arrived in the join and can only reactivate
           2            // the parent once
           3            token.setAbleToReactivateParent(false);
           4            總感覺(jué)這句是多余的,因?yàn)楹孟褚粋€(gè)子Token只有一次機(jī)會(huì)進(jìn)入Join節(jié)點(diǎn),然后他的生命周期也就結(jié)束了,再者在execute方法的后面有
           5                    Iterator iter = parentToken.getChildren().values().iterator();
           6                    while (iter.hasNext()) {
           7                        ((Token) iter.next()).setAbleToReactivateParent(false);
           8                    }

           9            略去關(guān)于處理Hibernate鎖機(jī)制的代碼,因?yàn)樗⒉恢苯佑绊戇@個(gè)流程的運(yùn)作.
          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                

              讀了這段邏輯很少,但是注釋很多的代碼相信已經(jīng)明白了Join內(nèi)部的核心了,有一種豁然開(kāi)朗的感覺(jué),JBPM的設(shè)計(jì)思想真的很迷人:幾句簡(jiǎn)單的If、Else
           就實(shí)現(xiàn)了讓人看來(lái)很神秘的功能。這個(gè)時(shí)候我們可以來(lái)分析一下他的幾個(gè)成員變量的作用了。
             如果isDiscriminator為T(mén)rue,這個(gè)時(shí)候Join節(jié)點(diǎn)其實(shí)是起到一種選擇器的作用:當(dāng)?shù)谝粋€(gè)子Token到達(dá)Join之后,Join就會(huì)馬上取消其他子Token執(zhí)行自身execute方法的能力,而且流程會(huì)馬上繼續(xù)而不會(huì)再理會(huì)其他的子Token有沒(méi)有到達(dá)Join或結(jié)束,因?yàn)楦鶕?jù)我們上面的分析,isDiscriminator被設(shè)置為false的子token是不具備激活父Token的能力的。原來(lái)實(shí)現(xiàn)網(wǎng)上經(jīng)常爭(zhēng)論的用Join實(shí)現(xiàn)多選一是那么簡(jiǎn)單(呵呵)!
             下一個(gè)有意思的是nOutOfM,代碼寫(xiě)的很明白,當(dāng)子Token到達(dá)Join的時(shí)候n就加1,如果n<nOutOfM Join就一個(gè)處于等待狀態(tài),直到n > nOutOfM 流程馬上繼續(xù),這就Join實(shí)現(xiàn)多選多的機(jī)制,真的很簡(jiǎn)單,當(dāng)然如果我們把nOutOfM設(shè)置為1,那么他所起到的作用就跟isDiscriminator一樣了。
             在說(shuō)明其他兩個(gè)變量之前,讓我們先來(lái)看一下public boolean mustParentBeReactivated(Token parentToken, Iterator childTokenNameIterator)方法
           1    /**
           2     * 檢查有沒(méi)有子Token的isAbleToReactivateParent為真,如果有一個(gè)子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實(shí)現(xiàn)了多選一和多選多,但是這兩個(gè)變量起到的控制作用是死的,也就是我們并不能指定從Fork那里產(chǎn)生的子Token中哪幾個(gè)到Join之后流程可繼續(xù)。例如Fork創(chuàng)建了A、B、C、D、E五個(gè)Token,如果用前面那兩個(gè)控制機(jī)制,這五個(gè)哪幾個(gè)到Join之后流程會(huì)繼續(xù)完全是不可控的,如果我們要實(shí)現(xiàn)必須是A、C、E到達(dá)Join之后流程才可繼續(xù),這樣的需求用isDiscriminator和nOutOfM是無(wú)法實(shí)現(xiàn)的。別著急,tokenNames是專(zhuān)為解決這個(gè)問(wèn)題而設(shè)置的,我們選定幾個(gè)子Token塞給tokenNames,那么Join就會(huì)自動(dòng)為我們做這些事情了。
           相比tokenNames Script為我們提供了更靈活的控制機(jī)制。如果Script返回Collection類(lèi)型,那么Script起到了tokenNames的作用,如果返回Bolean類(lèi)型,那么Script起到isDiscriminator的作用,源碼上注釋的很清楚,我就不在這羅嗦了。
               到這Join的代碼我們也就讀完了,如果上述四個(gè)成員變量都為默認(rèn)值,那么Join也就按默認(rèn)的行為執(zhí)行,Join是JBPM源碼中少數(shù)注釋很全的類(lèi),這也說(shuō)明這是JBPM開(kāi)發(fā)組的得意之作。通過(guò)四個(gè)屬性我們可以非常靈活的使用Join實(shí)現(xiàn)很多實(shí)用的效果。

                    文章原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處!

           

          posted on 2008-11-14 23:55 零全零美 閱讀(1931) 評(píng)論(2)  編輯  收藏 所屬分類(lèi): jbpm

          FeedBack:
          # re: [原創(chuàng)]JBPM源碼解讀之:Join[未登錄](méi)
          2008-11-15 11:09 | moon
          不錯(cuò)工作之余來(lái)逛逛居家生活網(wǎng) <a href="life126.com">life126.com</a>  回復(fù)  更多評(píng)論
            
          # re: [原創(chuàng)]JBPM源碼解讀之:Join[未登錄](méi)
          2008-11-15 11:10 | life126.com
          好文章 ,工具之余來(lái)逛逛居家生活網(wǎng)  回復(fù)  更多評(píng)論
            
          主站蜘蛛池模板: 新郑市| 顺义区| 霍山县| 荣昌县| 崇左市| 凌海市| 沾化县| 岚皋县| 寿宁县| 绵竹市| 皮山县| 凌海市| 平乐县| 北流市| 镇安县| 荔波县| 延川县| 绥阳县| 隆德县| 岚皋县| 米林县| 双柏县| 青川县| 来安县| 东海县| 大宁县| 天津市| 卢龙县| 清镇市| 安徽省| 曲周县| 新邵县| 白城市| 彭山县| 东丰县| 原阳县| 康马县| 武清区| 淳安县| 宜黄县| 玛曲县|