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

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

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

    空間站

    北極心空

      BlogJava :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
      15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks

    摘要:

    spring是支持控制反轉(zhuǎn)編程機制的一個相對新的框架。本文把spring作為簡單工作流引擎,將它用在了更加通用的地方。在對工作流簡單介紹之后,將要介紹在基本工作流場景中基于Spring的工作流API的使用。


     

    使用Spring來創(chuàng)建一個簡單的工作流引擎

    ――組織你的后臺處理任務使其成為一個基于spring的容易使用的工作流

    作者:Steve Dodge

    翻譯:DannyTan



    版權(quán)聲明:可以任意轉(zhuǎn)載,轉(zhuǎn)載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明
    原文地址:
    http://www.javaworld.com/javaworld/jw-04-2005/jw-0411-spring.html
    中文地址:
    http://www.matrix.org.cn/resource/article/43/43785_Spring.html
    關(guān)鍵詞: Spring work flow


    摘要
    spring是支持控制反轉(zhuǎn)編程機制的一個相對新的框架。本文把spring作為簡單工作流引擎,將它用在了更加通用的地方。在對工作流簡單介紹之后,將要介紹在基本工作流場景中基于Spring的工作流API的使用。(2,800個英文單詞; 2005/4/11)

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

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

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

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

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

    UML(統(tǒng)一建模語言)活動圖通常被用來作為一個機制對工作流建模。圖1顯示了一個基本的使用標準UML活動圖對順序工作流過程的建模過程。

    image
    圖 1順序工作流模式

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

    image
    圖 2.機票價格下降的簡單工作流

    圖1中的航線工作流負責創(chuàng)建和發(fā)送動態(tài)的email通知。過程中的每一步表示了一個活動(activity)。在工作流處于活動之前,一些額外事件必須發(fā)生。在這個例子中,事件是飛行路線費率的減少。

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

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

    控制反轉(zhuǎn)
    Spring通過使用spring容器來負責控制對象之間的依賴關(guān)系,使得我們不再對對象之間的依賴負責。 這種依賴關(guān)系的實現(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)和依賴注射的更加深入的討論。通過管理對象之間的依賴關(guān)系,spring就不需要那些只是為了使類能夠相互協(xié)作,而將對象粘合的代碼。

    作為spring beans的工作流組件
    在進一步討論之前,現(xiàn)在是簡要介紹spring中主要概念的恰當時候。接口ApplicationContext是從接口BeanFactory繼承的,它被用來作為在spring容器內(nèi)實際的控制實體和容器。

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

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

      
    <bean id="activity5" 
          class="org.iocworkflow.test.sequence.ratedrop.SendMessage">
          <property name="delegate">
             <ref bean="smtpSenderDelegate"></ref>
          </property>
          <property name="errorHandler">
             <ref bean="mailErrorHandler"/>
          </property>
       </bean>


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

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

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

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

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

      
    <!-- Airline rate drop as a simple sequence workflow process -->
       <bean id="rateDropProcessor" class="org.iocworkflow.SequenceProcessor" >
          <property name="activities">
             <list>
                <ref bean="activity1"/><!--Build recipients-->
                <ref bean="activity2"/><!--Construct DOM tree-->
                <ref bean="activity3"/><!--Apply XSL Transform-->
                <ref bean="activity4"/><!--Write Audit Data-->
                <ref bean="activity5"/><!--Attempt to send message-->
             </list>
          </property>
          <property name="defaultErrorHandler">
             <ref bean="defaultErrorHandler"></ref>
          /property>
          <property name="processContextClass">
             <value>org.iocworkflow.test.sequence.ratedrop.RateDropContext</value>
          </property>
       </bean>


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

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

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

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


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

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

    播種工作流
    既然我們知道怎樣使用spring來組裝一個簡單的工作流,就讓我們集中精力使用種子數(shù)據(jù)(seed data)示例工作流的過程。要明白怎樣開始工作流,看一看在實際接口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ù)情況下,工作流需要一些初始化激活才能開始。開始一個處理過程有兩個選項:doActivities(ObjectseedData)方法或者無參數(shù)的doActivities()。下面的代碼列表是包含在樣例代碼中為SequenceProcessor而實現(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;
          }
       }


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

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


    這些代碼是從包含在這篇文章中的測試例子中摘錄的。rateDropProcessor Bean是從ApplicationContext中檢索來的。rateDropProcessor實際上是裝配成SequenceProcessor的實例來處理順序執(zhí)行。createSeedData()方法實例化一個對象,這個對象封裝了初始化航線工作流所需要的所有種子數(shù)據(jù)。

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

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

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


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

    image
    圖 3. 通過積極檢測來啟動工作流

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

    image
    圖 4. 通過ESB事件來啟動工作流

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


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

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

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

    下面代碼例子演示了使用積極檢測來控制工作流啟動的一個活動:

    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;
       }
    }


    此外,包含在例子代碼的單元測試中的PollRates類提供了一個主動選舉啟動的可以運行的例子。PollRates模擬了對于航線費率下降的重復檢查。

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


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

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

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

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

    這里所提出的簡單工作流技術(shù)肯定不是最終的和革命性的。但是,使用Spring來實現(xiàn)像工作流這樣的通用任務是一個通過使用IoC容器而獲得的效率的好的示例。由于減少了粘合性代碼的需要,Spring在保持面向?qū)ο蟮募s束同時,減少面向?qū)ο蟛僮髀闊┑某潭取?br />
    我賞識Mikhail Garber,他是JavaWorld中另一篇與此文無關(guān)的文章(參見 "Use Search Engine Technology for Object Persistence")的作者。Mikhail是這篇文章的主要貢獻人,他提出的關(guān)于在簡單工作流中使用Spring的最初思想,對本文有一定的幫助。

    關(guān)于作者
    Steve Dodge專長于使用基于J2EE開源框架搭建商業(yè)軟件。他的開發(fā)經(jīng)驗超過7年,并且曾經(jīng)在多種政府和商業(yè)實體的開發(fā)項目工作,這些項目包括像美國郵政服務,美國國家海洋大氣管理局,電子數(shù)據(jù)系統(tǒng),和Verizon無線通話系統(tǒng)(在飛機上專用的無線系統(tǒng),編者注)。


    資源:
    下載與這篇文章相關(guān)的源代碼:
    http://www.javaworld.com/javaworld/jw-04-2005/spring/jw-0411-spring.zip
    Spring主頁:
    http://www.springframework.org
    OSWorkflow主頁:
    http://wiki.opensymphony.com/display/WF/2.3+Spring+framework
    工作流模式,分布式及并行數(shù)據(jù)庫, W.M.P. van der Aalst, A.H.M. ter Hofstede, B. Kiepuszewski, and A.P. Barros (2003年7月):
    http://is.tm.tue.nl/research/patterns/download/wfs-pat-2002.pdf
    工作流模式:
    http://is.tm.tue.nl/research/patterns/?
    反轉(zhuǎn)控制的分析:《Inversion of Control Containers and the Dependency Injection Pattern》 Martin Fowler 著(martinfowler.com):
    http://martinfowler.com/articles/injection.html
    《Event-Driven Services in SOA》 Jeff Hanson (JavaWorld, 2005年1月):
    http://www.javaworld.com/javaworld/jw-01-2005/jw-0131-soa.html
    Jboss應用服務器主頁:
    http://www.jboss.org
    Apache Maven 項目:
    http://maven.apache.org/
    《Use Search Engine Technology for Object Persistence》 Mikhail Garber (JavaWorld, 2005年1月):
    http://www.javaworld.com/javaworld/jw-01-2005/jw-0103-search.html
    有關(guān)Spring方面的文章,瀏覽這些最近的 JavaWorld文章:
    "Pro Spring: Spring and EJB," Rob Harrop 及 Jan Machacek (2005年2月):
    http://www.javaworld.com/javaworld/jw-02-2005/jw-0214-springejb.html
    "Let Your Ant Enjoy Spring," Josef Betancourt (2005年2月):
    http://www.javaworld.com/javaworld/jw-02-2005/jw-0214-antspring.html
    Leverage Spring, Struts, Hibernate, and Axis to "Design a Simple Service-Oriented J2EE Application Framework," Fangjian Wu (2004年10月):
    http://www.javaworld.com/javaworld/jw-10-2004/jw-1004-soa.html
    Learn how to build a Web application with JavaServer Faces, Spring, and Hibernate in "Put JSF to Work," Derek Yang Shen (2004年7月):
    http://www.javaworld.com/javaworld/jw-07-2004/jw-0719-jsf.html
    有關(guān)設計模式方面的文章瀏覽JavaWorld的主題索引的設計模式部分:
    http://www.javaworld.com/channel_content/jw-patterns-index.shtml
    有關(guān)開發(fā)工具方面的文章,瀏覽JavaWorld的主題索引的開發(fā)工具部分:
    http://www.javaworld.com/channel_content/jw-tools-index.shtml

    posted on 2008-08-01 13:13 蘆葦 閱讀(4761) 評論(2)  編輯  收藏 所屬分類: JAVA其他

    Feedback

    # re: 使用Spring來創(chuàng)建一個簡單的工作流引擎 2014-06-27 13:27
    東東  回復  更多評論
      

    # re: 使用Spring來創(chuàng)建一個簡單的工作流引擎[未登錄] 2015-09-14 17:08 Zheng
    看這篇文章的時候,A.H.M. ter Hofstede,就坐我對面  回復  更多評論
      

    主站蜘蛛池模板: 男人都懂www深夜免费网站| 亚洲av日韩综合一区二区三区| 亚洲国产香蕉人人爽成AV片久久 | 国产精品美女自在线观看免费| 永久久久免费浮力影院| 亚洲人成影院在线观看| 亚洲国产精品综合福利专区| 亚洲欧美国产日韩av野草社区| 偷自拍亚洲视频在线观看| 国产免费无码一区二区| 毛片免费视频在线观看| 亚洲国产成人精品女人久久久 | 久久精品国产亚洲麻豆| 亚洲xxxx18| 久久免费香蕉视频| 成人性生交视频免费观看| 亚洲精品国产首次亮相| **实干一级毛片aa免费| 中文字幕不卡亚洲| 亚洲一区二区影视| 久久免费国产视频| 亚洲一级特黄特黄的大片 | a级毛片毛片免费观看久潮| 好吊妞788免费视频播放| 久久精品国产亚洲av麻豆小说| 黄色毛片免费网站| 成年人视频在线观看免费| 国产成人亚洲精品蜜芽影院| 久久久久亚洲爆乳少妇无 | 在线视频观看免费视频18| 国产精品亚洲一区二区三区在线| 国产AV日韩A∨亚洲AV电影| 亚洲欧洲日产国码av系列天堂| 成人一级免费视频| 宅男666在线永久免费观看| 亚洲人成电影网站色| 亚洲综合日韩久久成人AV| 亚洲精品免费在线| 免费无码午夜福利片| 亚洲?V乱码久久精品蜜桃| 亚洲AV无码成人精品区狼人影院|