<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    走自己的路

    路漫漫其修遠(yuǎn)兮,吾將上下而求索

      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      50 隨筆 :: 4 文章 :: 118 評論 :: 0 Trackbacks
     

    在用戶修改了領(lǐng)域?qū)ο蟮闹岛螅覀冇袝r需要記錄下用戶的改動。比如對一些關(guān)鍵業(yè)務(wù)對象的改動有時往往需要發(fā)郵件通知客戶。有時用戶可能想查閱所有歷史的改動,甚至有可能會改回原先的值。

    領(lǐng)域邏輯關(guān)系往往比較復(fù)雜,這時我們會使用到ORM Framework。本文以toplink為例,講述如何利用toplink編寫一個完成此功能的簡易Framework,我們暫且把它稱為ActionMemed。

    我們先來看一下大體的流程:

    l           我們獲得用戶修改信息通常有兩種方式,一種是被動的監(jiān)聽,另一種主動的通知。被動的監(jiān)聽就是framework訂閱所關(guān)心領(lǐng)域?qū)ο蟮男薷?,主動通知?/span>application主動的將修改之前和之后的對象通知framework。

    l           Framework接著從整個對象的樹結(jié)構(gòu)中找出用戶所關(guān)心的某個特定的字段或者字段的組合,生成actionRecord。ActionRecord是描述用戶對領(lǐng)域?qū)ο笮薷牡臄?shù)據(jù)結(jié)構(gòu),會包括用戶修改的原因,修改者,修改的時間,修改的字段或者組合,修改前后的值等等信息。

    l           ActionRecord生成好之后,會將它記錄到DB,發(fā)郵件通知用戶或者通過JMS通知其他Application

    有了基本的概念后,看一下整體的結(jié)構(gòu):


     

    Registry: TopLink上注冊ActionListener。一旦在TopLink上檢測到業(yè)務(wù)對象的改動就會調(diào)用ActionService,生成ActionRecord并調(diào)用相關(guān)的ActionRecorder。

    ActionListenerTopLinkSessionListener,每次會話都會調(diào)用。我們在這里實現(xiàn)了preCommit方法,在UnitOfWork提交之前,捕捉用戶的所有修改,并從中選取出用戶所關(guān)心的對象的變動。

    ActionService:當(dāng)ActionListenerTopLink中獲得到改動的對象,就會調(diào)用ActionService生成ActionRecord,并通知相關(guān)的Recorder,可能是LogDB。如果用戶是通過主動的方式傳入新老兩個對象就不需要Listener,直接調(diào)用ActionService,將新老對象或者新對象和ValueDistiller作為參數(shù)傳入,

    ValueDistiler:根據(jù)當(dāng)前的新對象,萃取出老對象。TopLink就可以根據(jù)當(dāng)前UnitOfWork中的新對象獲取原始對象。方法是:

    public Object getOriginalVersionOfObject(Object workingClone)

     

    ExpressionActionMemed相關(guān)的配置數(shù)據(jù),由ExpressionParser解析出來后就會cache在內(nèi)存中。這個配置可以是文件,或者DB配置。只要能描述清楚就行。文件配置我們直接利用spring bean。

    ActionConstructor:ListenerTopLink ChangeSet中拿到的只是有改動的對象。而我們關(guān)心的只是對象上某個Field或者它引用的某個對象的Field,比如說EmployeePhoneNumber ListPhoneNumber有個屬性是areaCode,可能我們只關(guān)心areaCode值的更改,就只需要記錄areaCode的更改,并且通知客戶。所以我們需要根據(jù)用戶配置對新老對象進(jìn)行對比,比較是否有關(guān)注的屬性被用戶更改了。并且構(gòu)建ActionRecord。比較的方法我們可以用JXpath, Xpath的表達(dá)能力很強(qiáng),而且還可以自定義函數(shù),在自定義擴(kuò)展函數(shù)里用戶可以對字段進(jìn)行組合處理,從而生成它們自己想要記錄的值。

    ActionRecorder:當(dāng)Action構(gòu)建完成后,ActionRecorder就要將它通知客戶,用JMS發(fā)給其他項目或者記錄到DB。用戶可以配置多個ActionRecorder。

    MemedEventListener,讓用戶在ActionRecorder調(diào)用之前和之后做一些額外的處理。比如說用戶可能在之前對Action的數(shù)據(jù)結(jié)構(gòu)加入一些定制信息。

     

    上面介紹了ActionMemed的流程和相關(guān)模塊的功能。其實在使用中,特別是一次修改很多業(yè)務(wù)對象的時候,處理Action時間會有點長,況且Action的處理也并不需要實時。所以Action還需要提供異步處理的功能。



     

    將異步調(diào)用的模塊圖和先前的結(jié)構(gòu)圖進(jìn)行比較會發(fā)現(xiàn)有兩處不同:

    ServiceTask: 實現(xiàn)Java Runnable接口,基本實現(xiàn)類似于先前圖中的ActionService

     

    ObjectCloner: 如果我們使用TopLink,在異步的情況下,用戶當(dāng)前的UnitOfWork(事務(wù))會先提交,提交之后,從TopLink中萃取的舊對象會被Merge成新對象,這時我們只能提前在UnitOfWork提交之前自己根據(jù)Expression的結(jié)構(gòu)深Copy一份出來。

     

    ActionAsyncService: 為異步設(shè)計的ActionService,利用ValueDistillerUnitOfwork獲得當(dāng)前對象的原始clone,構(gòu)建ServiceTask,將ServiceTask提交到ThreadPool,當(dāng)task被執(zhí)行時,就會調(diào)用ActionService,這時的ActionService重用了同步流程中的ActonService

     

    幾個注意點:

    整個Framework的原理還是相當(dāng)簡單的,稍微值得注意的可能是下面幾個方面。

    Listener如何獲取被改動的對象

    TopLink會把所有改動過的對象都會被放在UnitOfWorkChangeSet中,因為在UnitOfWork提交的時候它需要將UnitOfWorkChangeSet中記下的改動提交到數(shù)據(jù)庫。然后mergesession cache。所以所有改動從UnitOfWork中都是可以拿到的。

    public class ActionLogPassiveAsyncListener extends AbstractActionLogAsyncListener {

        private ValueDistiller distiller;

     

        public void preCommitUnitOfWork(SessionEvent sessionevent) {

            log.debug("preCommitUnitOfWork begin.");

            UnitOfWork unitOfWork = (UnitOfWork) sessionevent.getSession();

     

            if (null == unitOfWork.getUnitOfWorkChangeSet()) {

             unitOfWork.setUnitOfWorkChangeSet((oracle.toplink.internal.sessions.UnitOfWorkChangeSet) unitOfWork

                        .getCurrentChanges());

            }

            UnitOfWorkChangeSet ucs = unitOfWork.getUnitOfWorkChangeSet();

            if (ucs != null && ucs.getAllChangeSets() != null) {

                Set finishedObjects = new HashSet();

                Map<Class<?>, List<ChangedPair>> changedPairs = new HashMap<Class<?>, List<ChangedPair>>();

     

                for (Enumeration objectChangeSetEnum = ucs.getAllChangeSets().keys(); objectChangeSetEnum

                        .hasMoreElements();) {

                    ObjectChangeSet objectChangeSet = (ObjectChangeSet) objectChangeSetEnum.nextElement();

                    if (objectChangeSet == null) {

                        continue;

                    }

                    Object clone = objectChangeSet.getUnitOfWorkClone();

                    if (!finishedObjects.contains(clone)) {

                        for (Class focusClass : this.focusClasses) {

                            if ((includeSubclass ? focusClass.isAssignableFrom(clone.getClass())

                                    : clone.getClass() == focusClass)

                                    && (filter == null || (filter != null && !filter.isFiltered(clone, unitOfWork)))) {

                                finishedObjects.add(clone);

                                if (objectChangeSet.hasChanges()) {

                                    List<ChangedPair> changedPairList = changedPairs.get(focusClass);

                                    if (null == changedPairList) {

                                        changedPairList = new ArrayList<ChangedPair>();

                                        changedPairs.put(focusClass, changedPairList);

                                    }

                                    Object originalObject = this.distiller.getOriginObject(clone);

     

                                    changedPairList.add(new ChangedPair(originalObject, clone));

     

                                }

     

                            }

                        }

                    }

                }

                if (!changedPairs.isEmpty()) {

                    try {

                        ChangedPairMap changedPairMap = this.assembleChangedPairMap(unitOfWork, changedPairs);

                        this.changedPairCache.set(changedPairMap);

                    } catch (ActionLogException e) {

                        if (shouldBreakIfException) {

                            throw new ActionLogRuntimeException(e);

                        }

                    }

                }

                log.debug("preCommitUnitOfWork end.");

            }

        }

    }

     

    異步狀態(tài)下,ActionRecord要在Application事務(wù)提交之后生成

    同步狀態(tài)下,ActionRecord的生成可以Join Applicationtransaction,這樣他們會一起成功或者失敗。但是異步情況下,就會是不同的事務(wù),兩個事務(wù)之間的關(guān)系可能是有先后順序或者互不相干。互不相干是不可能的,從業(yè)務(wù)意義上講只有Application的改動確實生效之后ActionRecord才能生成,但是將ActionRecord放在Application事務(wù)提交成功之后生成或者提交,也會面臨一個問題,就是application成功提交了,但ActionRecord的生成可能會失敗。但要知道ActionRecord失敗的幾率遠(yuǎn)比Application提交失敗的幾率要小得多,application常常會因為樂觀鎖的問題而提交失敗,但ActionRecord只可能因為DB Shutdown而丟失數(shù)據(jù)。失敗后會做詳細(xì)的備份,以便做恢復(fù)。那如何感知application事務(wù)是提交成功還是失敗了呢?TopLinkSessionEventListener有四個有用的回調(diào)方法:PreCommitPostCommit,PostRollbackPostRelease,用戶事務(wù)提交的時候在提交之前會調(diào)用PreCommit方法,這時我們還可以從UnitOfWork中獲取新老對象,我們會把老對象深clone一份出來,將他們存放在ThreadLocal中,而在PostCommit回調(diào)的實現(xiàn)中,我們會從ThreadLocal中取出新老對象完成ActionRecord的生成,而PostRollback就可以什么都不干了。但不管是提交成功還是提交失敗Rlease方法都會被調(diào)用,UnitOfWork需要release,這里我們就會去清空ThreadLocal,以便內(nèi)存即時的垃圾回收。這樣說來即使是主動調(diào)用ActionAsyncService也會注冊一個Listener,不同的是這個Listener不需要從UnitOfWork檢測變化。

    public class AbstractActionAsyncListener extends AbstractActionListener {

        protected ThreadLocal<ChangedPairMap> changedPairCache = new ThreadLocal<ChangedPairMap>();

       

        public void postCommitUnitOfWork(SessionEvent arg0) {

            ChangedPairMap changedPairMap = this.changedPairCache.get();

            if (changedPairMap != null) {

                if (!changedPairMap.isEmpty()) {

    //異步生成ActionRecord

                }

            }

        }

     

        public void postReleaseUnitOfWork(SessionEvent arg0) {

            if (this.changedPairCache.get() != null) {

                this.clearResource();

            }

        }

     

        private void clearResource() {

            this.changedPairCache.set(null);

    }

    }

     

     

    public class ActionActiveAsyncListener extends AbstractActionAsyncListener {

        private Map<Class<?>, List<ChangedPair>> changedPairs;

     

        public void preCommitUnitOfWork(SessionEvent sessionEvent) {

            UnitOfWork unitOfWork = (UnitOfWork) sessionEvent.getSession();

            try {

                ChangedPairMap changedPairMap = this.assembleChangedPairMap(unitOfWork, this.changedPairs);

                this.changedPairCache.set(changedPairMap);

            } catch (ActionLogException e) {

                log.error("Assemble ChangePairMap fails! ChangedPairs: " + changedPairs.toString(), e);

                if (shouldBreakIfException) {

                    throw new ActionRuntimeException(e);

                }

            }

        }

    }

     

     

    ObjectCloner

    如果對象樹的結(jié)構(gòu)很龐大,深copy的性能代價不得不考慮。BeanUtils進(jìn)行深copy的性能很差。5000個對象花了我20s。首先要說的是其實不需要所有對象引用都需要深copy,只有那些用戶對關(guān)注的對象屬性才需要深copy,clone的步驟大概如下:

    l           對根對象進(jìn)行淺copy

    l           對用戶關(guān)心的對象屬性迭代的進(jìn)行深copy

    l           如果關(guān)心的對象屬性是Collection,淺copy Collection中的每個對象并深copy對象中用戶關(guān)注的對象屬性

    l           其實那些domain class,早在做ORM的時就確定下來了,所以所有domain對象反射metadata都可以事先確定,存在內(nèi)存中,這樣會大大提高性能,其實toplink也會把這些反射結(jié)構(gòu)解析出來后緩存在內(nèi)存中,直接利用toplinkclone邏輯就可以了。1000個對象深clone一把大約是120ms




    評論

    # re: 偵聽和處理用戶對業(yè)務(wù)對象改動的簡易框架 2009-12-01 12:13 創(chuàng)意產(chǎn)品
    非常好的文章,謝謝樓主分享!  回復(fù)  更多評論
      

    # re: 偵聽和處理用戶對業(yè)務(wù)對象改動的簡易框架 2010-03-01 10:47 游客
    很好。公司的team也開發(fā)了記錄ActionLog的組件。不過在獲取改變集和比較對象上代價比較高。正好參考  回復(fù)  更多評論
      

    主站蜘蛛池模板: 亚洲中文字幕久在线| 国产V亚洲V天堂无码| 亚洲色偷偷综合亚洲AV伊人| 一本色道久久综合亚洲精品高清 | 我的小后妈韩剧在线看免费高清版| 免费观看激色视频网站(性色) | 99久久国产免费中文无字幕| 亚洲高清视频免费| 午夜寂寞在线一级观看免费| 红杏亚洲影院一区二区三区| 亚洲黄色在线观看网站| 亚洲国产精品网站在线播放 | 手机看片久久国产免费| 中文字幕亚洲一区二区三区| 亚洲高清中文字幕综合网| 亚洲中文字幕久久精品无码VA| 阿v免费在线观看| 国产婷婷成人久久Av免费高清| 波多野结衣中文字幕免费视频| 国产成人涩涩涩视频在线观看免费| 亚洲中文字幕无码久久精品1| 亚洲精品在线免费观看视频| 亚洲AV永久无码天堂影院 | 国产成人精品久久亚洲高清不卡| 中文字幕看片在线a免费| 国产成人精品免费视频网页大全| 免费国产在线观看老王影院| 日产亚洲一区二区三区| 毛片亚洲AV无码精品国产午夜| 日韩免费观看一区| 好爽好紧好大的免费视频国产| 婷婷久久久亚洲欧洲日产国码AV| 亚洲乱码日产精品一二三| 欧洲人免费视频网站在线| 国产免费人成视频在线观看 | 亚洲中文字幕久久精品无码APP| 亚洲人成高清在线播放| 久久国产美女免费观看精品| 大地资源在线观看免费高清| 国产AV无码专区亚洲AV手机麻豆| 亚洲一级毛片免费观看|