在用戶修改了領(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。
ActionListener:TopLink的SessionListener,每次會話都會調(diào)用。我們在這里實現(xiàn)了preCommit方法,在UnitOfWork提交之前,捕捉用戶的所有修改,并從中選取出用戶所關(guān)心的對象的變動。
ActionService:當ActionListener從TopLink中獲得到改動的對象,就會調(diào)用ActionService生成ActionRecord,并通知相關(guān)的Recorder,可能是Log到DB。如果用戶是通過主動的方式傳入新老兩個對象就不需要Listener,直接調(diào)用ActionService,將新老對象或者新對象和ValueDistiller作為參數(shù)傳入,
ValueDistiler:根據(jù)當前的新對象,萃取出老對象。TopLink就可以根據(jù)當前UnitOfWork中的新對象獲取原始對象。方法是:
public Object getOriginalVersionOfObject(Object workingClone) |
Expression:ActionMemed相關(guān)的配置數(shù)據(jù),由ExpressionParser解析出來后就會cache在內(nèi)存中。這個配置可以是文件,或者DB配置。只要能描述清楚就行。文件配置我們直接利用spring bean。
ActionConstructor:Listener從TopLink ChangeSet中拿到的只是有改動的對象。而我們關(guān)心的只是對象上某個Field或者它引用的某個對象的Field,比如說Employee有PhoneNumber List,PhoneNumber有個屬性是areaCode,可能我們只關(guān)心areaCode值的更改,就只需要記錄areaCode的更改,并且通知客戶。所以我們需要根據(jù)用戶配置對新老對象進行對比,比較是否有關(guān)注的屬性被用戶更改了。并且構(gòu)建ActionRecord。比較的方法我們可以用JXpath, Xpath的表達能力很強,而且還可以自定義函數(shù),在自定義擴展函數(shù)里用戶可以對字段進行組合處理,從而生成它們自己想要記錄的值。
ActionRecorder:當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)圖進行比較會發(fā)現(xiàn)有兩處不同:
ServiceTask: 實現(xiàn)Java Runnable接口,基本實現(xiàn)類似于先前圖中的ActionService。
ObjectCloner: 如果我們使用TopLink,在異步的情況下,用戶當前的UnitOfWork(事務(wù))會先提交,提交之后,從TopLink中萃取的舊對象會被Merge成新對象,這時我們只能提前在UnitOfWork提交之前自己根據(jù)Expression的結(jié)構(gòu)深Copy一份出來。
ActionAsyncService: 為異步設(shè)計的ActionService,利用ValueDistiller從UnitOfwork獲得當前對象的原始clone,構(gòu)建ServiceTask,將ServiceTask提交到ThreadPool,當task被執(zhí)行時,就會調(diào)用ActionService,這時的ActionService重用了同步流程中的ActonService。
幾個注意點:
整個Framework的原理還是相當簡單的,稍微值得注意的可能是下面幾個方面。
Listener如何獲取被改動的對象
TopLink會把所有改動過的對象都會被放在UnitOfWorkChangeSet中,因為在UnitOfWork提交的時候它需要將UnitOfWorkChangeSet中記下的改動提交到數(shù)據(jù)庫。然后merge到session cache。所以所有改動從UnitOfWork中都是可以拿到的。
public class ActionLogPassiveAsyncListener extends AbstractActionLogAsyncListener { private ValueDistiller distiller; public void preCommitUnitOfWork(SessionEvent sessionevent) { log.debug("preCommitUnitOfWork begin."); 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 Application的transaction,這樣他們會一起成功或者失敗。但是異步情況下,就會是不同的事務(wù),兩個事務(wù)之間的關(guān)系可能是有先后順序或者互不相干。互不相干是不可能的,從業(yè)務(wù)意義上講只有Application的改動確實生效之后ActionRecord才能生成,但是將ActionRecord放在Application事務(wù)提交成功之后生成或者提交,也會面臨一個問題,就是application成功提交了,但ActionRecord的生成可能會失敗。但要知道ActionRecord失敗的幾率遠比Application提交失敗的幾率要小得多,application常常會因為樂觀鎖的問題而提交失敗,但ActionRecord只可能因為DB Shutdown而丟失數(shù)據(jù)。失敗后會做詳細的備份,以便做恢復(fù)。那如何感知application事務(wù)是提交成功還是失敗了呢?TopLink的SessionEventListener有四個有用的回調(diào)方法:PreCommit,PostCommit,PostRollback,PostRelease,用戶事務(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) { 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進行深copy的性能很差。5000個對象花了我20s。首先要說的是其實不需要所有對象引用都需要深copy,只有那些用戶對關(guān)注的對象屬性才需要深copy,clone的步驟大概如下:
l 對根對象進行淺copy
l 對用戶關(guā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)存中,直接利用toplink的clone邏輯就可以了。1000個對象深clone一把大約是120ms。