SeamFramework.orgCommunity Documentation

章 1. 開始使用 Web Bean

1.1. 您的第一個 Web Bean
1.2. Web Bean 是什麼?
1.2.1. API 類型、綁定類型以及依賴注入
1.2.2. 建置類型(Deployment type)
1.2.3. Scope
1.2.4. Web Bean 名稱和 Unified EL
1.2.5. 攔截器綁定類型
1.3. 哪種物件屬於 Web Bean?
1.3.1. 基本的 Web Bean
1.3.2. 企業級的 Web Bean
1.3.3. Producer method
1.3.4. JMS 端點(endpoints)

您是否已準備好開始編寫您的第一個 Web Bean 了呢?或是您針對於 Web Bean 本身的規格還是存有著一些疑問呢?不過好消息就是您從以前到現在可能早就已經編寫並使用了上百甚至是上千個 Web Bean 了。您可能根本已經不記得您所編寫的第一個 Web Bean 為何了。

我們能夠很明確地告訴您,在絕大部分的情況下,所有含有不接受參數的 constructor 的 java class 都是個 Web Bean。這包含了所有的 JavaBean。另外,所有 EJB 3-style 的 session bean 也都屬於 Web Bean。當然,您先前所編寫的 JavaBean 和 EJB 皆無法有效利用 Web Bean 規格所定義的新服務,不過您卻可將它們全部作為 Web Bean 來使用 — 將它們注入其它 Web Bean 中,透過 Web Bean XML 配置功能來配置它們,甚至是加入攔截器(interceptor)與裝飾器(decorator) — 無須變動到您現有的程式碼。

假設我們目前有兩個使用於各種應用程式中多年的 Java class。第一個 class 會將一個字串剖析入一列句子中:

public class SentenceParser {

    public List<String
> parse(String text) { ... }
}

第二個 class 則是個外部系統的無狀態 session bean 前端,並且它可將句子由一種語言翻譯成另一種語言:

@Stateless

public class SentenceTranslator implements Translator {
    public String translate(String sentence) { ... }
}

Translator 為本地介面:

@Local

public interface Translator {
    public String translate(String sentence);
}

不巧的是,我們沒有一個可翻譯整個文字文件的現有 class。所以讓我們來編寫一個能夠完成這項工作的 Web Bean 吧:

public class TextTranslator {

    
    private SentenceParser sentenceParser;
    private Translator sentenceTranslator;
    
    @Initializer
    TextTranslator(SentenceParser sentenceParser, Translator sentenceTranslator) {
        this.sentenceParser = sentenceParser;
        this.sentenceTranslator = sentenceTranslator;
    }
    
    public String translate(String text) {
        StringBuilder sb = new StringBuilder();
        for (String sentence: sentenceParser.parse(text)) {
            sb.append(sentenceTranslator.translate(sentence));
        }
        return sb.toString();
    }
    
}

我們能夠藉由將注入一個 TextTranslator 的 instance 注入 Web Bean、Servlet 或是 EJB 中來取得這個 instance:

@Initializer

public setTextTranslator(TextTranslator textTranslator) {
    this.textTranslator = textTranslator;
}

另外,我們也可藉由直接調用 Web Bean 管理員的一個 method 來取得一個 instance:

TextTranslator tt = manager.getInstanceByType(TextTranslator.class);

不過請稍等:TextTranslator 並沒有一個無參數的 constructor!這樣它還是個 Web Bean 嗎?一個沒有無參數 constructor 的 class 若有個被標記了 @Initializer 的 constructor 的話,它還是能夠是個 Web Bean。

就如您所猜測地,@Initializer 這個標記和依賴注入有關係!@Initializer 可套用至一個 Web Bean 的 constructor 或是 method 來指示 Web Bean 管理員在例示 Web Bean 時去調用該 constructor 或 method。Web Bean 管理員會將其它 Web Bean 注入至該 constructor 或 method 的參數中。

當系統初始化時,Web Bean 管理員必須驗證是否有正好一個滿足所有注入點的 Web Bean 存在。在我們的範例中,若沒有可用的 Translator 實做 — 若 SentenceTranslator EJB 沒有被建置 — Web Bean 管理員便會回傳一個 UnsatisfiedDependencyException。若有超過一個可使用的 Translator 實做,那麼 Web Bean 管理員便會回傳一個 AmbiguousDependencyException

所以 Web Bean 到底是什麼?

Web Bean 是個包含了商業邏輯的應用程式 class。Web Bean 可由 Java 程式碼被直接地調用,或是它亦可透過 Unified EL 來引動。Web Bean 可存取交易性的資源。Web Bean 之間的相依性是透過 Web Bean 管理員來自動管理的。大部分的 Web Bean 都屬於 stateful(有狀態)contextual(語意式) 的。Web Bean 的生命週期(lifecycle)總是透過 Web Bean 管理員來管理的。

讓我們向後看,「contextual(語意式)」到底代表什麼意思?因為 Web Bean 能夠是有狀態的,而最重要的是我們有哪個 bean instance。和無狀態的元件模型(例如無狀態的 session bean)或單元件模型(例如 servlet 或是單獨的 bean)不同的是,對於不同的 Web Bean 客戶端而言,它們會看見不同狀態的 Web Bean。客戶端可看見的狀態基於客戶端參照的是 Web Bean 的哪個 instance。

不過,就和無狀態或是單獨的模型一樣,並與有狀態的 session bean 不同,客戶端無法藉由明確建立和刪除 instance 來控制 instance 的生命週期。反之,Web Bean 的 scope 可決定:

Web Bean 應用程式中的某個執行緒可能會有個和該 Web Bean 的 scope 關聯的 active context。這個 context 對於執行緒來說可能會是獨一無二的(例如,若 Web Bean 為 request scoped 的話),或是它亦有可能共享於其它特定執行緒之間(例如,若 Web Bean 為 session scoped 的話)或甚至是共享於所有其它執行緒之間(若它是 application scoped 的話)。

相同 context 的客戶端(例如其它 Web Bean)將會看見相同的 Web Bean instance。不過不同 context 的客戶端則會看見不同的 instance。

Contextual model 的其中一個主要優點就是它允許有狀態的 Web Bean 能被視為是一項服務!客戶端不需要擔心如何管理它所使用的 Web Bean 的生命週期,它甚至不需要知道這個生命週期為何。Web Bean 會透過傳送訊息來進行互動,並且 Web Bean 的實做也能定義它們自己的狀態的生命週期。Web Bean 為鬆散耦合(loosely coupled)的,因為:

我們能夠在不影響其它 Web Bean 實做的情況下將一個 Web Bean 取代為另一個實做相同 API 並有不同生命週期(不同 scope)的 Web Bean。事實上,Web Bean 可在建置時定義一項用來置換 Web Bean 實做的複雜功能,就如我們將在 節 4.2, “建置類型” 中所見。

請注意,並非所有 Web Bean 的客戶端都是 Web Bean。像是 Servlet 或訊息導向的 Bean 之類的其它物件 — 無法被注入,並屬於 contextual object — 也能透過注入來取得 Web Bean 的參照。

根據規格:

Web Bean 包含著:

  • 一組(非空的)API 類型(API type)

  • 一組(非空的)綁定標記類型(binding annotation type)

  • 一個 scope

  • 一個建置類型(deployment type)

  • 亦可選擇性地包含著一組 Web Bean 名稱

  • 一組攔截器綁定類型(interceptor binding type)

  • 一個 Web Bean 實做(Web Bean implementation)

讓我們來看看這些術語對 Web Bean 開發人員來說代表什麼。

Web Bean 通常會透過依賴注入(dependency injection)來取得其它 Web Bean 的參照。任何被注入的屬性都會指定一個「合同(contract)」,該合同必須被 Web Bean 滿足才可被注入。這個合同為:

API 是個用戶定義的 class 或介面。(若 Web Bean 是個 EJB session bean 的話,那麼 API 類形便是 @Local 介面或是 bean-class 的 local view)。綁定類型代表一些客戶端可見的語意,這些語意可藉由一些 API 實做來滿足。

綁定類型是透過用戶定義、本身已被標記為 @BindingType 的標記來表示的。比方說,以下注入點含有一個 PaymentProcessor API 類型以及 @CreditCard 綁定類型:

@CreditCard PaymentProcessor paymentProcessor

若在注入點沒有綁定類型被明確指定的話,那麼預設的綁定類型 @Current 就會被假設。

Web Bean 管理員會針對於各個注入點搜尋滿足合同(實做 API 並擁有所有綁定類型)的 Web Bean,然後將該 Web Bean 注入。

下列 Web Bean 的綁定類型為 @CreditCard 並且實做了 PaymentProcessor 這個 API 類型。因此它可被注入至範例的注入點中:

@CreditCard

public class CreditCardPaymentProcessor 
    implements PaymentProcessor { ... }

若 Web Bean 不明確指定一組綁定類型的話,它便會只有一個綁定類型:也就是預設的綁定類型 @Current

Web Bean 會定義一個複雜不過不難理解的 resolution algorithm(解析運算法),它可在有超過一個滿足特定合同的 Web Bean 存在的情況下協助 container 決定該怎麼作。我們將在 章 4, 依賴注入(Dependency injection) 中詳細討論。

我們已經看過 JavaBean、EJB 和一些其它的 Java class 都能屬於 Web Bean。不過 Web Bean 到底是哪種物件呢?

Web Bean 的規格顯示了所有 EJB 3 類型的 session 和 singleton 的 bean 都屬於企業級的 Web Bean。訊息導向的 bean 則不屬於 Web Bean — 因為它們不會被注入其它的物件中 — 不過它們能夠有效利用 Web Bean 大部分的功能,這包括依賴注入(dependency injection)以及攔截器(interceptor)。

所有沒有 wildcard 類型參數或是類型變數的企業級 Web Bean 的本地介面以及它所有的 superinterface 都屬於企業級 Web Bean 的 API 類型。若 EJB bean 有個 bean class local view 的話,那麼這個 bean class 以及它所有的 superclass 也都會是個 API 類型。

有狀態的 session bean 應宣告一個無參數的 remove method 或是一個標記為 @Destructor 的 remove method。Web Bean 管理員會調用這個 method 來在它的生命週期結束時刪除有狀態的 session bean instance。這個 method 亦稱為企業級 Web Bean 的 destructor method。

@Stateful @SessionScoped

public class ShoppingCart {
    ...
    
    @Remove
    public void destroy() {}
}

所以我們該何時使用企業級的 Web Bean 何時使用基本 Web Bean 呢?每當我們需要 EJB 所提供的進階企業級服務時,例如:

當需要以上服務時我們便應使用企業級的 Web Bean。當我們不需要任何的這些服務時,使用基本的 Web Bean 即可。

許多 Web Bean(包括任何 session 或 application scoped 的 Web Bean)都能被並行存取(concurrent access)。因此,EJB 3.1 所提供的並行管理(concurrency management)特別地有幫助。大部分的 session 和 application scoped 的 Web Bean 都應屬於 EJB。

持有重量級資源之參照或持有許多內部狀態的 Web Bean 皆可受益于支援 passivation 和類別儲備的 EJB @Stateless/@Stateful/@Singleton 模型所定義並由進階 container 所管理的生命週期。

最後,一般來講,何時需要使用到 method 層級的交易性管理、method 層級的安全性、計時器、遠端 method 或是非同步的 method 其實都是非常顯而易見的。

通常從基本的 Web Bean 開始會較容易,然後只要再透過附加一個 @Stateless@Stateful 或是 @Singleton 標記來將它轉換為一個 EJB 即可。