網頁

2009年12月20日 星期日

[GWT] 利用 HandlerManager 實作共用的 Event Bus

GWT 從版本 1.6 開始提供了新的 Event Model - Handler,並捨棄了原先的 Listener 方式。

關於 Listener 與 Handler 的差別,我推荐看這篇文章: GWT’s new Event Model – Handlers in GWT 1.6 [1]。

在這篇文章中,我將兩者與本篇文章有關的部份做介紹:


Listener:
  1. 對於每一個 Event Source 會有一個對應的 Listener,例如 MouseListener, KeyboardListener...等。
  2. 在每一個 Widget 中,針對每一種 Event Source 的 Listener,都會用一個獨立的 ListenerCollection 去紀錄。
Handler:
  1. 每一個 Event Type 都會有一個對應的 Handler,因此原先 MouseListener 相關的事件,將會被重新區分成 MouseDownHandler, MouseUpHandler...等。這樣做的一個額外的好處是,當只需要針對一種 Event Type 做處理時,實作 Handler 的類別只需要 over-ridding 一個對應的 method,而不需要像寫 Listener 時,仍然需要 over-ridding 其他沒用到的 method。
  2. 每一個 Widget 中,都只會有一個 HandlerManager,負責紀錄所有的 Event Handler。

由於 HandlerManager 能夠針對不同的 Event,dispatch 其對應的 Handler 去處理,因此我們也可以利用 HandlerManager 來做為整個 Web App 共用的 Event Bus。[2], [3]


先說明一下本篇文章欲使用的例子:
在一個 Web App 上,有許多的 Panel,我們現在希望當按下 A Panel 上的 Button 時,B Panel 上的 TextBox 會輸出文字。

在原本的做法裡,我們會讓 A panel 實作 ClickHandler,當 A panel 收到事件時,就去呼叫 B panel 所提供的方法。

這個作法會使得 A panel 必須持有 B panel 的 reference(就 UML 的角度來說就是 A 對 B 有 Aggregation 關係),這樣會使得 A, B 之間的耦合度高,造成未來擴充、修改的困難。

接下來就是解決辦法啦!

  1. 首先,於 EntryPoint 建立 HandlerManager 物件,並且將 Reference 傳給所有的 sub-component,這個部份可以利用 Constructor 達成。
    HandlerManager eventBus = new HandlerManager(null); 
  2. 接下來為每一個欲處理的事件寫一組 Event / Handler
    public class FooEvent extends GwtEvent<FooEvent.FooHandler> {
    
        public interface FooHandler extends EventHandler{
            void onFoo(FooEvent event);
        }
    
        public static final GwtEvent.Type<FooHandler> TYPE =
                new GwtEvent.Type<FooHandler>();
    
        @Override
        protected void dispatch(FooHandler handler) {
            handler.onFoo(this);
        }
    
        @Override
        public GwtEvent.Type<FooHandler> getAssociatedType() {
            return TYPE;
        }
    }
    
  3. 然後在 B panel 裡頭寫上事件處理,並且向 Event Bus 註冊。
    @Override
    protected void onLoad(){
        // register event handler
        fooRegistration = eventBus.addHandler(FooEvent.TYPE, new FooHandler(){
            @Override
            public void onFoo(FooEvent event) {
                textBox.setText("A panel's button click!");
            }
        });
    }
    
    @Override
    protected void onUnload(){
        // unregister event handler
        fooRegistration.removeHandler();
    }
    
  4. 最後是 A panel 的事件觸發,當按下按鈕時:
    eventBus.fireEvent(new FooEvent());
    

這樣一來,A 與 B 彼此可以完全不知道對方的存在,將可以大大的降低耦合度。

參考資料:
[1]: GWT’s new Event Model – Handlers in GWT 1.6
[2]: Google Web Toolkit Architecture: Best Practices For Architecting Your GWT App
[3]: GWT Event Bus Discussions
[4]: 1.6 版多了什麼新東西?

[Java] 製作 Sign Applet

前言:

Applet 是用於網頁上的小程式,基於安全理由,它不允許你做任何的 IO 操作,(存取本機電腦 及 網路連線...等,只有存放該網頁的 server 例外)否則一旦開了惡意的 applet,電腦就如同中了木馬一般。

這種概念稱為 Sandbox,只允許你在盒子內部操作,所以無論你在盒子裡做了什麼事,都不會影響到盒子外的世界。如果有特殊的需求,可以使用 Sign Applet,在開啟程式前會先出現確認視窗,待使用者同意後,程式便能夠有較高的存取權限。

製作 Sign Applet 方法:

  1. 產生 key:
    keytool -genkey -keyalg RSA -alias "key_name"


    1. 如果 key store 目前並不存在,
      則必須設定 keystore 密碼;
      反之,如果先前已經設定過密碼,
      則此時就需要輸入先前所設定的密碼。
    2. 接著必須輸入一些基本資料,
      如: 姓名, 單位, 國碼, ... 等等,
      此處就不贅述了。
    3. 最後輸入 key 密碼,即可順利產生 key。

  2. 將 key 加入 applet 中:
    jarsigner "applet.jar" "key_name"
    此時需要依序輸入 key store 密碼與 key 密碼。 
  3.  Sign Applet 製作完成,
    此時連上嵌入此 Sign Applet 的網頁,
    即會跳出確認視窗,詢問是否同意進行操作。 
延伸說明:
  1. 在產生 keytool 時,如果沒有特別指定,
    將會存放在此: ~/.keystore
    註: 此處以 ubuntu 為例, Windows 作業系統將會在其他的位址。
    如果需要額外指定存放位址, 需加上參數 -keystore "path"。
  2. 列出目前已有的 key:
    keytool -list
  3. 檢查 jar 是否已經完成 Sign
    jarsigner -verify "applet.jar"

Java Access Modifiers

Visibility
public
protected
package
(default)
private
same class
Y
Y
Y
Y
non-subclass in the same package
Y
Y
Y
N
non-subclass outside the package
Y
N
N
N
subclass in the same package
Y
Y
Y
N
subclass outside the package
Y
Y
N
N

  1. Class 只有 public 和 package 兩種 modifiers。
  2. 表格中 subclass 的 visiblility 指的是使用繼承方式 access 該 member
  3. protected 所修飾之 member,若其 subclass outside the package,則在 subclass 中,該 member 為 private。
  4. sub-package 不算是 same package

Java Thread

Java 的 Thread 和 Process 一樣,共有五個 state,
分別是 New, Runnable, Running, Waiting/blocked/sleeping, Dead。

New - 當 thread's instance 已經建立,但是尚未呼叫 start(),
此時,這個 thread 仍然稱做 not alive。

(其他的 state 和 Process 幾乎相同,這裡就省略了)


接下來介紹有關操作 thread 所需要呼叫的 method。

start() - Thread's non-static method
將該 thread 從 New state 移至 Runnable state,
至此,該 thread 才成為一個 alive thread。

無論如何,同一個 thread 物件都只能 start() 一次,
否則將會產生 RuntimeException;
然而同一個 Runnable interface,
則可以經由 Thread's Constructor,建立並啟動多個 thread。

yield() - Thread's static method
將目前的 thread 由 Running state 移至 Runnable,
並且由 Runnable state 中,
選一 "相同" priority 的 thread 進入 Running state,
故有可能會選到原先的 thread。

由於 JVM 對於 priority 的實作未必確實為 1~10,
可能會有多個設定值對應到同一個 priority 的情形,
所以需要特別注意。

sleep() - Thread's static method
將目前的 thread 由 Running state 移至 Sleeping,
並等待指定的時間後,才會進入 Runnable。

並不是時間一到立刻進入 Running,
所以通常該 thread 的休息時間會較長於指定的時間。

若是在 synchronize block 中呼叫 sleep(),
則 thread 並不會釋放自己所擁有的資源。

join() - Thread's non-static method
暫停目前 thread 的執行,並等待指定的 thread 執行完畢後,
此 thread 才會回到 runnable state。

以下三個指令都必須在 synchronize block 中才可呼叫。

wait() - Object's non-static method
目前 thread 進入 Waiting,暫時釋放指定的資源。

當此 thread 經由 notify() or notifyAll() 叫醒後,
會送出 InterruptedException()。

另外,wait() 也有 over-loading 的版本,
提供最長等待時間作為參數。

notify() - Object's non-static method
叫醒某個正等待此物件之 thread。

叫醒的順序與 thread 進入 Wainting 的時間無關。

notifyAll() - Object's non-static method
叫醒所有正在等待此物件之 thread。

註: notify 與 notifyAll 所叫醒的 thread 只是回到 runnable,
而並不會立刻執行。          

Java Assertion

Assertion 是 Java 1.4 後才有的一個新特色,作用是幫助 debug。

例如在以下的程式中,num 的值應該要大於 0,則你可以使用 Assertion 來幫助你確認。

private void methodA(int num) {
    assert(num>0);
    useNum(num+x);
}

簡單的說,當 assert 後方的引數為 false 時,則程式就會丟出 AssertionError。

另一種用法是提供一些訊息給 Assertion,當出現 AssertionError 時,
這些訊息就會顯示在 Stack Trace 上。

例: assert(num>0):"num must greater than 0";
冒號後方的引數可為 primitive 或 Object。


Asertion 預設是不啟用,若是要啟用 Assertion 的功能,
在編譯時必須使用 "javac -source 1.4"。
(預設是 "javac -source 1.3")

目前在 6.0 版 Compile 時,已經不需要增加額外的參數了。

而在執行時則用 "java -ea" 或著 "java -enableassertions",
同樣的預設值也是不啟用。

如果只須啟用部分 class 或著部分 package 的 Assertions 功能,
可以用這樣的方式 "java -ea:com.foo.Bar",
或著只有某 package 不啟用 "java -ea -da:com.foo",
(-da 為 -disableassertions 的縮寫)
另外還有兩個參數為 "-dsa" 和 "-esa",
分別代表 "disable systema ssertion" 和 "enable systema ssertion"。

[註] Java 中的 package 並無階層的關係,
例如 com.foo 以及 com.foo.xxx,其中並沒有任何的關係,
它們只是在檔案系統中使用相同的目錄。

但是 Assertion 是唯一的例外,上述的 "java -ea -da:com.foo",
除了啟動 com.foo 的 Assertion 機制, com.foo 的 subpackage 也會啟動。

在 Assertion 未啟用的情況下,JVM 會忽略 Assertion 的處理。


Assettion 在以下的情況不建議被使用:

  1. 用來驗證 public method 的 argument。
    這是因為 public method 可以被任何人呼叫, 而在 Assetion 未啟用的情況下, 即使是不正常的參數, 程式仍然會繼續執行,應該改用例外處理。
    只適用於 private method 的 argument。
  2. 用來驗證 public method 的 argument。
    原因同上。
  3. 用在會導致 side effect 的情況下。
    程式在 assertion 的檢查過程中,不應該改變程式目前的 state。

Java Stream 簡介

Java 的 stream 相關類別很多,讓人目不暇給,
FileInputStream, FileOutputStream, FileReader, FileWriter
光是 File 開頭的就有這四種,
其他開頭的還有 Object, Data, Print, Buffered....,
另外還有兩個 InputStreamReader 和 OutputStreamWriter 這兩個奇怪東西。


到底我該用的是什麼? 它們之間又有什麼差別?

先從 Reader/Writer 和 InputStream/OutputStream 說起好了,

Reader 是讀,InputStream 也是讀,
兩著乍看之下都是在做讀資料,
其間差別究竟何在?

根據 API 上的說法,InputStream 是以 byte 為單位,
而 Reader 則是以字元為單位,並且會根據編碼 decode。
當然,OutputStream 與 Writer 的差別也是同理。


再來討論這些 streams 的另一個差別,
在前面所提到的所有類別中,
只有 File 開頭類別的沒有 "以其它 Stream 類別為參數" 的 Constructor。
(以下就只以 OutputStream 為例)

這是因為只有 FileOutputStream 是直接在跟 File 溝通,
其他的 Stream 都只是將資料寫入 Constructor 參數中的 OutputStream。


那麼,我們為什麼不只用 FileOutputStream 就好?

你可以去看看 FileOutputStream 提供了哪些 method,
會發現它只提供了有關 byte array 的操作,
如果你想要輸出一行字給 FileOutputStream,你可能會自己處理得很辛苦。

所以啦,Java 提供了一系列好用的 Stream,
把最底層,只提供 byte array 操作的 Stream 包裝起來,
讓你操作起來更直覺,
因此想要寫入字串可以用 PrintStream,
想要把各種基本資料型態,
如 int, double...等的 "值" 輸出,可以用 DataOutputStream。
想要把整個物件 Serialize,則可以用 ObjectOutputStream。

或許你有注意到,
PrintWriter 也有提供檔名和 File 物件做為 Constructor 的參數呀!
但這只是為了你使用上的方便,
實際上 PrintWriter 還是會自己宣告一份 FileOutputStream 與檔案溝通。


而 InputStreamReader 和 OutputStreamWriter,
則是用來將 Stream 包裝成 Reader/Writer,
並且由這兩個物件做編碼的處理。


另外,這些包裝是可以多層的,
例如 ObjectOutputStream -> BufferedOutputStream -> FileOutputStream,
或著 PrintWriter -> OutputStreamWriter -> FileOutputStream。


如果你想要惡搞,
用 ObjectOutputStream 去包裝 DataOutputStream,
甚至是 ObjectOutputStream -> ObjectOutputStream -> ObjectOutputStream -> ....
這樣在宣告與執行上也不會有問題。
(這樣也算是錯誤,不過這是程式邏輯上的錯誤)