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

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

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

    snowolf

    這樣的一種生活
    posts - 23, comments - 5, trackbacks - 0, articles - 11

    使用Annotations設計一個MVC框架

    Posted on 2006-02-10 15:56 snowolf 閱讀(291) 評論(0)  編輯  收藏 所屬分類: JAVA類
    Annotations能夠幫助你去掉應用組件之間的耦合

    摘要

    Model-View-Controller (MVC)是一種軟件架構,它可以將應用程序的數(shù)據(jù)模型,用戶接口,以及控制邏輯分開, 使其成為獨立的組件.這樣一來,對其中一個組件的修改而產(chǎn)生的對其他組件的影響能夠被降到最低. 在這篇文章中,你將學習到如何使用annotation來設計一個幾乎能夠完全去掉model和view之間耦合的繼承的MVC的框架.

    版權聲明:任何獲得Matrix授權的網(wǎng)站,轉載時請務必保留以下作者信息和鏈接
    作者:Riccardo Govoni;wenzi_33
    原文:http://www.matrix.org.cn/resource/article/44/44204_Annotations+MVC.html
    關鍵字:annotations;MVC;Framework

    當設計一個應用程序時, 清晰的分離該程序的不同邏輯組件, 總是被證明是有益的. 同時也存在許多不同的模式來幫助開發(fā)者實現(xiàn)這個目標。其中最有名同時也最常用的自然是Model-View-Controller (MVC)了, 它能夠?qū)⒚總€應用程序(或者應用程序的一部分)分成三個不同功能的組件,并且定義了把他們聯(lián)結在一起的規(guī)則。Swing本身就是基于這個模式的,而且每個使用Struts,這個流行的開發(fā)Web應用框架的人也都了解隱藏在MVC后面的理論.

    這篇文章介紹了怎么樣通過使用annotation而增加一個新的組件來加強MVC,使其能夠更加方便地去掉models跟views之間的耦合。這篇文章介紹了一個叫Stamps的開源庫, 它是基于MVC組件之上的,但它去除了所有在開發(fā)MVC時所需的, 在models, views和controllers之間建立聯(lián)系的負擔。

    基礎知識: MVC和annotations

    正如MVC這個名字所指出的, Model-View-Controller模式建議將一個應用程序分成以下三個組件:
    ·Model: 包含了數(shù)據(jù)模型和所有用來確定應用程序狀態(tài)的信息。 它一般來說是有條理的并且獨立于其他組件的。
    ·View: 從不同于model的角度出發(fā),它定義了存儲在模型中數(shù)據(jù)的展現(xiàn)方式。它通常被認為是你的應用程序的用戶界面(或者GUI),或者以Web應用為例,場景就是你通過瀏覽器看到的頁面。
    ·Controller: 它代表應用程序的邏輯部分。在這里,它定義了一個用戶如何和應用程序進行交互并且也定義了用戶行為是如何映射到model的改變。

    這些組件緊密的聯(lián)系在一起: 用戶影響view, 反過來view通知controller來更新model.最終model又更新view來反映它的新狀態(tài)。圖1就展現(xiàn)了這種典型的MVC結構。

    2006_02_08_232846_XtnRHlDmNe.jpg
    圖1. 一個典型的MVC結構

    作為J2SE 5.0所提供的一個新的功能,annotations允許開發(fā)者往classes,methods,fields,和其他程序元素中增加元數(shù)據(jù)。就像反射機制一樣,之后很多應用程序為了某些原因能在運行時期獲取并使用那些元數(shù)據(jù)。因為J2SE 5.0只是定義了怎么樣編寫和讀取annotations,并沒有說明在哪里使用他們(象@Override這樣的用于提前定義的例外),開發(fā)者擁有無窮多的在許多不同場合使用他們的可能性:文檔編寫,與對象相關的映射,代碼生成,等等.. Annotations已經(jīng)變的十分流行,以至于大多數(shù)框架和庫都更新自己來支持他們。至于更多的關于MVC和annotations的信息請參見資源。

    超越MVC: dispatcher

    就像前文提到的一樣,models和views之間的一些耦合是必要的因為后者必須反映前者的狀態(tài)。普通Java程序使用直接或間接的耦合將組件綁定在一起。直接耦合發(fā)生在當view和model之間有一個直接相關的時候,model包含一列需要維持的views。間接耦合通常發(fā)生在一個基于事件分派的機制中。Model會在它狀態(tài)改變時激發(fā)事件,同時一些獨立的views會將他們自己注冊成事件偵聽器。

    通常我們比較青睞間接耦合因為它使model完全不知道view的存在,相反view必須和model保持一定的聯(lián)系從而將自己注冊到model上。在這篇文章里我將介紹的框架就是使用間接耦合,但是為了更好的降低組件之間的耦合,view必須不知道m(xù)odel的存在;也就是說,model和view沒有被綁定在一起。

    為了實現(xiàn)這個目標,我已經(jīng)定義了一個新的組件,就是dispatcher,它能作為一個存在于views和models之間的分離層。它能處理models和views雙方之間的注冊并且分派由model激發(fā)的事件到注冊的views上。它使用java.beans.PropertyChangeEvent對象來表現(xiàn)由model傳送到view的事件;然而,這個框架的設計是足夠開放的,它可以支持不同事件類型的實現(xiàn)。

    管理注冊的views列表的負擔于是就從model上移開了,同時,因為view只和這個獨立于應用程序的dispatcher有關,view不知道m(xù)odel的存在。如果你熟悉Struts內(nèi)部,你也許能夠看出Struts的controller就是在履行這樣一個任務,它將Actions和他們關聯(lián)的JSP(JavaServer Pages)表現(xiàn)頁面聯(lián)系在一起。

    現(xiàn)在,我們所設計的MVC框架就像圖2所描述的一樣。Dispatcher在其中擔當了一個于controller相稱的角色。

    2006_02_08_233016_NNAtmqYNNs.jpg
    圖2.擁有額外dispatcher組件的改進的MVC框架

    由于dispatcher必須是獨立于應用程序的,所以必須定義一些通用的聯(lián)結models和views的規(guī)范。我們將使用annotations來實現(xiàn)這種聯(lián)結,它將會被用來標注views并且確定哪個view是受哪個model的影響的,及這種影響是怎么樣的。通過這種方式,annotations就像是貼在明信片上的郵票一樣,驅(qū)動dispatcher來執(zhí)行傳遞model事件的任務(這就是這一框架名字的由來)。


    應用實例

    我們將使用一個簡單的計秒器應用程序做該框架的一個應用實例:它允許用戶設置時間周期來記數(shù)和啟動/停止這個定時器。 一旦過去規(guī)定的時間,用戶將會被詢問是否取消或者重啟這個定時器。這個應用程序的完全源代碼可以從項目主頁上找到。

    2006_02_08_233211_otZImPhZpB.jpg
    圖3.一個簡單的應用程序

    這個modle是非常簡單的,它只存儲兩個屬性:周期和已經(jīng)過去的秒數(shù)。注意當它其中一個屬性發(fā)生變化時它是如何使用java.beans.PropertyChangeSuppor來激發(fā)事件。
    public class TimeModel {

       public static final int DEFAULT_PERIOD = 60;

       private Timer timer;
       private boolean running;

       private int period;
       private int seconds;

       private PropertyChangeSupport propSupport;

       /**
        * Getters and setters for model properties.
        */

       /**
        * Returns the number of counted seconds.
        *
        * @return the number of counted seconds.
        */
       public int getSeconds() {
          return seconds;
       }

       /**
        * Sets the number of counted seconds. propSupport is an instance of PropertyChangeSupport
        * used to dispatch model state change events.
        *
        * @param seconds the number of counted seconds.
        */
       public void setSeconds(int seconds) {
          propSupport.firePropertyChange("seconds",this.seconds,seconds);
          this.seconds = seconds;
       }

       /**
        * Sets the period that the timer will count. propSupport is an instance of PropertyChangeSupport
        * used to dispatch model state change events.
        *
        * @param period the period that the timer will count.
        */
       public void setPeriod(Integer period){
          propSupport.firePropertyChange("period",this.period,period);
          this.period = period;
       }

       /**
        * Returns the period that the timer will count.
        *
        * @return the period that the timer will count.
        */
       public int getPeriod() {
          return period;
       }

       /**
        * Decides if the timer must restart, depending on the user answer. This method
        * is invoked by the controller once the view has been notified that the timer has
        * counted all the seconds defined in the period.
        *
        * @param answer the user answer.
        */
       public void questionAnswer(boolean answer){
          if (answer) {
             timer = new Timer();
             timer.schedule(new SecondsTask(this),1000,1000);
             running = true;
          }
       }

       /**
        * Starts/stop the timer. This method is invoked by the controller on user input.
        */
       public void setTimer(){
          if (running) {
             timer.cancel();
             timer.purge();
          }
          else {
             setSeconds(0);
             timer = new Timer();
             timer.schedule(new SecondsTask(this),1000,1000);
          }

          running = !running;
       }

       /**
        * The task that counts the seconds.
        */
       private class SecondsTask extends TimerTask {

          /**
           * We're not interested in the implementation so I omit it.
           */

       }
    }


    Controller只定義了用戶可以執(zhí)行的并且能夠從下列接口抽象出來的actions。
    public interface TimeController {

       /**
        * Action invoked when the user wants to start/stop the timer
        */
       void userStartStopTimer();

       /**
        * Action invoked when the user wants to restart the timer
        */
       void userRestartTimer();

       /**
        * Action invoked when the user wants to modify the timer period
        *
        * @param newPeriod the new period
        */
       void userModifyPeriod(Integer newPeriod);
    }


    你可以使用你自己喜歡的GUI編輯器來畫這個view。出于我們自身的情況,我們只需要幾個公共的methods就可以提供足夠的功能來更新view的fields,如下面的這個例子所示:
    /**
        * Updates the GUI seconds fields
        */
       public void setScnFld(Integer sec){
          // scnFld is a Swing text field
          SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                scnFld.setText(sec.toString());
             }
          });
       }


    在這里我們注意到我們正在使用POJOs (plain-old Java objects),同時我們不用遵守任何編碼習慣或者實現(xiàn)特定的接口(事件激發(fā)代碼除外)。剩下的就只有定義組件之間的綁定了。

    事件分派annotations

    綁定機制的核心就是@ModelDependent annotation的定義:
    @Retention(RetentionPolicy.RUNTIME) 
    @Target(ElementType.METHOD)
    public @interface ModelDependent {

       String modelKey() default "";

       String propertyKey() default "";

       boolean runtimeModel() default false;

       boolean runtimeProperty() default false;

    }


    這個annotation能被用在view的methods上,同時dispatcher也會使用這些提供的參數(shù)(即modelKey和propertyKey)來確定這個view將會響應的model事件。這個view既使用modelKey參數(shù)來指定它感興趣的可利用的models又使用propertyKey參數(shù)來匹配分配的java.beans.PropertyChangeEvents的屬性名稱。

    View method setScnFld()因此被標注以下信息(這里,timeModel提供了用來將model注冊到dispatcher上的key):
    /**
        * Updates the GUI seconds fields
        */
       @ModelDependent(modelKey = "timeModel", propertyKey = "seconds")
       public void setScnFld(final Integer sec){
          // scnFld is a Swing text field
          SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                scnFld.setText(sec.toString());
             }
          });
       }


    由于dispatcher既知道m(xù)odel激發(fā)的事件又知道事件本身-例如,它知道關聯(lián)的modelKey和propertyKey-這是唯一需要用來綁定views和models的信息。Model和view甚至不需要分享通信接口或者共用的數(shù)據(jù)庫。

    借助我們討論的綁定機制,我們可以輕易的改變潛在的view而不改變其他任何東西。下面的代碼是按照使用SWT(Standard Widget Toolkit)而不是Swing實現(xiàn)的同一個method:
    @ModelDependent(modelKey = "timeModel", propertyKey = "seconds")
       public void setScnFld(final Integer sec){
          Display.getDefault().asyncExec(new Runnable() {
             public void run() {
                secondsField.setText(sec.toString());
             }
          });
       }


    一個完全沒有耦合的系統(tǒng)存在以下優(yōu)點:View可以更加容易地適應model地改變,盡管model通常都是穩(wěn)定地,相反view是經(jīng)常被改變。加上系統(tǒng)可以通過使用GUI編輯器或者其他源碼生成器來設計,避免了將生成地代碼與model-view通信代碼混合在一起。又由于model-view的綁定信息是和源碼關聯(lián)的元數(shù)據(jù),于是也相對容易把它應用到IDE生成的GUIs或者將已經(jīng)存在的應用程序轉化成這個框架。加之擁有單獨的基礎代碼,view和model可以被當作是獨立組件來開發(fā),這很可能簡化了應用程序的開發(fā)過程。組件測試也可以被簡化,因為每個組件可以被單獨地測試,并且出于調(diào)試的目的,我們可以用假的model和view來代替真實的組件。

    然而,這里也存在許多缺點。因為現(xiàn)在當使用接口和公共的classes來綁定model和view時,我們不能再提供編譯時期的安全性了,可能出現(xiàn)的打字錯誤將導致組件之間一個綁定的遺漏,從而導致出現(xiàn)運行時期的錯誤。

    通過使用@ModelDependent的討論過的modelKey和propertyKey元素,你可以定義model和view之間靜態(tài)的聯(lián)系。然而,現(xiàn)實世界的應用程序證明view必須能夠經(jīng)常動態(tài)的適應變化的models和應用程序的狀態(tài):考慮到用戶界面的不同部分能夠在應用程序的生命周期內(nèi)被創(chuàng)造和刪除。因此我將介紹怎么使用這個框架與其他常用技術一起來處理此類情形。

    動態(tài)MVC綁定

    對于那些依賴XML綁定(或者其他一些基于配置文件的聲明性綁定)的框架,存在一個問題那就是靜態(tài)綁定規(guī)則。在這些框架下,動態(tài)變化是不可能的,于是通常開發(fā)者決定每次將冗余的綁定信息與一些使用正確綁定的判定算法耦合在一起。

    為了巧妙的解決這個問題,Stamps框架提供了兩種方式在運行時期改變綁定。 第一種方式是,views和models可以采用事件監(jiān)聽器與GUI窗口小部件聯(lián)合的方式在dispatcher上注冊和注銷。這樣允許特定的views只在需要他們的時候被通知到。例如,一個與應用程序有聯(lián)系的監(jiān)視控制臺可以只在用戶請求的時候與被它監(jiān)視的對象綁定在一起。

    第二種方式是利用@ModelDependent annotation提供的兩個元素runtimeModel() 和 runtimeProperty()。他們指明了某個確定的model和它的分配事件會在運行時期被確定。如果這兩個設定中有一個是正確的,那么各自的key(modelKey 或propertyKey)會在view上被method調(diào)用來得到需要使用的值。例如:一個負責顯示一組新channels (每個channel就是一個model)的view,它就依賴于用戶的輸入來確定需要綁定的channel。

    這種情形的實例如下:
    // This method is invoked to display all the messages of one news channel
       @ModelDependent(modelKey = "dynamicChannel", propertyKey = "allmessages" , runtimeModel = true)
       public void setAllMessages(java.util.List messages) {
          // Updates the user interface
       }

       public String getDynamicChannel() {
          // Returns the channel requested by the user
       }


    附加的annotations

    由于世界并不完美,一些附加的annotations被定義來幫助解決現(xiàn)實世界的案例。@Namespace允許開發(fā)者為了更好的管理model domain將其再細分成不同的部分。由于單獨一個dispatcher可以處理多個models,model keys中將出現(xiàn)的沖突。因此,它能將成群的models和相關的views分到不同的但同屬一個namespace下的domains中去, 這樣一來,他們就不會干擾對方。

    @Transform annotation提供了on-the-fly對象轉化, 從包含在model事件中的對象到被receiving views接受的對象的。因而,這個框架就可以適應已存的代碼而不需要做任何的改動。這個annotation接受一個注冊在有效轉化上的單一參數(shù)(被定義成一個特殊接口的實現(xiàn))。

    @Refreshable annotation能通過標注model的屬性來支持前面討論的動態(tài)連接和分離views。使用這個annotation,該框架可以處理靜態(tài)和動態(tài)的MVC布局,在不同的時間把不同的views綁定到model上。

    要理解@Refreshable的使用,我們必須回到之前的那個監(jiān)控控制臺的例子。這個控制臺(用MVC的術語來說就是一個view)可以動態(tài)地綁定和離開model,取決于用戶的需要。當控制器連接到model的時候@Refreshable annotation可以被用來讓這個控制器隨時了解其model的狀態(tài)。當一個view連接到這個框架時,它必須在當前model的狀態(tài)下被更新。因此,dispatcher掃描model尋找@Refreshable annotations并且生成與view它本身從model普通接受到的相同的事件。這些事件接著被之前討論過的綁定機制分派。

    分布式MVC網(wǎng)絡

    Dispatcher有一個很重的負擔那就是它負責處理事件的傳送周期中所有重型信息的傳遞:
    ·        Model激發(fā)一個事件用來確定它已經(jīng)經(jīng)歷過的一些改變, dispatcher處理通知model.
    ·        Dispatcher掃描所有注冊在它那里的views, 尋找@ModelDependent annotations, 這些annotations明確了views希望通知的改變及當每個model事件發(fā)生時,需要在views上調(diào)用的method.
    ·        如果需要,轉化將會被用于事件數(shù)據(jù)上.
    ·        view method在被調(diào)用時會從被激發(fā)的事件里抽取參數(shù),接著view會更新自己.

    從另一個方面來講,當一個新view在dispatcher上注冊時:
    ·        View告訴dispatcher有關modelKey的信息,modelkey能確定它將被連接到哪一個model上(該model的事件將負責組裝view)
    ·        如果需要,dispatcher掃描model尋找@Refreshable annotations并使用他們來生產(chǎn)將要及時更新view假的model事件
    ·        這些事件將通過使用上述的順序被分派, 接著view被更新.

    所有這些既不涉及view也不涉及model的工作,他們站在他們各自的信息通信渠道的兩端.無所謂這些信息是在一個本地JVM內(nèi)部傳輸還是在多個遠程主機上的JVM之間傳輸.如果想將本地應用程序轉化成Client/Server應用程序所需的只是簡單地改變dispatcher里面的邏輯,而model和view都不會受影響.下圖就是一個示例: 

    2006_02_08_233827_rlIDvGWfPI.jpg 
    圖4. 一個基于分布式網(wǎng)路建立的MVC.

    如上圖所示,單一的dispatcher被一個與model處在同一個host上的transmitter(it.battlehorse.stamps.impl.BroadcastDispatcher的一個instance)和一個(或多個) 與view處在同一個host上的receiver(it.battlehorse.stamps.impl.FunnelDispatcher)所取代. Stamps 框架默認的實現(xiàn)使用了一個創(chuàng)建于JGroups上的消息傳送層, JGroups是一個可靠的多點傳送通信的工具包,象網(wǎng)絡傳輸機制(但是不同的實現(xiàn)和使用)一樣工作. 通過使用它可以獲得一個穩(wěn)定可靠的, 多協(xié)議的, 失敗警覺的通信.

    對我們應用程序(dispatcher)初步建立的一個改變, 使我們從一個單一用戶界面的獨立運行的應用程序轉移到一個多用戶分布式的應用程序.當model進入或離開這個網(wǎng)絡(想象一個通信失敗)的時候,框架可以通知無數(shù)的監(jiān)聽接口, 于是遠程views可以采取適當?shù)捻憫?例如,顯示一個警告信息給用戶. 這個框架也可以提供有用的methods來幫助將本地的controllers轉化成遠程的.

    總結和摘要
    仍有許多元素需要被探索,就像設計controllers的方式一樣,它在目前和dispatchers具有一致的普遍性.該框架假設普通的controller-model綁定,由于前者需要知道如何去驅(qū)動后者.未來的開發(fā)方向?qū)⑹侵С植煌愋偷膙iews,例如使用一個Web瀏覽器, 網(wǎng)絡警覺的applets,和Java與JavaScript的通信.

    已經(jīng)討論的Stamps庫說明如何在一個MVC架構中降低views和models之間的耦合以及這個框架可以有效的利用Java annotations將綁定信息從實際開發(fā)程序組件分離開.擁有隔離的綁定邏輯允許你在物理上將元件分離開并且能提供一個本地和一個client/server結構而不需要改變應用邏輯或者表示層. 這些目標提供對由一個象MVC一樣堅固的設計模式與由annotations提供的功能強大的元數(shù)據(jù)結合在一起所提供的可能性的洞察.  

    關于作者
    Riccardo Govoni自從2003年開始就做為一名J2EE的開發(fā)者在一家位于意大利北部的金融服務公司工作.在那里,他為遺留的銀行系統(tǒng),數(shù)據(jù)庫管理,和繁重的數(shù)據(jù)處理任務開發(fā)了許多Web前臺. Govoni在J2EE多層應用程序方面擁有豐富經(jīng)驗并且擁有關于Java GUI 庫象Swing 和SWT詳盡的知識.他擁有物理學碩士學位.在閑暇時間,Govoni在網(wǎng)絡上四處尋找最新的Java新聞,或者與朋友和(有時候)他女朋友的狗一起討論新的項目主意.

    資源
    ·        Stamps項目主頁,及相關源碼文件和文檔:http://sourceforge.net/projects/stamps-mvc/
    ·        Wikipedia上關于MVC的定義:http://en.wikipedia.org/wiki/MVC
    ·        最有名的MVC實現(xiàn)之一, Struts Web 應用框架:http://struts.apache.org/
    ·        JGoodies 綁定,一個在Swing應用程序中解決model-view-controller綁定的不同方法:https://binding.dev.java.net/
    ·        TikeSwing, 一個基于Swing之上的開源框架, 通過擴展Swing widgets來引入MVC “與結合Swing來使用高層次的MVC和POJOs” Tomi Tuomainen(JavaWorld, 2005年六月):http://www.javaworld.com/javaworld/jw-06-2005/jw-0620-tikeswing.html
    ·        Sun J2SE 5.0 annotations向?qū)?http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html
    ·        J2SE 5.0 annotations 入門介紹: “Taming Tiger, 第三部分,” Tarak Modi(JavaWorld, 2004年七月):http://www.javaworld.com/javaworld/jw-07-2004/jw-0719-tiger3.html
    ·        “零距離: J2SE 5.0 Annotations,” Kyle Downey (ONJava.com, 2004年十月):http://www.onjava.com/pub/a/onjava/2004/10/06/anno1.html
    ·        JGroups, 是一個可靠的多點傳送通信的工具包, 在分布式Stamps MVC網(wǎng)絡中象傳輸層一樣工作:http://www.jgroups.org/javagroupsnew/docs/index.html
    ·        “一個Swing架構的一般了解”, 作者: Amy Fowler(Sun開發(fā)者網(wǎng)絡)討論Swing’s MVC的根源:http://java.sun.com/products/jfc/tsc/articles/architecture/
    主站蜘蛛池模板: 午夜福利不卡片在线播放免费| 日本一道本不卡免费 | 亚洲午夜国产精品| 久久影院亚洲一区| 啦啦啦手机完整免费高清观看| 中文字幕久无码免费久久| 亚洲av无码成人精品国产| 亚洲色四在线视频观看| 国产亚洲精品a在线观看app| 免费观看理论片毛片| 免费无码一区二区三区蜜桃大 | 亚洲午夜电影在线观看| 日本久久久久亚洲中字幕| 亚洲国产成人精品无码区在线观看| 日韩免费毛片视频| 国产v片免费播放| 国产成人免费网站在线观看| 国产伦精品一区二区三区免费迷| 国产精品成人免费视频网站京东| 亚州免费一级毛片| 久久久久国色AV免费观看性色| 无码乱肉视频免费大全合集| 日韩免费一区二区三区在线| 精品熟女少妇AV免费观看| 午夜一区二区免费视频| 久久精品国产亚洲5555| 亚洲色无码一区二区三区| 久久亚洲精品中文字幕无码 | 亚洲免费黄色网址| 一个人在线观看视频免费| 日韩在线视频免费看| 亚洲Av无码乱码在线znlu| 亚洲av无码国产精品夜色午夜| 亚洲国产人成在线观看69网站| 亚洲丝袜中文字幕| 国产va免费精品| 日韩精品无码区免费专区| 亚洲欧洲久久av| 亚洲日韩精品国产3区| 成全高清在线观看免费| 国产成人在线免费观看|