Sung in Blog

                     一些技術(shù)文章 & 一些生活雜碎
          spring是支持控制反轉(zhuǎn)編程機(jī)制的一個(gè)相對(duì)新的框架。本文把spring作為簡(jiǎn)單工作流引擎,將它用在了更加通用的地方。在對(duì)工作流簡(jiǎn)單介紹之后,將要介紹在基本工作流場(chǎng)景中基于Spring的工作流API的使用。

            許多J2EE應(yīng)用程序要求在一個(gè)和主機(jī)分離的上下文中執(zhí)行處理過程。在許多情況下,這些后臺(tái)的進(jìn)程執(zhí)行多個(gè)任務(wù),一些任務(wù)依賴于以前任務(wù)的狀態(tài)。由于這些處理任務(wù)之間存在相互依賴的關(guān)系,使用一套基于過程的方法調(diào)用常常不能滿足要求。開發(fā)人員能夠利用Spring來容易地將后臺(tái)進(jìn)程分離成活動(dòng)的集合。Spring容器連接這些活動(dòng),并將它們組織成簡(jiǎn)單的工作流。

            在本文中,簡(jiǎn)單工作流被定義成不需要用戶干預(yù),以一定順序執(zhí)行的任意活動(dòng)的集合。然而,我們并不建議將這種方式代替存在的工作流框架。在一些場(chǎng)景中,需要更多的用戶交互,例如基于用戶輸入而進(jìn)行的轉(zhuǎn)向,連接或傳輸,這時(shí),比較好的方法是配用一個(gè)單獨(dú)的開源或者商業(yè)的工作流引擎。一個(gè)開源項(xiàng)目已經(jīng)成功地將更復(fù)雜的工作流設(shè)計(jì)集成到spring中。

            如果你手上的工作流任務(wù)是簡(jiǎn)單的,那么,與功能完備的獨(dú)立工作流框架相比,簡(jiǎn)單工作流的策略就會(huì)變得有意義,特別地,如果已經(jīng)使用了spring,這種快速實(shí)現(xiàn)可以保證時(shí)間不會(huì)變得更加漫長(zhǎng)。此外,考慮到spring輕量級(jí)的控制反轉(zhuǎn)容器的特點(diǎn),spring在資源負(fù)載上減少了資源負(fù)載。

            這篇文章簡(jiǎn)短地從編程主題的角度介紹工作流。通過使用工作流的概念,spring被用來作為驅(qū)動(dòng)工作流引擎的框架。然后,討論了生產(chǎn)部署選項(xiàng)。現(xiàn)在,讓我們從工作流的設(shè)計(jì)模式和相關(guān)背景信息來介紹簡(jiǎn)單工作流的思想吧。

            簡(jiǎn)單工作流

            工作流模型是一個(gè)早在70年代就有人開始研究的主題,許多開發(fā)者都試圖創(chuàng)建工作流模型規(guī)范。W.H.M. van der Aalst等人寫了《工作流模型》白皮書(2003年7月),它成功地提煉出一組設(shè)計(jì)模式,這些設(shè)計(jì)模式準(zhǔn)確地將大多數(shù)通用的工作流場(chǎng)景建模。當(dāng)中,最普通的工作流模式是順序模式 (Sequence pattern)。順序工作流模式滿足了簡(jiǎn)單工作流的設(shè)計(jì)原則,并且由一組順序執(zhí)行的活動(dòng)組成。

            UML(統(tǒng)一建模語言)活動(dòng)圖通常被用來作為一個(gè)機(jī)制對(duì)工作流建模。圖1顯示了一個(gè)基本的使用標(biāo)準(zhǔn)UML活動(dòng)圖對(duì)順序工作流過程的建模過程。

          圖 1順序工作流模式

            順序工作流是一個(gè)在J2EE中流行的標(biāo)準(zhǔn)工作流模式。J2EE應(yīng)用程序在后臺(tái)線程中,通常需要一些順序發(fā)生的事件或者異步事件。圖2中的活動(dòng)圖描述了一個(gè)簡(jiǎn)單的工作流,用來通知感興趣的旅行者,他們感興趣的目的地的機(jī)票價(jià)格已經(jīng)下降的事件。

          圖 2.機(jī)票價(jià)格下降的簡(jiǎn)單工作流
          圖1中的航線工作流負(fù)責(zé)創(chuàng)建和發(fā)送動(dòng)態(tài)的email通知。過程中的每一步表示了一個(gè)活動(dòng)(activity)。在工作流處于活動(dòng)之前,一些額外事件必須發(fā)生。在這個(gè)例子中,事件是飛行路線費(fèi)率的減少。

            讓我們來簡(jiǎn)要的看一下航線工作流的業(yè)務(wù)邏輯。如果第一個(gè)活動(dòng)找不到對(duì)費(fèi)率減少通知感興趣的用戶,那么整個(gè)工作流就被取消。如果發(fā)現(xiàn)了感興趣的用戶,那么接下來的活動(dòng)繼續(xù)執(zhí)行。隨后,一個(gè)XSL(擴(kuò)展樣式表)轉(zhuǎn)換生成消息內(nèi)容,之后,記錄審計(jì)信息 (audit information)。最后,工作流試圖通過SMTP服務(wù)器發(fā)送這個(gè)消息。如果這個(gè)任務(wù)沒有錯(cuò)誤地完成,便在日志中記錄成功的信息,進(jìn)程結(jié)束。但是,如果在和SMTP服務(wù)器通訊時(shí)發(fā)生了錯(cuò)誤,一個(gè)特別的錯(cuò)誤處理例程將要管理這些錯(cuò)誤。錯(cuò)誤處理代碼將會(huì)試著去重新發(fā)送消息。

            考慮這個(gè)航線的例子,一個(gè)明顯的問題是:你怎么樣有效地將順序處理過程分解為單獨(dú)的活動(dòng)?這個(gè)問題被spring巧妙的處理了。下面,讓我們快速地討論spring的反轉(zhuǎn)控制框架。

            控制反轉(zhuǎn)

            Spring通過使用spring容器來負(fù)責(zé)控制對(duì)象之間的依賴關(guān)系,使得我們不再對(duì)對(duì)象之間的依賴負(fù)責(zé)。 這種依賴關(guān)系的實(shí)現(xiàn)就是大家所知道的控制反轉(zhuǎn)(IoC)或依賴注射。參見Martin Fowler's "Inversion of Control Containers and the Dependency Injection Pattern"(martinfowler.com, 2004年2月)得到關(guān)于控制反轉(zhuǎn)和依賴注射的更加深入的討論。通過管理對(duì)象之間的依賴關(guān)系,spring就不需要那些只是為了使類能夠相互協(xié)作,而將對(duì)象粘合的代碼。

            作為spring beans的工作流組件

            在進(jìn)一步討論之前,現(xiàn)在是簡(jiǎn)要介紹spring中主要概念的恰當(dāng)時(shí)候。接口ApplicationContext是從接口BeanFactory繼承的,它被用來作為在spring容器內(nèi)實(shí)際的控制實(shí)體和容器。

            ApplicationContext負(fù)責(zé)對(duì)一組作為spring beans的一組bean的初始化,配置和生命期管理。我們通過裝配在一個(gè)基于XML的配置文件中的spring beans來配置ApplicationContext。這個(gè)配置文件說明了spring beans互相協(xié)作的本質(zhì)特點(diǎn)。這樣,用spring的術(shù)語來說,與其他spring beans交互的spring beans就被叫著協(xié)作者(collaborators)。缺省情況下,spring beans是作為單例存在于ApplicationContext中的,但是,單例的屬性能夠被設(shè)置為false,從而有效地改變他們?cè)趕pring中調(diào)用原型模式時(shí)的行為。

            回到我們的例子,在飛機(jī)票價(jià)下降的時(shí)候,一個(gè)SMTP發(fā)送例程的抽象就被裝配在工作流過程例子中的最后的活動(dòng)(例子代碼可以在 Resources中得到)。由于是第5個(gè)活動(dòng),我們命名它為activity5。要發(fā)送消息,activity5就要求一個(gè)代理協(xié)作者和一個(gè)錯(cuò)位處理句柄。


          ????? class="org.iocworkflow.test.sequence.ratedrop.SendMessage">
          ?????
          ????????
          ?????

          ?????
          ????????
          ?????

          ??


            將工作流組件實(shí)施成spring beans產(chǎn)生了兩個(gè)令人喜悅的結(jié)果,就是容易進(jìn)行單元測(cè)試和很大程度上可重用能力。IoC容器的特點(diǎn)明顯地提供了有效的單元測(cè)試。使用像spring這樣的Ioc容器,在測(cè)試期間,協(xié)作者之間的依賴能夠容易的用假的替代者替代。在這個(gè)航線的例子中,能夠容易地從唯一的測(cè)試ApplicationContext中檢索出像activity5活動(dòng)這樣的spring bean。用一個(gè)假的SMTP代理SMTP服務(wù)器,就有可能單獨(dú)地測(cè)試activity5。

            第二個(gè)意外的結(jié)果,可重用能力是通過像XSL轉(zhuǎn)換這樣的工作流活動(dòng)實(shí)現(xiàn)的。一個(gè)被抽象成工作流活動(dòng)的XSL轉(zhuǎn)換現(xiàn)在能夠被任何處理XSL轉(zhuǎn)換的工作流所重用。

            裝配工作流

            在提供的API中(從Resources下載),spring控制了一些操作者以一種工作流的方式交互。關(guān)鍵接口如下:

          Activity: 封裝了工作流中一個(gè)單步業(yè)務(wù)邏輯
          ProcessContext:在工作流活動(dòng)之間傳遞具有ProcessContext類型的對(duì)象。實(shí)現(xiàn)了這個(gè)接口的對(duì)象負(fù)責(zé)維護(hù)對(duì)象在工作流轉(zhuǎn)換中從一個(gè)活動(dòng)轉(zhuǎn)換到另一個(gè)活動(dòng)的狀態(tài)。
          ErrorHandler: 提供錯(cuò)誤處理的回調(diào)方法。
          Processor: 描述一個(gè)作為主工作流線程的執(zhí)行者的bean。

            下面從例子源碼中摘錄的代碼是將航線例子裝配為簡(jiǎn)單工作流過程的spring bean的配置。



          ??
          ?????
          ????????
          ???????????
          ???????????
          ???????????
          ???????????
          ???????????
          ????????

          ?????

          ?????
          ????????
          ????? /property>
          ?????
          ???????? org.iocworkflow.test.sequence.ratedrop.RateDropContext
          ?????

          ??


          SequenceProcessor類是一個(gè)對(duì)順序模式建模的具體子類。有5個(gè)活動(dòng)被連接到工作流處理器,工作流處理器將順序執(zhí)行這5個(gè)活動(dòng)。

            與大多數(shù)過程式后臺(tái)進(jìn)程相比,工作流的解決方案真正的突出了高度強(qiáng)壯的錯(cuò)誤處理。錯(cuò)誤處理句柄可以單獨(dú)地處理每個(gè)活動(dòng)。這種類型的句柄在單一活動(dòng)級(jí)別提供了細(xì)致的錯(cuò)誤處理。如果沒有單獨(dú)處理單個(gè)活動(dòng)的錯(cuò)誤處理句柄,那么全局工作流處理器的錯(cuò)誤處理句柄將會(huì)處理出現(xiàn)的問題。例如,如果在工作流處理過程中的任意時(shí)刻,一個(gè)沒有被處理的錯(cuò)誤出現(xiàn)了,那么它將會(huì)向外傳播,被使用defaultErrorHandler屬性裝配的ErrorHandler Bean處理。

            更復(fù)雜的工作流框架將工作流轉(zhuǎn)換之間的狀態(tài)持久化存儲(chǔ)到數(shù)據(jù)庫中。在這篇文章中,我們僅僅對(duì)狀態(tài)轉(zhuǎn)換是自動(dòng)完成的工作流感興趣。狀態(tài)信息僅僅在實(shí)際工作流運(yùn)行時(shí)在ProcessContext中得到。在ProcessContext中,你僅僅能看到ProcessContext的接口的兩個(gè)方法:


          public interface ProcessContext extends Serializable {
          ????? public boolean stopProcess();???
          ????? public void setSeedData(Object seedObject);
          ?? }


            用于航線例子工作流的具體的ProcessContext類是RateDropContext類。RateDropContext類封裝了用于執(zhí)行航線費(fèi)率降低工作流必須的數(shù)據(jù)。

            到現(xiàn)在為止,經(jīng)由缺省的ApplicationContext的作用,所有bean實(shí)例都已經(jīng)成了單例。但是,對(duì)于每一個(gè)航線工作流的調(diào)用,我們必須創(chuàng)建一個(gè)新的RateDropContext類的實(shí)例。為了處理這種需求,需要配置SequenceProcessor,采用全限定類名作為processContextClass屬性的值。對(duì)于每個(gè)工作流的執(zhí)行,SequenceProcessor使用指定的類名從spring檢索ProcessorContext類的一個(gè)新的實(shí)例。為了使這種機(jī)制能夠起作用,非單件的spring bean或者類型org.iocworkflow.test.sequence.simple.SimpleContext的原型必須存在于ApplicationContext中(完整的列表,參見rateDrop.xml)。

            播種工作流

            既然我們知道怎樣使用spring來組裝一個(gè)簡(jiǎn)單的工作流,就讓我們集中精力使用種子數(shù)據(jù)(seed data)示例工作流的過程。要明白怎樣開始工作流,看一看在實(shí)際接口Processor上表現(xiàn)的方法:


          public interface Processor {
          ????? public boolean supports(Activity activity);
          ????? public void doActivities();
          ????? public void doActivities(Object seedData);
          ????? public void setActivities(List activities);
          ????? public void setDefaultErrorHandler(ErrorHandler defaultErrorHandler);
          ?? }


            大多數(shù)情況下,工作流需要一些初始化激活才能開始。開始一個(gè)處理過程有兩個(gè)選項(xiàng):doActivities(ObjectseedData)方法或者無參數(shù)的doActivities()。下面的代碼列表是包含在樣例代碼中為SequenceProcessor而實(shí)現(xiàn)的doActivities():


          public void doActivities(Object seedData) {
          ?? //Retrieve injected by Spring
          ?? List activities = getActivities();
          ?? //Retrieve a new instance of the Workflow ProcessContext
          ?? ProcessContext context = createContext();
          ?? if (seedData != null)
          ????? context.setSeedData(seedData);
          ?? //Execute each activity in sequential order
          ?? for (Iterator it = activities.iterator(); it.hasNext();) {
          ????? Activity activity = (Activity) it.next();
          ????? try {
          ??????????? context = activity.execute(context);
          ????? } catch (Throwable th) {
          ???????? //Determine if an error handler is available at the activity level
          ???????? ErrorHandler errorHandler = activity.getErrorHandler();
          ???????? if (errorHandler == null) {
          ??????????? getDefaultErrorHandler().handleError(context, th);
          ??????????? break;
          ???????? } else {
          ??????????? //Handle error using default handler
          ??????????? errorHandler.handleError(context, th);
          ???????? }
          ????? }
          ??????????? //Ensure it's ok to continue the process
          ????? if (processShouldStop(context, activity))
          ???????? break;
          ????? }
          ?? }

          在這個(gè)航空費(fèi)用減少的例子中,工作流過程的種子數(shù)據(jù)包括航線信息和費(fèi)率減少的信息。使用容易測(cè)試的航線工作流例子,通過doActivities(Object seedData)方法發(fā)出種子數(shù)據(jù)并激活一個(gè)單一的工作流過程是簡(jiǎn)單的:

          BaseProcessor processor = (BaseProcessor)context.getBean("rateDropProcessor");
          ?? processor.doActivities(createSeedData());


            這些代碼是從包含在這篇文章中的測(cè)試?yán)又姓浀摹ateDropProcessor Bean是從ApplicationContext中檢索來的。rateDropProcessor實(shí)際上是裝配成SequenceProcessor的實(shí)例來處理順序執(zhí)行。createSeedData()方法實(shí)例化一個(gè)對(duì)象,這個(gè)對(duì)象封裝了初始化航線工作流所需要的所有種子數(shù)據(jù)。

            Processor選項(xiàng)

            雖然包含在源代碼中的Processor具體的子類僅僅是SequenceProcessor,但是,許多Processor接口的實(shí)現(xiàn)也是可以想象得到的。可以開發(fā)其他工作流處理過程子類來控制不同的工作流類型,例如,另一種像并行切割模式那樣有著變化的執(zhí)行路徑的工作流。對(duì)于簡(jiǎn)單工作流來說,因?yàn)榛顒?dòng)的順序是預(yù)先決定了的,所以SequenceProcessor是好的選擇。盡管沒有被包括進(jìn)來,對(duì)于使用基于spring的簡(jiǎn)單工作流的實(shí)現(xiàn)來說,排他選擇模式是另一個(gè)好的選擇。當(dāng)使用排他選擇模式時(shí),在每個(gè)活動(dòng)執(zhí)行之后,Processor具體類就會(huì)訊問ProcessorContext,接下來將要執(zhí)行哪一個(gè)活動(dòng)。

            注:有關(guān)并行切割,排他選擇和其他工作流模式的更多信息,請(qǐng)參看W.M.P. van der Aalst等人寫的《工作流模式》一書。

            啟動(dòng)工作流

            考慮到工作流過程常常需要異步執(zhí)行的特點(diǎn),使用分離的執(zhí)行線程來啟動(dòng)工作流就變得有意義了。對(duì)于工作流的異步啟動(dòng)而言,有好幾個(gè)選項(xiàng);我們主要集中在其中的兩個(gè):積極地檢測(cè)(actively polling)一個(gè)隊(duì)列來啟動(dòng)工作流,或者使用通過ESB(enterprise service bus, 企業(yè)服務(wù)總線)的事件驅(qū)動(dòng)方式來啟動(dòng)工作流,而Mule就是ESB的一個(gè)開源項(xiàng)目(關(guān)于Mule的更多信息,請(qǐng)參加"Event-Driven Services in SOA"(JavaWorld, 2005年1月)。

            圖3和圖4描繪了兩種啟動(dòng)策略。圖3中,積極檢測(cè)在工作流中第一個(gè)活動(dòng)經(jīng)常檢查資源的情形下發(fā)生,比如數(shù)據(jù)源或POP3郵件帳戶。如果圖3中的積極檢測(cè)發(fā)現(xiàn)有任務(wù)等待處理,那么啟動(dòng)就會(huì)開始。

          圖 3. 通過積極檢測(cè)來啟動(dòng)工作流

            另一方面,圖4表示了使用JMS(JAVA消息服務(wù))的J2EE應(yīng)用程序把事件放到隊(duì)列上的情形。一個(gè)通過ESB配置的事件監(jiān)聽器收到圖4中的事件,并且開始工作流,這樣,啟動(dòng)工作流過程。

          圖 4. 通過ESB事件來啟動(dòng)工作流

          使用所提供樣例的代碼,讓我們更詳細(xì)的看看主動(dòng)選擇啟動(dòng)方式與事件驅(qū)動(dòng)的啟動(dòng)方式。


            積極檢測(cè)

            積極檢測(cè)是一種花費(fèi)較少的啟動(dòng)工作流過程的方案。SequenceProcessor足夠靈活,以使得能夠通過平滑的選擇工作來進(jìn)行啟動(dòng)過程。盡管并不令人滿意,在沒有時(shí)間進(jìn)行事件驅(qū)動(dòng)子系統(tǒng)的配置和部署的許多情景中,積極檢測(cè)是明智的選擇。

            使用Spring的ScheduledTimerTask,檢測(cè)模式就能夠容易地裝配。缺點(diǎn)就是必須創(chuàng)建額外的活動(dòng)來進(jìn)行檢測(cè)。這個(gè)檢測(cè)活動(dòng)必須被設(shè)計(jì)來訊問某些實(shí)體,如數(shù)據(jù)庫表,pop郵件帳戶,或者Web服務(wù),然后決定新的工作是否等待參與到工作流中。

            在所提供的例子中,PollingTestCase類實(shí)例化一個(gè)基于檢測(cè)的工作流過程。使用一個(gè)有著積極檢測(cè)處理過程與事件驅(qū)動(dòng)的啟動(dòng)過程的不同之處在于,spring支持doActivities()方法的無參數(shù)版本。相反地,在事件驅(qū)動(dòng)的啟動(dòng)中,啟動(dòng)處理過程的實(shí)體通過doActivities(Object seedData)方法提供了種子數(shù)據(jù)來啟動(dòng)工作流。檢測(cè)方法的另一個(gè)缺點(diǎn)是:資源不一定能夠被重復(fù)地使用。依賴于應(yīng)用程序環(huán)境,這種資源的消耗是不可接受的。

            下面代碼例子演示了使用積極檢測(cè)來控制工作流啟動(dòng)的一個(gè)活動(dòng):

          public class PollForWork implements Activity
          {
          ?? public ProcessContext execute(ProcessContext context) throws Exception {
          ????? //First check if work needs to be done
          ????? boolean workIsReady = lookIntoDatabaseForWork();
          ????? if (workIsReady) {
          ???????? //The Polling Action must also load any seed data
          ???????? ((MyContext) context).setSeedData(createSeedData());
          ????? } else {
          ???????? //Nothing to do, terminate the workflow process for this iteration
          ???????? ((MyContext) context).setStopEntireProcess(true);
          ????? }
          ????? return context;
          ?? }
          }


            此外,包含在例子代碼的單元測(cè)試中的PollRates類提供了一個(gè)主動(dòng)選舉啟動(dòng)的可以運(yùn)行的例子。PollRates模擬了對(duì)于航線費(fèi)率下降的重復(fù)檢查。

            通過ESB的事件驅(qū)動(dòng)啟動(dòng)工作流

            理想地,一個(gè)包含了適當(dāng)?shù)姆N子數(shù)據(jù)的線程能夠異步地啟動(dòng)工作流。這種情況的一個(gè)例子是收到從JAVA消息服務(wù)隊(duì)列的消息。一個(gè)監(jiān)聽JMS隊(duì)列或者主題的客戶會(huì)收到通知,這個(gè)通知告知處理應(yīng)該在onMessage()方法中開始工作流。然后,通過使用Spring和doActivities(Object seedData)方法就能夠獲得工作流處理器Bean。


            使用ESB,實(shí)際用于發(fā)送啟動(dòng)事件的機(jī)制能夠恰當(dāng)?shù)貜墓ぷ髁魈幚砥髦蟹蛛x出來。開源項(xiàng)目Mule ESB有緊湊地和Spring相集成的好處。任意傳送機(jī)制,比如JMS,JVM,或者POP3郵箱都能夠發(fā)起事件的傳播。

            工作流的連續(xù)運(yùn)行

            工作流引擎后臺(tái)進(jìn)程應(yīng)該能夠沒有干擾地連續(xù)運(yùn)行。對(duì)于正在運(yùn)行的基于spring的工作流單一進(jìn)程來說好,有幾個(gè)選項(xiàng)。一個(gè)有著main()方法的簡(jiǎn)單Java類就足夠演示與這篇文章伴隨著的單元測(cè)試中的例子了。一個(gè)更加可靠的用于部署的機(jī)制是嵌入工作流到某種形式的J2EE組件中。Spring很好地支持和J2EE兼容的web應(yīng)用程序歸檔或者war文件的集成。基于Java管理附件(JMX)服務(wù)歸檔和JBoss應(yīng)用服務(wù)器(更多信息,參見JBoss homepage)支持的sar文件是更加合適的可部署組件,這種更合適的可部署組件也能夠被用來將部署歸檔。在JBoss 4.0中,sar文件已經(jīng)被大家所知道的deployer的格式所取代了。

            例子代碼

            打包成zip格式的例程代碼最好是用Apache Maven來使用它們。你能夠在主源代碼目錄src/java找到API。src/java目錄中有三個(gè)單元測(cè)試,包括:SimpleSequenceTestCase,RateDropTestCase和PoolingTestCase。要運(yùn)行所有這些測(cè)試,在命令行shell中鍵入maven test,然后在編譯和運(yùn)行之前,Maven將會(huì)下載所有必需的jar文件。實(shí)際的XSL轉(zhuǎn)換將會(huì)發(fā)生在兩個(gè)測(cè)試中,它們的結(jié)果被管道輸出到控制臺(tái)。鍵入maven test:ui來拉出圖形化的測(cè)試運(yùn)行器,然后選擇你想要運(yùn)行的測(cè)試,并且觀察控制臺(tái)的結(jié)果。

            結(jié)論

            在這篇文章中你已經(jīng)通過設(shè)計(jì)模式看到了工作流過程種類,在這些模式中,我們主要集中介紹了順序模式。通過使用接口,我們來對(duì)基本工作流組件建模。通過裝配多個(gè)接口實(shí)現(xiàn)到Spring,實(shí)現(xiàn)一個(gè)順序工作流。最后還討論了啟動(dòng)和部署工作流的不同選項(xiàng)。

            這里所提出的簡(jiǎn)單工作流技術(shù)肯定不是最終的和革命性的。但是,使用Spring來實(shí)現(xiàn)像工作流這樣的通用任務(wù)是一個(gè)通過使用IoC容器而獲得的效率的好的示例。由于減少了粘合性代碼的需要,Spring在保持面向?qū)ο蟮募s束同時(shí),減少面向?qū)ο蟛僮髀闊┑某潭取?/P>

          posted on 2005-10-13 17:30 Sung 閱讀(215) 評(píng)論(0)  編輯  收藏 所屬分類: Java
          主站蜘蛛池模板: 安塞县| 汤阴县| 招远市| 老河口市| 绥滨县| 滁州市| 华安县| 多伦县| 兴海县| 伊金霍洛旗| 雅江县| 潍坊市| 昌吉市| 丽水市| 广灵县| 巩义市| 阿坝| 突泉县| 昌吉市| 乌审旗| 昭平县| 华阴市| 鄂伦春自治旗| 达日县| 晋城| 马公市| 静安区| 醴陵市| 浦县| 丹寨县| 水城县| 尼玛县| 潼关县| 洛扎县| 广水市| 红安县| 朝阳市| 三原县| 福建省| 曲松县| 德兴市|