织梦CMS - 轻松建站从此开始!

罗索实验室

当前位置: 主页 > 杂项技术 > JAVA >

剖析Java Event-Delegation Model(上)(作者:林錦\陽)

罗索客 发布于 2004-03-03 09:31 点击:次 
http://hpc.ee.ntu.edu.tw/~sflin/java/Event-Delegation%20Model_1.htm *前言 ======= 會想寫這篇文章,主要是因為最近實驗室有個計劃,必須Porting Java AWT到一個Embedded Window System,這逼得筆者非得去Trace JDK的AWT Package,而在Trace的過程中,以前一直覺
TAG:

http://hpc.ee.ntu.edu.tw/~sflin/java/Event-Delegation%20Model_1.htm

*前言
=======
會想寫這篇文章,主要是因為最近實驗室有個計劃,必須Porting
Java AWT到一個Embedded Window System,這逼得筆者非得去Trace
JDK的AWT Package,而在Trace的過程中,以前一直覺得不太清楚
的Java Event運\作模式,現在終於獲得解決,如果各位有留意過一
些市面上的Java書籍,不管是講Java 1.0的Event Model或是Java
1.1之後的”Event-Delegation” Model(”Source-Listener”
Model),大部分都只會提到Event該怎麼用(Event的用法),而對
於Java Event的來源和底層的原理卻是少有著墨。而本篇文章算
是筆者個人的工作心得,重點在於探討”Event的來源”以及”
Event dispatch的流程”,在此提出來跟大家分享,希望對各位
正在學Java或已經是Java高手的讀者能有所幫助,當然,非常歡
迎各位讀者來信指教,筆者將會很樂意與您一起切磋求進。

*初探Java Event Model
=======================
首先必須跟各位讀者聲明的是,由於本篇的主角乃Java 1.1之後的
Event Delegation Model(事件委派模式),因此舊有的Java 1.0
Event Model在此將不做任何討論(文後若有出現Java Event Model
,都是指Delegation Model)。再者,本篇文章重點在於探討Event
的實際運\作方式,不在說明個別Event的使用方法,所以遇到關鍵
處大多只是舉例說明,點到為止,至於個別Event的使用方式,大
部分的Java書籍都會提到,筆者就不再贅述。

本篇文章的讀者必須要有一點Java Event Model的基礎,為了讓不
太清楚的讀者也能夠有繼續一探究竟的基礎,所以筆者將以簡短的
篇幅,概略性地描述Java Event-Delegation Model,當然,如果您
早已經熟悉這套Java Event Model,那麼這個單元您可以跳過不看。

學習Event Delegation Model,基本上要把握住兩個基本觀念:

事件的類別
----------
Event Delegation Model定義了許多新的事件類別(主要被放在
java.awt.event這個package),而事件類別的階層關係如圖1所示。
所有事件類別均為java.util.EventObject這個類別的子類別,而這
個類別中有個重要的成員-source(型態為Object),它代表了發生事
件的元件,在Event Delegation Model中就是靠它來找到發生事件
的來源物件,是不可或缺的成員之一,可由getSource()取得發生事
件的元件。

圖1

而緊接著繼承下來的是java.awt.AWTEvent,此類別為java.awt.event
package中所有事件類別的超類別。而這個類別中同樣有個相當重要
的成員-id(為整數型態),它代表了Event的Type,如MouseEvent就可
以表示七種不同型態的滑鼠事件如下: MOUSE_PRESSED、MOUSE_RELEASED
、MOUSE_CLICKED、MOUSE_ENTERED、MOUSE_EXITED、MOUSE_MOVED、
MOUSE_DRAGGED。在Event dispatch的過程中,就是由Event的Type來
決定最後的Event-Handler,可由getID()取出該Event的Type。由此
可知,所有java.awt.event package中所定義的事件類別都具有兩個
最基本也是最重要的屬性-source和id。

委派的機制
----------
在說明事件委派機制之前,必須先弄清楚兩個重要的角色分別是
Event-Source跟Event-Listener。Source代表了發生事件的來源物件,
可能是一個Button或是Frame…,而Listener則代表了事件委派者,也
就是被Source委託來處理Event的被委派者。當某個Source有Event發
生時,Source本身會先收到該Event,接著再把Event交給對應的被委
託者(事件委派者)來處理,所以用來處理Event的Event-Handler必須
被實作在事件委派者。

由圖2中可以看出Source跟Listener之間的關係,而且Source是以
Multicast的方式把Event送到各個Listeners手上; 換言之,在Source
發生的Event,可以交由多個事件委派者來處理(這是Event Delegation
Model的特點之一)。

圖2

再來必須要知道的是,Source跟Listener之間的委託關係是如何建立
的呢?建立的方式很簡單,只要透過Source所提供的addXXXListener
(ListenerClass) (Ex: addActionListener)並傳入對應的ListenerClass
物件[此物件必須實作XXXXListener的介面,否則無法通過編譯],便可
以讓Source和Listener建立委託關係。通常Source會有某些特定可以
處理的Events(例如Button可以處理ActionEvent),只要是Source可以
處理的Event,Source中便會有對應的addXXXListener來提供建立委託
關係的管道,當然相對的removeXXXListener便是解除委託關係的方法
。再次強調,前面所謂的ListenerClass物件,指的就是implements
XXXListener介面的物件,每個java.awt.event package中所定義的
Event都會有對應的Event Listener。

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
以下便是一個簡單的例子(處理ActionEvent):
import java.awt.event.
*;public class ButtonHandler implements ActionListener{
    public void actionPerformed(ActionEvent e){   //實作之Event-Handler
System.out.println(“Action OK!”);
    }
}

-----------------------------------------------------------
import java.awt.*
;public class MyButtonTest{
public static void main(String args[]){
Frame f = new Frame(“Hello”);
Button b = new Button(“test”);
b.addActionListener(new ButtonHandler()); //建立委託關係(註冊)
f.add(b, BorderLayout.CENTER);
f.pack();
f.setVisible(true);
}
}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

最後針對Source以及Listener做個小結論:

Event-Source   |負責接收Events,再dispatch到已經註冊過的listener objects。
               |所以基本上Event Source必須具備註冊的功能(by addXXXListener)

Event-Listener |負責實作出對應之Event Handlers。必須implements java.awt.event
               |package中的Listener interface。

*Event從何而來? 往哪裡去?

聰明的讀者們,相信你們第一次使用Java寫GUI程式時,一定跟筆者
同樣有個疑問,那就是Java的Event究竟從何而來?畢竟,事件驅動是
GUI程式的基本精神之一,然而卻由於Java本身是直譯式(interpret)
語言,大部分Java語言底層的介面都交由Java虛擬機器(Virtual
Machine)這個幕後的大黑手來處理,所以我們從Java的Program(.java)
中,看不到任何像傳統寫Window程式(EX:Win32 SDK)所必須去handle
的Message Loop。當然這對Programmer而言是件好事(因為複雜度減
少),但是追根究底,Java GUI程式的底層,還是會有個Message Loop
,從System Queue或Application Queue中捕捉Message,並一路包裝
到Java這一層來處理。而這就是本篇文章所要探討的主題之一。

我們知道在Win32系統中,當我們執行某個Application(GUI程式),
系統背後會提供這個Application可以使用的System/Application
Queue,程式會不斷到這些Queue來GetMessage(不管是系統或是其他
應用程式送過來的訊息),若發現訊息,就處理之;若沒有訊息,則
把控制權交還給系統。那Java呢? Java的Event Model是不是也有
類似的機制呢?答案是肯定的!

由於java.awt.event 這個package位在java.awt的下一層目錄,所
以我們直接到JDK(必須1.1以上的版本,筆者採用的版本為JDK1.1.6)
所附的Class Library Source中的java/awt這個目錄去瞧瞧,果然
,我們看到了一個叫做EventQueue的類別,從這個類別中我們發現
了兩個Key Points:

1.EventQueue中有個"queue"的私有資料成員,其宣告如下:
  private EventQueueItem queue;

  再來我們看看EventQueueItem的類別定義:
class EventQueueItem {
    AWTEvent event;
    int      id;
    EventQueueItem next;
    EventQueueItem(AWTEvent evt) {
        event = evt;
        id = evt.getID();
    }
}
EventQueueItem中的event跟id不就是Java Event Model中,所有
Event類別 最基本且最必要的資料嗎!而EventQueue中的queue則
是用來存放所有被丟到 Java EventQueue的Events。

2.在EventQueue的建構子當中(如下):
  public EventQueue() {
        queue = null;
        String name = "AWT-EventQueue-" + nextThreadNum();
        new EventDispatchThread(name, this).start();
  }
我們發現了一個Java Event Model中很重要的角色-"EventDispatchThread"
,原來在EventQueue物件被產生的同時,會有一條名叫EventDispatchThread
的Thread物件被產生,這究竟代表什麼呢?各位讀者們,你們已經想到
為什麼要這麼做了嗎?如果還沒想出來,就繼續往下看囉...^O^.
 
由EventDispatchThread的名稱來看,顧名思義,它應該就是我們要
找尋的訊息分派者,每當系統的EventQueue被產生,一條對應的
EventDispatchThread便會同時被產生,並擔任這個EventQueue的
Dispatcher。我們可由new EventDispatchThread時所傳入的第二個
參數--this(也就是EventQueue本身)看出端倪。

接下來我們就直接到EventDispatchThread.java看個究竟吧。首先我
們發現此EventDispatchThread是繼承自Thread,這在意料之中,不過
既然是Thread物件,那當然就直攻run() method囉! 以下是部分
run() method的code:
 
 public void run() {
     while (doDispatch) {
try {
  AWTEvent event = theQueue.getNextEvent();
//theQueue就是之前傳進來的EventQueue
        ...
        Object src = event.getSource();
            if (src instanceof Component) {
                ((Component)src).dispatchEvent(event);
            } else if (src instanceof MenuComponent) {
                    ((MenuComponent)src).dispatchEvent(event);
                ...
    }
...
        }
        catch(...)
...
   }//end while
  }

果然,如我們所料,EventDispatchThread會不斷向EventQueue拿Event
(by theQueue.getNextEvent( );),接著再根據抓回的Event,判斷其
Source(也就是發生Event的來源物件),如果Source為Component物件,
則交給Component物件的dispatchEvent(event)來繼續dispatching的工
作;如果是MenuComponent物件,同理,交由MenuComponent的dispatchEvent
(event)來繼續dispatching的工作。簡言之,EventDispatchThread的
角色就相當於Win32的Message Loop,不僅僅負責抓Message,同時也負
責Message Dispatching的工作。說到這裡,各位讀者是否對Java Event
Model有更深一層的了解了呢?還是根本...愈聽愈模糊了呢? ^o^...沒
關係,喘口氣,我再用一張圖(圖3)來說明這整個流程,希望能加深各
位讀者的印象。

圖3

由圖3,各位應該可以清楚知道,究竟Event從何而來,往哪裡去了吧!
其實說穿了,Java雖然有自己的Event Model,但是一些系統的Message
還是得由底層的Native Window來捕捉(Ex: Mouse/Keyboard),在透過
Java Virtual Machine這一層的包裝後,才將Event送往Java的EventQueue
,以進入Java自己的Event Model。到目前為止,筆者已經解釋過了
Event從何而來? 但是Event往哪裡去了呢?,嚴格說起來,只提了一半
,當EventQueue中的Event被EventDispatchThread  dispatch出去之
後呢?接下來Event會如何被處理呢?其實到了這個階段以後便是Java這
一層的事情了,這部分我們將在文後說明。(PS: postEvent是EventQueue
所提供的method,程式中可以透過此method把Event丟到EventQueue中!)

*EventQueue產生的時機?

以上筆者已經把Java Event Model的核心部分作了說明,但是眼尖的
讀者一定會覺得好奇,EventQueue既然如此重要,那它又是什麼時候
被產生的呢?是在Java這一層被new出來的,還是由Java Virtual Machine
產生的呢?如果以SUN的版本為例,因為筆者在Source中找不到EventQueue
被產生的code,所以在此大膽假設,EventQueue應該是由Java Virtual
Machine所產生,例如:當我們以java.exe classname來執行Java程式
時,Java Virtual Machine會自動判別該Java程式是屬於Console Mode
或是GUI Mode程式,以決定是否要產生系統的EventQueue,當然這只
是我的初步假設,尚未實地去證明它,筆者才疏學淺\,如果各位讀者
清楚SUN對這一層的實作情形,請您務必來信指教,3Q3Q..。

筆者雖然對SUN的EventQueue實作不是很清楚,但是若就這樣矇混過去
,實在也是有點……嗯…啊…我也不知道該怎麼講….^o^! 還好Java
Virtual Machine不是只有SUN做得出來,不知道各位有沒有聽過Kaffe
Virtual Machine(註1)這一號人物?這是一套Open Source的Java
Virtual Machine,筆者就以Kaffe對EventQueue的實作情形來彌補一下吧!

在開始談Kaffe的做法前,我們先談一下SUN對AWT的實作方式,基本上,
SUN 的AWT是採用”Peer-based”的機制,雖然在Java這一層可以輕易得
使用一些視窗元件,然而實際上,每個Java AWT所支援的元件(註2),都
會跟底層Native Window所支援的視窗元件做一個Mapping,例如: Java
中可能會new一個Frame,那麼底層就會有個對應的Window被產生。而這
個Mapping的機制,SUN採用了Peer-based的方式,每個正在執行的Java
AWT元件都會有個對應的peer,透過這個peer介面來跟底層Native Window
要求服務(透過Native Method),所以各位可以看到每個Java AWT的元件
都會import一個對應的peer介面,Ex: Button.java中就會有”import
java.awt.peer.ButtonPeer”,筆者不打算對此多做討論,在此只想讓
各位讀者先有個概念,再來我們要談的是Kaffe的做法。

Kaffe對於AWT的實作部分並沒有完全採用SUN的方式,它用的是比較直
接的機制,透過一個Toolkit類別中的Native Methods,直接跟底層
Native Window溝通,相對於SUN的”Peer-based”,Kaffe省去了一層
Peer的介面,效率自然會比較好,至於SUN為何要採用”Peer-based”
的方式,筆者猜測,這應該是為了”移植性”的考量。呼…. 各位一
定覺得,筆者怎麼講了一堆好像跟Event不相干的東西呢? 其實筆者想
表達的只有幾句話,那就是Kaffe的Toolkit類別中,存在了我們的目
標:EventQueue,而在Toolkit的Static Block中做了一些初始化的動
作,EventQueue就在此時會被產生。為什麼呢?為什麼EventQueue要在
Toolkit類別被載入時產生呢? 這個問題筆者也曾經思考過,各位不妨
也思考一下吧! 其實道理蠻簡單的,因為只要是Java程式(不管是不是
GUI程式),都一定得透過Java Virtual Machine來執行,如果執行的是
Console Mode的程式,自然不需要EventQueue,但如果是GUI Mode的程
式,那EventQueue就必須在程式動到GUI之前被產生,而之前提到Toolkit
中定義了所有會呼叫到底層Native Window的Native Methods,換言之
,只要Java程式中要使用跟Window相關的Native Method時,Toolkit類
別就必須先被載入,而Toolkit類別的載入不就代表EventQueue被產生
了嗎! 透過這種方式,EventQueue自然就只有在Java程式為GUI程式時
才會出現。圖4用一個最簡單的例子(show一個Java Frame)來說明
EventQueue被產生的情況:

圖4 (略)

(1)當MyFrame呼叫show() method時,此時Native System必須有一個對
   應的Window被產生並且被顯示出來(PS:當執行new MyFrame( )時,
   底層的Window並不會立即被產生)。
(2)因為此時必須呼叫Toolkit中所定義的Native Method(createNativeFrame)
   ,所以Toolkit類別會先被載入,而Toolkit被載入的過程中,EventQueue
   就會被產生。
(3)EventQueue被產生時,相對的EventDispatchThread也會被產生。
(4)在Toolkit被載入完成後,開始呼叫createNativeFrame這個Native Method。
(5)最後才呼叫到底層的Native Window System,把對應的Window顯示出來。

講了那麼多有關EventQueue的東西,我們現在就來證明一下,順便瞧瞧在
Console Mode以及GUI Mode下跑的基本Threads有何不同。在此筆者將用
兩個Samples來測試,一個是Console Mode程式,另一個則是GUI Mode程
式,兩個程式會分別執行listAllThreads()這個Method(如下所示),來列
出目前程式中正在執行的Threads。

    public static void showThreadGroup(ThreadGroup tg,String tab){
    int threadNumber,groupNumber;
    if(tg==null) return;
    threadNumber=tg.activeCount();
    groupNumber=tg.activeGroupCount();
    Thread[] threads=new Thread[threadNumber];
    ThreadGroup[] tgs=new ThreadGroup[groupNumber];
    tg.enumerate(threads,false);
    tg.enumerate(tgs,false);
    System.out.println(tab+"ThreadGroup:"+tg.getName());

    for(int i=0;i      System.out.println(tab+" "+threads[i]);
    }
    for(int i=0;i      showThreadGroup(tgs[i],tab+" ");
    }
    }

    public static void listAllThreads(){
    ThreadGroup current,root,parent;                 current=Thread.currentThread().getThreadGroup();                 root=current;parent=root.getParent();
    //find root     while(parent!=null){
      root=parent;
      parent=parent.getParent();
    }

    showThreadGroup(root,"");
    }
----------------------------------------------------------------------
執行的結果如下:

Console Mode  | ThreadGroup:system
              | Thread[Signal dispatcher,5,system]
              | Thread[Reference Handler,10,system]
              | Thread[Finalizer,8,system]
              | null
              | ThreadGroup:main
              |  Thread[main,5,main]
---------------------------------------------------
GUI Mode      | ThreadGroup:system
              | Thread[Signal dispatcher,5,system]
              | Thread[Reference Handler,10,system]
              | Thread[Finalizer,8,system]
              | null
              | null
              | null
              | null
              | ThreadGroup:main
              |  Thread[main,5,main]
              |  Thread[AWT-EventQueue-0,6,main]
              |  Thread[SunToolkit.PostEventQueue-0,5,main]
              |  Thread[AWT-Windows,5,main]
---------------------------------------------------
而此結果可以看出,GUI Mode程式的main ThreadGroup會比Console Mode
多了3個Threads,而Thread[AWT-EventQueue-0,6,main]這條Thread即為
EventDispatchThread,它的Priority會比一般的Thread多1。至於
SunToolKit這條Thread實際上跟sun.awt這個package以及peer-based的
機制有關,在此筆者則不多做討論。有關EventQueue的介紹就到此告一
段落了,接下來將繼續筆者之前只說了一半的主題”Event 往哪裡去了?”
,也就是Java Event在被EventDispatchThread送出後的Dispatch旅程。 (林錦陽)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www1.rosoo.net/a/200403/1401.html]
本文出处: 作者:林錦陽
顶一下
(1)
100%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容