SeamFramework.orgCommunity Documentation

Web Beans: Java Contexts and Dependency Injection

The new standard for dependency injection and contextual state management


Note
I. 使用概念上的物件
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)
2. JSF 網站應用程式範例
3. Web Beans, the Reference Implementation of JSR-299
3.1. Using JBoss AS 5
3.2. Using Apache Tomcat 6.0
3.3. Using GlassFish
3.4. numberguess 範例
3.4.1. The numberguess example for Tomcat
3.5. 轉譯器範例
4. 依賴注入(Dependency injection)
4.1. 綁定標記
4.1.1. member 和綁定標記
4.1.2. 綁定標記的組合
4.1.3. 綁定標記和 producer method
4.1.4. 預設綁定類型
4.2. 建置類型
4.2.1. 啟用 deployment type
4.2.2. Deployment type 優先權
4.2.3. 範例 deployment type
4.3. 修正相依性不足(unsatisfied dependencies)的問題
4.4. 客戶端代理伺服器(Client proxies)
4.5. 透過程式性的搜尋來取得一個 Web Bean
4.6. Lifecycle callback、@Resource@EJB@PersistenceContext
4.7. InjectionPoint 物件
5. Scope 與 context
5.1. Scope type
5.2. 內建 scope
5.3. conversation scope
5.3.1. Conversation demarcation(對話區分)
5.3.2. Conversation propagation(傳播)
5.3.3. Conversation timeout(逾時)
5.4. dependent pseudo-scope
5.4.1. @New 標記
6. Producer method
6.1. producer method 的 scope
6.2. 注入 producer method
6.3. 使用 @New 和 producer method
II. 開發鬆散耦合(loosely-coupled)的程式碼
7. 攔截器(Interceptor)
7.1. 攔截器綁定
7.2. 實做攔截器
7.3. 啟用攔截器
7.4. 與成員綁定的攔截器
7.5. 多重攔截器綁定標記
7.6. 攔截器綁定類型的 inheritance
7.7. 使用 @Interceptors
8. 裝飾器(Decorators)
8.1. Delegate(類別;表示委派)屬性
8.2. 啟用裝飾器
9. 事件(Events)
9.1. 事件觀察器(Event observers)
9.2. 事件產生器(Event producers)
9.3. 動態式地註冊觀察器
9.4. member 的事件綁定
9.5. 多重事件綁定(Multiple event binding)
9.6. 交易觀察器(Transactional observers)
III. 最大程度地使用強類型(strong typing)
10. 主要探討模板(Stereotypes)
10.1. 主要探討模板的預設 scope 和建置類型
10.2. 利用主要探討模板來限制 scope 與 type
10.3. 主要探討模板的攔截器綁定
10.4. 主要探討模板的名稱預設
10.5. 標準主要探討模板
11. 專門化(Specialization)
11.1. 使用 specialization
11.2. Specializarion 的優點
12. 使用 XML 來定義 Web Bean
12.1. 宣告 Web Bean class
12.2. 宣告 Web Bean metadata
12.3. 宣告 Web Bean 成員
12.4. 宣告內嵌式(inline)Web Bean
12.5. 使用 schema
IV. Web Beans 與 Java EE 應用系統
13. Java EE 整合
13.1. 將 Java EE 資源注入 Web Bean 中
13.2. 透過一個 Servlet 來調用 Web Bean
13.3. 透過訊息導向的 Bean 來調用 Web Bean
13.4. JMS 端點
13.5. 封裝和建置
14. 延伸 Web Bean
14.1. Manager 物件
14.2. Bean class
14.3. Context 介面
15. 接下來的步驟
V. Web Beans Reference
16. Application Servers and environments supported by Web Beans
16.1. Using Web Beans with JBoss AS
16.2. Glassfish
16.3. Tomcat (or any plain Servlet container)
16.4. Java SE
16.4.1. Web Beans SE Module
17. JSR-299 extensions available as part of Web Beans
17.1. Web Beans Logger
17.2. XSD Generator for JSR-299 XML deployment descriptors
A. 將 Web Bean RI 整合入其它環境中
A.1. Web Beans RI SPI
A.1.1. Web Bean 搜尋
A.1.2. EJB services
A.1.3. JPA services
A.1.4. Transaction Services
A.1.5. The application context
A.1.6. Bootstrap and shutdown
A.1.7. JNDI
A.1.8. 資源載入
A.1.9. Servlet injection
A.2. 與 container 的合同

JSR-299 has recently changed its name from "Web Beans" to "Java Contexts and Dependency Injection". The reference guide still refers to JSR-299 as "Web Beans" and the JSR-299 Reference Implementation as the "Web Beans RI". Other documentation, blogs, forum posts etc. may use the new nomenclature, including the new name for the JSR-299 Reference Implementation - "Web Beans".

You'll also find that some of the more recent functionality to be specified is missing (such as producer fields, realization, asynchronous events, XML mapping of EE resources).

Web Beans(JSR-299)規格為 Java EE 環境定義了一組簡化應用程式開發的服務。Web Bean 針對於現有的 Java 元件類型(包含 JavaBeans 以及 Enterprise Java Beans)提供了增強的生命週期與互動模型(interaction model)。為了補充傳統的 Java EE 程式撰寫模型(Programming Model),Web Bean 服務提供了:

依賴注入和 contextual 生命週期管理這兩者組合起來可讓一個不熟悉的 API 的用戶無須過問下列問題:

Web Bean 只會指定它所依賴的 Web Bean 的類型和語意。它無須知道實際的生命週期、具體的實做、執行緒模型,或是任何它所依賴的 Web Bean 的其它客戶端。更好的是,它所依賴的 Web Bean 的具體實做、生命週期和執行緒模型可根據建置的情況來改變,並且不影響到任何的客戶端。

事件、攔截器以及裝飾器可增強在此模型中所繼承的 loose-coupling(鬆散結合性):

最重要的是,Web Bean 以一種 typesafe 的方式來提供了所有的這些功能。Web Bean 從不使用基於字串的識別符號(identifier)來斷定共同協作的物件如何相輔相成。雖然 XML 也是選項之一不過卻很少被使用到。取而代之,Web Bean 使用了 Java 物件模型中可使用的 typing 資訊並統合了一個稱為 binding annotations(綁定標記)的新格式,它可將 Web Bean、它們的相依性、它們的攔截器和裝飾器以及它們的事件用戶聯繫在一起。

Web Bean 服務為通用的並且適用於下列位於 Java EE 環境中的元件類型:

Web Bean 甚至提供了必要的整合點來讓未來 Java EE 規格或是非標準架構所定義的其它類型元件能夠利落地與 Web Bean 整合、有效利用 Web Bean 服務,並與任何其它類型的 Web Bean 進行互動。

Web Bean 受到了幾個現有的 Java framework(包括 Seam、Guice 以及 Spring)的影響。不過,Web Bean 擁有它自己獨特的特性:比 Seam 還要完善的 typesafe、比 Spring 更 stateful 而沒有那麼地以 XML 為中心,並且針對於網站和企業級的應用程式上的處理能力比 Guice 完善。

最重要的是,Web Bean 是一個能與 Java EE 以及可使用崁入式的 EJB Lite 的任何 Java SE 環境整合的 JCP 標準。

內容目錄

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)
2. JSF 網站應用程式範例
3. Web Beans, the Reference Implementation of JSR-299
3.1. Using JBoss AS 5
3.2. Using Apache Tomcat 6.0
3.3. Using GlassFish
3.4. numberguess 範例
3.4.1. The numberguess example for Tomcat
3.5. 轉譯器範例
4. 依賴注入(Dependency injection)
4.1. 綁定標記
4.1.1. member 和綁定標記
4.1.2. 綁定標記的組合
4.1.3. 綁定標記和 producer method
4.1.4. 預設綁定類型
4.2. 建置類型
4.2.1. 啟用 deployment type
4.2.2. Deployment type 優先權
4.2.3. 範例 deployment type
4.3. 修正相依性不足(unsatisfied dependencies)的問題
4.4. 客戶端代理伺服器(Client proxies)
4.5. 透過程式性的搜尋來取得一個 Web Bean
4.6. Lifecycle callback、@Resource@EJB@PersistenceContext
4.7. InjectionPoint 物件
5. Scope 與 context
5.1. Scope type
5.2. 內建 scope
5.3. conversation scope
5.3.1. Conversation demarcation(對話區分)
5.3.2. Conversation propagation(傳播)
5.3.3. Conversation timeout(逾時)
5.4. dependent pseudo-scope
5.4.1. @New 標記
6. Producer method
6.1. producer method 的 scope
6.2. 注入 producer method
6.3. 使用 @New 和 producer method

您是否已準備好開始編寫您的第一個 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 即可。

現在我們將採用一個完整的範例來描述這些概念。我們將要為一個使用 JSF 的應用程式實做用戶的登錄/登出。首先,我們將要定義一個 Web Bean 來在登錄時保持已輸入的用戶名稱與密碼:

@Named @RequestScoped

public class Credentials {
        
    private String username;
    private String password;
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    
}

這個 Web Bean 已透過下列 JSF 格式綁定至登錄提示:

<h:form>

    <h:panelGrid columns="2" rendered="#{!login.loggedIn}">
        <h:outputLabel for="username"
>Username:</h:outputLabel>
        <h:inputText id="username" value="#{credentials.username}"/>
        <h:outputLabel for="password"
>Password:</h:outputLabel>
        <h:inputText id="password" value="#{credentials.password}"/>
    </h:panelGrid>
    <h:commandButton value="Login" action="#{login.login}" rendered="#{!login.loggedIn}"/>
    <h:commandButton value="Logout" acion="#{login.logout}" rendered="#{login.loggedIn}"/>
</h:form
>

實際的工作是由 session 導向的 Web Bean 所完成的,這個 Web Bean 保留了有關於目前已登入用戶的相關資料並將 User 這個 entity 提供給其它 Web Bean:

@SessionScoped @Named

public class Login {
    @Current Credentials credentials;
    @PersistenceContext EntityManager userDatabase;
    private User user;
    
    public void login() {
            
        List<User
> results = userDatabase.createQuery(
           "select u from User u where u.username=:username and u.password=:password")
           .setParameter("username", credentials.getUsername())
           .setParameter("password", credentials.getPassword())
           .getResultList();
        
        if ( !results.isEmpty() ) {
           user = results.get(0);
        }
        
    }
    
    public void logout() {
        user = null;
    }
    
    public boolean isLoggedIn() {
       return user!=null;
    }
    
    @Produces @LoggedIn User getCurrentUser() {
        return user;
    }
}

當然,@LoggedIn 是個綁定標記:

@Retention(RUNTIME)

@Target({TYPE, METHOD, FIELD})
@BindingType
public @interface LoggedIn {}

現在,任何其它 Web Bean 都可輕易地注入目前的用戶:

public class DocumentEditor {


    @Current Document document;
    @LoggedIn User currentUser;
    @PersistenceContext EntityManager docDatabase;
    
    public void save() {
        document.setCreatedBy(currentUser);
        docDatabase.persist(document);
    }
    
}

但願此範例能針對於 Web Bean 程式撰寫模型(Programming Model)提供相關益處。在下個章節當中,我們將會更深入地探討 Web Bean 依賴注入(Web Beans dependency injection)。

The Web Beans is being developed at the Seam project. You can download the latest developer release of Web Beans from the the downloads page.

Web Beans comes with a two deployable example applications: webbeans-numberguess, a war example, containing only simple beans, and webbeans-translator an ear example, containing enterprise beans. There are also two variations on the numberguess example, the tomcat example (suitable for deployment to Tomcat) and the jsf2 example, which you can use if you are running JSF2. To run the examples you'll need the following:

  • the latest release of Web Beans,

  • JBoss AS 5.0.1.GA, or

  • Apache Tomcat 6.0.x, and

  • Ant 1.7.0。

You'll need to download JBoss AS 5.0.1.GA from jboss.org, and unzip it. For example:

$ cd /Applications
$ unzip ~/jboss-5.0.1.GA.zip

Next, download Web Beans from seamframework.org, and unzip it. For example

$ cd ~/
$ unzip ~/webbeans-$VERSION.zip

接下來,我們需要讓 Web Bean 知道 JBoss 的位置在哪裡。請編輯 jboss-as/build.properties 然後設置 jboss.home 內容。例如:

jboss.home=/Applications/jboss-5.0.1.GA

To install Web Beans, you'll need Ant 1.7.0 installed, and the ANT_HOME environment variable set. For example:

$ unzip apache-ant-1.7.0.zip
$ export ANT_HOME=~/apache-ant-1.7.0

Then, you can install the update. The update script will use Maven to download Web Beans automatically.

$ cd webbeans-$VERSION/jboss-as
$ ant update

現在,您已準備好建置您的第一個範例!

提示

The build scripts for the examples offer a number of targets for JBoss AS, these are:

  • ant restart - 以分解的格式來建置範例

  • ant explode - 在不重新建置的情況下更新一個已分解的範例

  • ant deploy - 以 jar 格式來建置範例

  • ant undeploy - 將範例由伺服器中移除

  • ant clean - 清除範例

若要建置 numberguess 範例:

$ cd examples/numberguess
ant deploy

Start JBoss AS:

$ /Application/jboss-5.0.0.GA/bin/run.sh

提示

If you use Windows, use the run.batscript.

請等待應用程式的建置,然後在 http://localhost:8080/webbeans-numberguess 花上幾個小時!

Web Beans includes a second simple example that will translate your text into Latin. The numberguess example is a war example, and uses only simple beans; the translator example is an ear example, and includes enterprise beans, packaged in an EJB module. To try it out:

$ cd examples/translator
ant deploy

請等待應用程式的建置並進入 http://localhost:8080/webbeans-translator

You'll need to download Tomcat 6.0.18 or later from tomcat.apache.org, and unzip it. For example:

$ cd /Applications
$ unzip ~/apache-tomcat-6.0.18.zip

Next, download Web Beans from seamframework.org, and unzip it. For example

$ cd ~/
$ unzip ~/webbeans-$VERSION.zip

Next, we need to tell Web Beans where Tomcat is located. Edit jboss-as/build.properties and set the tomcat.home property. For example:

tomcat.home=/Applications/apache-tomcat-6.0.18

提示

The build scripts for the examples offer a number of targets for Tomcat, these are:

  • ant tomcat.restart - deploy the example in exploded format

  • ant tomcat.explode - update an exploded example, without restarting the deployment

  • ant tomcat.deploy - deploy the example in compressed jar format

  • ant tomcat.undeploy - remove the example from the server

  • ant tomcat.clean - clean the example

To deploy the numberguess example for tomcat:

$ cd examples/tomcat
ant tomcat.deploy

Start Tomcat:

$ /Applications/apache-tomcat-6.0.18/bin/startup.sh

提示

If you use Windows, use the startup.batscript.

請等待應用程式的建置,然後在 http://localhost:8080/webbeans-numberguess 花上幾個小時!

在 numberguess 應用程式中,您將會有 10 次機會來猜一個介於 1 至 100 之間的號碼。每當猜過一遍,系統便會告知您您所輸入的數字是否太大或太小。

numberguess 範例包含了 Web Bean 的一個數字、配置檔案,以及 Facelet JSF 頁面,並且封裝為 war。讓我們先從配置檔案開始。

此範例的所有配置檔案都位於 WEB-INF/ 中,並且它又儲存在來源樹中的 WebContent 裡。首先,我們有個可使用來指定 JSF 來使用 Facelet 的 faces-config.xml


<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="1.2"
              xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
    
    <application>
        <view-handler
>com.sun.facelets.FaceletViewHandler</view-handler>
    </application>

</faces-config
>

有個空的 web-beans.xml 檔案,它會將此應用程式標記為一個 Web Bean 應用程式。

最後為 web.xml

Let's take a look at the Facelet view:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:s="http://jboss.com/products/seam/taglib">

  <ui:composit(1)ion template="template.xhtml">
    <ui:define name="content">
       <h1
>Guess a number...</h1>
       <h:form(2) id="NumberGuessMain">
          <div style="color: red">
             <h:messages id="messages" globalOnly="false"/>
             <h:outputText id="Higher" value="Higher!" rendered="#{game.number gt game.guess and game.guess ne 0}"/>
             <h:outputText id="Lower" value="Lower!" rendered="#{game.number lt game.guess and game.guess ne 0}"/>
          </div>
   
          <div(3)>
             I'm thinking of a number between #{game.smallest} and #{game.biggest}.
             You have #{game.remainingGuesses} guesses.
          </div>
     
          <div>
             Y(4)our guess: 
             <h:inputText id="inputGuess" 
                          value="#{game.guess}" 
                          required="true" 
                          size="3" 
              (5)            disabled="#{game.number eq game.guess}">
                <f:validateLongRange maximum="#{game.biggest}" 
                                     minimum="#{game.smallest}"/>
             <(6)/h:inputText>
            <h:commandButton id="GuessButton"  
                             value="Guess" 
                             action="#{game.check}" 
                             disabled="#{game.number eq game.guess}"/>
          </div>
          <div>
            <h:commandButton id="RestartButton" value="Reset" action="#{game.reset}" immediate="true" />
          </div>
       </h:form>
    </ui:define>
  </ui:composition>
</html
>
1

Facelets is a templating language for JSF, here we are wrapping our page in a template which defines the header.

2

There are a number of messages which can be sent to the user, "Higher!", "Lower!" and "Correct!"

3

As the user guesses, the range of numbers they can guess gets smaller - this sentance changes to make sure they know what range to guess in.

4

This input field is bound to a Web Bean, using the value expression.

5

A range validator is used to make sure the user doesn't accidentally input a number outside of the range in which they can guess - if the validator wasn't here, the user might use up a guess on an out of range number.

6

And, of course, there must be a way for the user to send their guess to the server. Here we bind to an action method on the Web Bean.

範例存有 4 個類別,前兩個為綁定類型。首先,有個使用來注入亂數號碼的 @Random 綁定類型:

@Target( { TYPE, METHOD, PARAMETER, FIELD })

@Retention(RUNTIME)
@Documented
@BindingType
public @interface Random {}

還有個用來注入可注入之最大號碼的 @MaxNumber 綁定類型:

@Target( { TYPE, METHOD, PARAMETER, FIELD })

@Retention(RUNTIME)
@Documented
@BindingType
public @interface MaxNumber {}

Generator 類別負責透過產生器的方式來建立亂數號碼。它也會透過一個產生器的方式來顯示最大的可能號碼:

@ApplicationScoped

public class Generator {
   
   private java.util.Random random = new java.util.Random( System.currentTimeMillis() );
   
   private int maxNumber = 100;
   
   java.util.Random getRandom()
   {
      return random;
   }
   
   @Produces @Random int next() { 
      return getRandom().nextInt(maxNumber); 
   }
   
   @Produces @MaxNumber int getMaxNumber()
   {
      return maxNumber;
   }
}

您將會注意到 Generator 是屬於應用程式導向的;因此,我們不會每次都一定能得到不同的亂數號碼。

應用程式中最後的 Web Bean 為 session 導向的 Game

您將會注意到我們使用了 @Named 標記,如此一來我們便可在 JSF 頁面中的 EL 上使用這個 bean。最後,我們使用了 constructor injection 來利用亂數號碼來初始化了這個遊戲。當然,當玩家贏時我們將需要告知玩家,並藉由一個 FacesMessage 來給予回應。

package org.jboss.webbeans.examples.numberguess;



import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.webbeans.AnnotationLiteral;
import javax.webbeans.Current;
import javax.webbeans.Initializer;
import javax.webbeans.Named;
import javax.webbeans.SessionScoped;
import javax.webbeans.manager.Manager;
@Named
@SessionScoped
public class Game
{
   private int number;
   
   private int guess;
   private int smallest;
   private int biggest;
   private int remainingGuesses;
   
   @Current Manager manager;
   
   public Game()
   {
   }
   
   @Initializer
   Game(@MaxNumber int maxNumber)
   {      
      this.biggest = maxNumber;
   }
   public int getNumber()
   {
      return number;
   }
   
   public int getGuess()
   {
      return guess;
   }
   
   public void setGuess(int guess)
   {
      this.guess = guess;
   }
   
   public int getSmallest()
   {
      return smallest;
   }
   
   public int getBiggest()
   {
      return biggest;
   }
   
   public int getRemainingGuesses()
   {
      return remainingGuesses;
   }
   
   public String check()
   {
      if (guess
>number)
      {
         biggest = guess - 1;
      }
      if (guess<number)
      {
         smallest = guess + 1;
      }
      if (guess == number)
      {
         FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Correct!"));
      }
      remainingGuesses--;
      return null;
   }
   
   @PostConstruct
   public void reset()
   {
      this.smallest = 0;
      this.guess = 0;
      this.remainingGuesses = 10;
      this.number = manager.getInstanceByType(Integer.class, new AnnotationLiteral<Random
>(){});
   }
   
}

轉譯器範例能接受您所輸入的任何句子,然後將它們翻譯成拉丁文。

轉換器範例被建置為一個 ear 並包含著 EJB。正因如此,它的結構比 numberguess 範例要複雜得多。

首先,讓我們先來看一下 ear 聚合器,它位於 webbeans-translator-ear 模組中。Maven 會自動地為我們產生 application.xml


<plugin>
   <groupId
>org.apache.maven.plugins</groupId>
   <artifactId
>maven-ear-plugin</artifactId>
   <configuration>
      <modules>
         <webModule>
            <groupId
>org.jboss.webbeans.examples.translator</groupId>
            <artifactId
>webbeans-translator-war</artifactId>
            <contextRoot
>/webbeans-translator</contextRoot>
         </webModule>
      </modules>
   </configuration>
</plugin
>

在此我們將設置 context 路徑,它能提供給我們一個網址(http://localhost:8080/webbeans-translator)。

提示

若您不使用 Maven 來產生這些檔案,那麼您將需要 META-INF/application.xml


<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/javaee" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd"
             version="5">
  <display-name
>webbeans-translator-ear</display-name>
  <description
>Ear Example for the reference implementation of JSR 299: Web Beans</description>
  
  <module>
    <web>
      <web-uri
>webbeans-translator.war</web-uri>
      <context-root
>/webbeans-translator</context-root>
    </web>
  </module>
  <module>
    <ejb
>webbeans-translator.jar</ejb>
  </module>
</application
>

Next, lets look at the war. Just as in the numberguess example, we have a faces-config.xml (to enable Facelets) and a web.xml (to enable JSF) in WebContent/WEB-INF.

還有更有趣的就是使用來轉換文字的 facelet。就和 numberguess 範例中一樣,我們有個圍繞著 form 的頁面格式(在此將省略不提):


<h:form id="NumberGuessMain">
            
   <table>
      <tr align="center" style="font-weight: bold" >
         <td>
            Your text
         </td>
         <td>
            Translation
         </td>
      </tr>
      <tr>
         <td>
            <h:inputTextarea id="text" value="#{translator.text}" required="true" rows="5" cols="80" />
         </td>
         <td>
            <h:outputText value="#{translator.translatedText}" />
         </td>
      </tr>
   </table>
   <div>
      <h:commandButton id="button" value="Translate" action="#{translator.translate}"/>
   </div>
   
</h:form
>

用戶可在左手邊的文字區域中輸入一些文字,然後點選轉譯按鈕並於右手邊的區域中查看結果。

最後,讓我們來看一下 ejb 模組 webbeans-translator-ejb。在 src/main/resources/META-INF 中只有一個用來將 archive 標記為包含著 Web Bean 的空 web-beans.xml

我們將最有趣的部份保留到了最後,那就是程式碼!該專案含有兩個基本的 bean,SentenceParserTextTranslator,以及兩個企業級的 bean,TranslatorControllerBeanSentenceTranslator。到了現在您應該已經很熟悉 Web Bean 長得如何了,因此我們在此將只著重於其它較有趣的部份。

SentenceParserTextTranslator 兩者皆為相依性的 bean,並且 TextTranslator 使用了 constructor 初始化:

public class TextTranslator { 

   private SentenceParser sentenceParser; 
   private Translator sentenceTranslator; 
   
   @Initializer
   TextTranslator(SentenceParser sentenceParser, Translator sentenceTranslator) 
   { 
      this.sentenceParser = sentenceParser; 
      this.sentenceTranslator = sentenceTranslator;

TextTranslator 是個無狀態的 bean(以及一個本地的商業介面)- 當然,我們無法開發一個完整的轉譯器。

最後,有個 UI 導向的控制器,它會藉由用戶收集文字然後將它發送給轉譯器。這是個請求導向、被命名,而有狀態的 session bean,並且會注入轉譯器。

@Stateful

@RequestScoped
@Named("translator")
public class TranslatorControllerBean implements TranslatorController
{
   
   @Current TextTranslator translator;

這個 bean 針對於頁面上所有欄位都有 getter 與 setter。

因為這是個 stateful(有狀態)的 session bean,因此我們必須要有個 remove method:

   @Remove

   public void remove()
   {
      
   }

當 bean 被毀掉後,Web Bean 管理員會為您調用 remove 這個 method;在此情況下為請求結束之後。

That concludes our short tour of the Web Beans examples. For more on Web Beans , or to help out, please visit http://www.seamframework.org/WebBeans/Development.

我們在所有層面都需要協助 - 錯誤修正、編寫新功能、編寫範例,以及翻譯此參照指南。

Web Bean 支援了三個主要的依賴注入機制:

Constructor parameter injection:

public class Checkout {

        
    private final ShoppingCart cart;
    
    @Initializer
    public Checkout(ShoppingCart cart) {
        this.cart = cart;
    }
}

Initializer method parameter injection:

public class Checkout {

        
    private ShoppingCart cart;
    @Initializer 
    void setShoppingCart(ShoppingCart cart) {
        this.cart = cart;
    }
    
}

以及 direct field injection:

public class Checkout {


    private @Current ShoppingCart cart;
    
}

每當 Web Bean 的 instance 第一次被例示時,依賴注入就會發生。

  • 首先,Web Bean 管理員會調用 Web Bean constructor,並取得 Web Bean 的某個 instance。

  • 接下來,Web Bean 管理員便會初始化 Web Bean 所有已注入欄位的值。

  • 然後,Web Bean 管理員將會調用 Web Bean 的所有 initializer method。

  • 最後,(若存在的話)Web Bean 的 @PostConstruct method 將會被調用。

EJB Bean 不支援 Constructor parameter injection,因為 EJB 是由 EJB container 來例示(instantiate)的,而不是以 Web Bean 管理員。

當套用了預設的 @Current 綁定類型時,Constructor 和 initializer method 的參數不需要被明確地標記。不過,儘管套用了預設的綁定類型,被注入的欄位還是一定要指定綁定類型。若該欄位不指定綁定類型的話,它將不會被注入。

Producer method 也支援 parameter injection:

@Produces Checkout createCheckout(ShoppingCart cart) {

    return new Checkout(cart);
}

最後,observer method(我們會在 章 9, 事件(Events) 中談到)、disposal method 以及 destructor method 全都支援 parameter injection。

Web Bean 規格定義了一個流程,該流程稱為 typesafe resolution algorithm(typesafe 解析演算法),當 Web Bean 要注入至一個注入點時,Web Bean 管理員便會遵照該流程來進行。這個演算法乍看之下非常地複雜,不過當您理解它之後,您會發現它實際上並不複雜。Typesafe 解析會在系統初始化時進行,這也代表了若 Web Bean 的相依性無法被滿足的話,管理員會即刻透過回傳一個 UnsatisfiedDependencyException 或是 AmbiguousDependencyException 來通知用戶。

這個演算法就是為了要讓多重 Web Bean 實做相同的 API 類型以及:

  • 讓客戶端藉由使用 binding annotations 來選擇需要的實做、

  • 讓應用程式建置人員在不改變客戶端的情況下藉由啟用或停用 deployment types 來選擇用於特定建置的適當實做,或是

  • 允許建置時透過使用 deployment type precedence 來讓某個 API 的一個實做能在不改變客戶端的情況下置換另一個相同 API 的實做。

讓我們來探討 Web Bean 管理員如何判斷某個 Web Bean 要如何被注入。

若我們擁有超過一個實做特定 API 類型的 Web Bean,注入點可藉由使用綁定標記來確切地指定哪個 Web Bean 應該被注入。比方說,PaymentProcessor 的實做可能有兩個:

@PayByCheque

public class ChequePaymentProcessor implements PaymentProcessor {
    public void process(Payment payment) { ... }
}
@PayByCreditCard

public class CreditCardPaymentProcessor implements PaymentProcessor {
    public void process(Payment payment) { ... }
}

@PayByCheque@PayByCreditCard 為綁定標記:

@Retention(RUNTIME)

@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCheque {}
@Retention(RUNTIME)

@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCreditCard {}

客戶端 Web Bean 開發人員會使用綁定標記來確切指定哪個 Web Bean 應被注入。

使用 field injection:

@PayByCheque PaymentProcessor chequePaymentProcessor;

@PayByCreditCard PaymentProcessor creditCardPaymentProcessor;

使用 initializer method injection:

@Initializer

public void setPaymentProcessors(@PayByCheque PaymentProcessor chequePaymentProcessor, 
                                 @PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
   this.chequePaymentProcessor = chequePaymentProcessor;
   this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}

或使用 constructor injection:

@Initializer

public Checkout(@PayByCheque PaymentProcessor chequePaymentProcessor, 
                @PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
   this.chequePaymentProcessor = chequePaymentProcessor;
   this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}

所有 Web Bean 都有一個 deployment type(建置類型)。各個 deployment type 都可標識一組應依照條件性地被安裝在系統的某些 deployment 中的 Web Bean。

比方說,我們可定義一個名為 @Mock 的 deployment type,它會找出只應在系統執行於一個整合測試環境中的時候才會被安裝的 Web Bean:

@Retention(RUNTIME)

  @Target({TYPE, METHOD})
  @DeploymentType
  public @interface Mock {}

假設我們有一些和外部系統進行互動以便處理款項的 Web Bean:

public class ExternalPaymentProcessor {

        
    public void process(Payment p) {
        ...
    }
    
}

因為這個 Web Bean 並未明確地指定一個 deployment type,因此它的 deployment type 會是預設的 @Production

假設要進行整合或是單元測試(unit testing),不過外部系統較慢或是無法使用。因此我們將建立一個 mock 物件:

@Mock 

public class MockPaymentProcessor implements PaymentProcessor {
    @Override
    public void process(Payment p) {
        p.setSuccessful(true);
    }
}

不過 Web Bean 管理員要如何判斷在特定建置中該使用哪個實做?

若您有在細心注意的話,您應該會疑惑 Web Bean 管理員如何決定哪個實做 — ExternalPaymentProcessorMockPaymentProcessor — 中選擇哪一個。請思考當管理員遇上了這個注入點時會如何:

@Current PaymentProcessor paymentProcessor

有兩個 Web Bean 可滿足 PaymentProcessor 合同。當然,我們無法使用綁定標記來消除語意上的含糊意義,因為綁定標記已寫死(hard-coded)在注入點的來源之中,而且我們希望管理員能夠在 deployment time 時作決定!

這項問題的解決方式就是利用各個 deployment type 的不同優先權。Deployment type 的優先權是透過它們出現在 web-beans.xml 中的順序來決定的。在我們的範例中,@Mock 出現的順序在 @Production 之後,因此它會有較高的優先權。

每當管理員發現了多於一個 Web Bean 可滿足由某個注入點所指定的合同(API 類型加上綁定標記)時,它會考量到 Web Bean 的相關優先權。若有一方的優先權較高,它便會選擇優先權較高的那個 Web Bean 來注入。因此,在我們的範例中,當 Web Bean 管理員執行於我們的整合測試環境中的時候(這正是我們想要的),它將會注入 MockPaymentProcessor

和現今多人使用的管理員架構相較之下,這個功能相當地有趣。各種「lightweight」的 container 都允許存在 classpath 中的 class 的條件性建置,不過要被建置的 class 必須要明確、各別地列在配置程式碼或是一些 XML 配置檔案中。Web Bean 不支援透過 XML 的 Web Bean 定義與配置,不過在一般情況下當不需要複雜的配置時,deployment type 允許一整組 Web Bean 能夠透過 XML 中的一個單獨行列來被啟用。其間,瀏覽程式碼的開發人員能夠輕易地分辨出 Web Bean 將會使用哪種建置方案(deployment scenario)。

一個已注入的 Web Bean 的客戶端通常不會持有一個 Web Bean instance 的直接參照。

想像一個綁定至應用程式 scope 的 Web Bean 持有一個綁定至請求 scope 的 Web Bean 的直接參照。這個應用程式 scope 的 Web Bean 會在許多不同的請求之間被共享。不過,各個請求都應要看見一個不同的請求 scope Web Bean 的 instance!

現在,請想像一個綁定至 session scope 的 Web Bean 持有一個綁定至應用程式 scope 的 Web Bean 的直接參照。有時,session context 會被序列化至磁碟中以便更有效率地使用記憶體。不過,應用程式 scope 的 Web Bean instance 不該和 session scope 的 Web Bean 一起被序列化!

因此,除非有個 Web Bean 擁有預設的 @Dependent scope,否則 Web Bean 管理員便必須透過一個 proxy 物件來將所有注入的參照重新指向 Web Bean。這個 client proxy 負責確保收到 method 調用的 Web Bean instance 是個和目前 context 相聯的 instance。客戶端 proxy 亦可允許在不遞迴地序列化其它已注入的 Web Bean 的情況下也能讓綁定至 context(例如 session context)的 Web Bean 被序列化至磁碟。

不巧的是,礙於 Java 語言的限制,有些 Java 類型無法被 Web Bean 管理員代理(proxied)。因此,若某個注入點的 type 無法被代理的話,Web Bean 管理員便會回傳一個 UnproxyableDependencyException

下列 Java 類型無法被 Web Bean 管理員代理:

要修正 UnproxyableDependencyException 通常相當容易。只要將一個無參數的 constructor 附加至注入的 class、採用一個介面,或將已注入的 Web Bean 的 scope 更改為 @Dependent 即可。

有幾種特定相依物件 — 含有 @Dependent 這個 scope 的 Web Bean — 需要知道有關於物件或是它們被注入的注入點相關資訊才能進行它們本應進行的工作。比方說:

含有 @Dependent 這個 scope 的 Web Bean 能夠注入一個 InjectionPoint instance 並存取和它所屬的注入點相關的 metadata。

讓我們來探討下列範例。下列程式碼較為冗長,並且有重構(refactoring)問題上的弱點:

Logger log = Logger.getLogger(MyClass.class.getName());

這個 producer method 能讓您在不明確指定 log category 的情況下注入一個 JDK Logger

class LogFactory {


   @Produces Logger createLogger(InjectionPoint injectionPoint) { 
      return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); 
   }
}

現在我們可寫入:

@Current Logger log;

若您無法被說服的話,我們還有第二個範例。若要注入 HTTP 參數,我們需要定義一個綁定類型:

@BindingType

@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface HttpParam {
   @NonBinding public String value();
}

我們可如下在注入點使用此綁定類型:

@HttpParam("username") String username;

@HttpParam("password") String password;

下列 producer method 可完成此工作:

class HttpParams


   @Produces @HttpParam("")
   String getParamValue(ServletRequest request, InjectionPoint ip) {
      return request.getParameter(ip.getAnnotation(HttpParam.class).value());
   }
}

(請注意,HttpParam 標記的 value() 成員已被 Web Bean 管理員忽略掉,因為它已被標記為 @NonBinding.

Web Bean 管理員提供了實做 InjectionPoint 介面的內建 Web Bean:

public interface InjectionPoint { 

   public Object getInstance(); 
   public Bean<?> getBean(); 
   public Member getMember(): 
   public <extends Annotation
> T getAnnotation(Class<T
> annotation); 
   public Set<extends Annotation
> getAnnotations(); 
}

到目前為止,我們已見過了一些 scope 類型標記(scope type annotations)的範例了。Web Bean 的 scope 可決定 Web Bean instance 的生命週期(lifecycle)。Scope 也能決定哪個客戶端參照哪個 Web Bean 的 instance。根據 Web Bean 規格,scope 可決定:

  • 任何含有該 scope 的 Web Bean 的新 instance 應何時被建立

  • 任何含有該 scope 的 Web Bean 的現有 instance 應何時被刪除

  • 哪個被注入的參照代表含有該 scope 的 Web Bean 的任何 instance

比方說若我們有個 session scope 的 Web Bean CurrentUser,那麼所有在相同 HttpSession 的 context 中被調用的 Web Bean 都會看見相同的 CurrentUser 的 instance。當 CurrentUser 在該 session 中第一次被需要時,這個 instance 就會自動地被建立,並且在這個 session 結束時被自動地刪除掉。

Web Bean 的 conversation scope 和傳統的 session scope 類似,它們都持有著有關於系統用戶的狀態,並發出多重請求至伺服器。conversation scope 和 session scope 不同的地方在於:

Conversation(對話)代表一項工作,從用戶角度來看是項工作的單位。conversation 的 context 持有和用戶目前工作相關的狀態。若用戶同時間一次進行多項工作的話,那就會有多個 conversation。

conversation context 會在任何 JSF 請求進行中的時候啟用。不過,大部分的 conversation 都會在請求結束後被刪除掉。若有個 conversation 必須持有多重請求的狀態,它便需要被明確地轉為 long-running conversation

Web Bean 提供了一個內建的 Web Bean,它可被用來在 JSF 應用程式中控制 conversation 的生命週期。這個 Web Bean 能透過注入下列來取得:

@Current Conversation conversation;

若要將和目前的請求關聯的 conversation 轉為 long-running conversation 的話,請由應用程式的程式碼調用 begin() method。若要將目前的 long-running conversation context 排程在目前請求結束時被刪除,請調用 end()

在下列範例中,有個 conversation-scoped 的 Web Bean 會控制和它關聯的 conversation:

@ConversationScoped @Stateful

public class OrderBuilder {
    private Order order;
    private @Current Conversation conversation;
    private @PersistenceContext(type=EXTENDED) EntityManager em;
    
    @Produces public Order getOrder() {
        return order;
    }
    public Order createOrder() {
        order = new Order();
        conversation.begin();
        return order;
    }
    
    public void addLineItem(Product product, int quantity) {
        order.add( new LineItem(product, quantity) );
    }
    public void saveOrder(Order order) {
        em.persist(order);
        conversation.end();
    }
    
    @Remove
    public void destroy() {}
    
}

這個 Web Bean 能夠透過使用 Conversation API 來控制它自己的生命週期。不過有些其它 Web Bean 的生命週期完全取決於另一個物件。

除了這四個內建的 scope,Web Bean 還提供了一項稱為 dependent pseudo-scope 的功能。這是個未明確宣告 scope 類型的 Web Bean 的預設 scope。

比方說,這個 Web Bean 的 scope 類型為 @Dependent

public class Calculator { ... }

當 Web Bean 的一個注入點解析至一個相依 Web Bean 時,每當第一個 Web Bean 被例示(instantiate)時,相依 Web Bean 的一個新的 instance 就會被建立。相依 Web Bean 的 instance 絕不會被共享於不同的 Web Bean 或不同的注入點之間。它們是其它 Web Bean instance 的 dependent object(相依物件)

相依 Web Bean 的 instance 會在它們所依賴的 instance 被刪除掉時跟著被一起刪除。

Web Bean 使得取得 Java class 或是 EJB bean 的相依 instance 變得相當容易,儘管該 class 或是 EJB bean 已被宣告為一個含有其牠 scope 類型的 Web Bean。

Producer method 讓我們在當 Web Bean 管理員(而不是應用程式)負責舉例說明(instantiating)物件時能夠解決一些特定發生的問題。它們同時也是將非 Web Bean 物件整合入 Web Bean 環境中最簡單的方式。(我們將在 章 12, 使用 XML 來定義 Web Bean 中提及第二個方式。)

根據規格:

A Web Beans producer method acts as a source of objects to be injected, where:

  • the objects to be injected are not required to be instances of Web Beans,

  • the concrete type of the objects to be injected may vary at runtime or

  • the objects require some custom initialization that is not performed by the Web Bean constructor

For example, producer methods let us:

  • expose a JPA entity as a Web Bean,

  • expose any JDK class as a Web Bean,

  • define multiple Web Beans, with different scopes or initialization, for the same implementation class, or

  • vary the implementation of an API type at runtime.

In particular, producer methods let us use runtime polymorphism with Web Beans. As we've seen, deployment types are a powerful solution to the problem of deployment-time polymorphism. But once the system is deployed, the Web Bean implementation is fixed. A producer method has no such limitation:

@SessionScoped

public class Preferences {
    
    private PaymentStrategyType paymentStrategy;
    
    ...
    
    @Produces @Preferred 
    public PaymentStrategy getPaymentStrategy() {
        switch (paymentStrategy) {
            case CREDIT_CARD: return new CreditCardPaymentStrategy();
            case CHEQUE: return new ChequePaymentStrategy();
            case PAYPAL: return new PayPalPaymentStrategy();
            default: return null;
        } 
    }
    
}

Consider an injection point:

@Preferred PaymentStrategy paymentStrat;

This injection point has the same type and binding annotations as the producer method, so it resolves to the producer method using the usual Web Beans injection rules. The producer method will be called by the Web Bean manager to obtain an instance to service this injection point.

.

上述程式碼有個潛在的問題。CreditCardPaymentStrategy 的實做是透過使用 Java 的 new operator 來例示(instantiate)的。被應用程式直接例示的物件無法有效利用依賴注入(dependency injection)並且沒有攔截器。

若這不是我們所想要的,我們可使用依賴注入至 producer method 之中來取得 Web Bean 的 instance:

@Produces @Preferred @SessionScoped

public PaymentStrategy getPaymentStrategy(CreditCardPaymentStrategy ccps,
                                          ChequePaymentStrategy cps,
                                          PayPalPaymentStrategy ppps) {
    switch (paymentStrategy) {
        case CREDIT_CARD: return ccps;
        case CHEQUE: return cps;
        case PAYPAL: return ppps;
        default: return null;
    } 
}

請稍等,若 CreditCardPaymentStrategy 是個請求 scope 的 Web Bean 的話要怎麼辦?若是如此那麼 producer method 便有將現有的 request scope 的 instance「推入」session scope 中的效應。這幾乎能被確定是個 bug!request scope 物件會在 session 結束前被 Web Bean 管理員毀掉,不過該物件的參照會被「保留」在 session scope 中。這項錯誤將不會被 Web Bean 管理員偵測到,所以當由 producer method 回傳 Web Bean 的 instance 時請格外小心!

我們有三種可修正這項錯誤的方法。我們可更改 CreditCardPaymentStrategy 實做的 scope,不過這將會影響該 Web Bean 的其它客戶端。較好的方式就是將 producer method 的 scope 更改為 @Dependent@RequestScoped

不過較普遍的解決方式就是使用特殊的 @New 綁定標記。

Web Bean 的第一個重點就是 loose coupling(鬆散結合性)。我們已經看過了三種 loose coupling 的方式:

這些技巧都可用來啟用客戶端以及伺服器的 loose coupling。客戶端已不再固定綁定至一個特定 API 的實做,並且它亦無須管理伺服器物件的生命週期。這個方法能讓有狀態的物件被視為服務一般地來進行互動

Loose coupling 會使系統變得更加動態式。系統可透過充足定義的方式來回應變更。過去,有許多 framework 嘗試了提供以上所列出的功能,不過卻都是藉由犧牲了 type safety 來達成的。Web Bean 是第一個以 typesafe 的方式來實現此層級的 loose coupling 的技術。

Web Bean 提供了三個額外的重要功能以用來達成 loose coupling:

讓我們先來探討攔截器(interceptor)。

Web Bean 會重新使用 EJB 3.0 的基本攔截器架構,並朝兩個方向來延伸該功能:

  • 不只是 session bean,任何 Web Bean 都能有攔截器。

  • 針對於將攔截器綁定至 Web Bean,Web Bean 含有較為復雜的標記導向方式。

EJB 格式定義了兩種類型的攔截點:

  • business method 的攔截,以及

  • lifecycle callback 的攔截。

business method 攔截器可適用於來自 Web Bean 客戶端的 Web Bean 的 method 調用:

public class TransactionInterceptor {

    @AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
}

lifecycle callback 攔截器可適用於來自 container 的 lifecycle callback 的調用:

public class DependencyInjectionInterceptor {

    @PostConstruct public void injectDependencies(InvocationContext ctx) { ... }
}

攔截器 class 可攔截 lifecycle callback 以及 business method。

呼叫攔截器(Interceptors)是個用來擷取和區分與型別系統垂直的相關問題的強大方式。任何攔截器皆可攔截任何 Java 類型的調用(invocation)。這使它們適用於解決涉及技術上的問題,例如交易管理和安全性。不過,就一般來講,攔截器並不會知道它們所攔截的事件之實際 語。因此,攔截器並不適合作為一個區分涉及商業問題的工具。意

裝飾器(decorators)則是相反的。裝飾器只會針對於特定 java 介面來攔截調用,因此它知道該介面的所有語意。這使得裝飾器適用於模擬涉及商業上的問題。同時這也代表裝飾器並沒有攔截器的普遍性。裝飾器無法解決涉及多種不同類型的技術問題。

假設我們有個顯示帳號的介面:

public interface Account {

    public BigDecimal getBalance();
    public User getOwner();
    public void withdraw(BigDecimal amount);
    public void deposit(BigDecimal amount);
}

我們的系統中會有幾個不同的 Web Bean 實做 Account 介面。不過,我們有個法律上的統一規定需求,那就是不管是任何哪種類型的帳號,大型的交易都必須被系統記錄於一個特殊的日誌中。這是一項適合裝飾器的工作。

裝飾器(decorator)是個單純的 Web Bean,它可實做它所裝飾的類型並且會被標記為 @Decorator

@Decorator

public abstract class LargeTransactionDecorator 
        implements Account {
    
    @Decorates Account account;
    
    @PersistenceContext EntityManager em;
    
    public void withdraw(BigDecimal amount) {
        account.withdraw(amount);
        if ( amount.compareTo(LARGE_AMOUNT)
>0 ) {
            em.persist( new LoggedWithdrawl(amount) );
        }
    }
    
    public void deposit(BigDecimal amount);
        account.deposit(amount);
        if ( amount.compareTo(LARGE_AMOUNT)
>0 ) {
            em.persist( new LoggedDeposit(amount) );
        }
    }
    
}

和其它單純的 Web Bean 不同的是,裝飾器可能會是個抽象類別(abstract class)。若裝飾器無須為被裝飾之介面的特定 method 進行任何特殊動作的話,您便無須實做該 method。

Web Bean 事件通知功能能讓 Web Bean 以一個完全 decouple 的方式來進行互動。事件產生器(producers)會產生事件並且之後會被透過 Web Bean 管理員來傳送給事件觀察器(observers)。這個基本的 schema 可能看起來和熟悉的觀察器/可觀察的格式類似,不過卻有幾點不大相同:

  • 不只是事件產生器由觀察器被 decouple;觀察器也完全地由產生器被 decouple 了,

  • 觀察器可指定一組「選擇器(selectors)」的組合來過濾並減少它們所將會收到的事件通知數量,並且

  • 觀察器可即刻地被通知,或是您亦可指定將事件的傳送延遲到目前交易動作(又稱為工作邏輯單元)結束之後

事件產生器可透過注入來取得一個事件通知器(event notifier) 物件:

@Observable Event<Document

> documentEvent

@Observable 這個標記暗示性地以 @Dependent 這個 scope 和 @Standard 這個建置類型,以及 Web Bean 管理員所提供的實做來定義了一個 Web Bean。

產生器可藉由調用 Event 介面的 fire() method,並傳送一個 event object 來產生事件:

documentEvent.fire(document);

Event object 可能會是個沒有 type variable 或是 wildcard type 參數的任何 Java class 的 instance。該事件會被傳送給符合下列條件的每個 observer method:

Web Bean 管理員會調用所有 observer method,並將 event object 作為是事件參數的值一般地來傳送。若任何 observer method 回傳了一個 exception,Web Bean 管理員便會停止調用 observer method,然後這個 exception 會被 fire() method 重新回傳。

若要指定一個「selector」,事件產生器可將事件綁定類型的一個 instance 傳送至 fire() 這個 method:

documentEvent.fire( document, new AnnotationLiteral<Updated

>(){} );

AnnotationLiteral 這個 helper class 可例示內部的綁定類型,因為要在 Java 中這麼作相當的困難。

事件會被傳送給符合下列條件的每個觀察器 method:

另外,事件綁定亦可透過標記事件通知器注入點來指定:

@Observable @Updated Event<Document

> documentUpdatedEvent

若是如此,所有透過 Event 的 instance 來產生的事件都會含有被標記的事件綁定。事件會被傳送給符合下列條件的每個觀察器 method:

交易觀察器會在事件被產生後,於交易完成之前或之後收到它們的事件通知。比方說,下列 observer method 需要更新一組快取儲存於應用程式 context 中的查詢結果,不過只有更新了 Category tree 的交易會成功:

public void refreshCategoryTree(@AfterTransactionSuccess @Observes CategoryUpdateEvent event) { ... }

交易觀察器分為三種類型:

交易觀察器在一個像是 Web Bean 的 stateful 物件模型中相當地重要,因為 state 一般被保持的時間比單一 atomic transaction(不可分割的交易)還要長。

想像我們已快取了一個設置於應用程式 scope 中的 JPA 查詢結果:

@ApplicationScoped @Singleton

public class Catalog {
    @PersistenceContext EntityManager em;
    
    List<Product
> products;
    @Produces @Catalog 
    List<Product
> getCatalog() {
        if (products==null) {
            products = em.createQuery("select p from Product p where p.deleted = false")
                .getResultList();
        }
        return products;
    }
    
}

Product 經常會被建立或刪除。當這情況發生時,我們便需要更新 Product 的 catalog。不過我們還是應該等到交易成功完成之後才去進行這項更新!

建立和刪除 Product 的 Web Bean 可產生事件,例如:

@Stateless

public class ProductManager {
    @PersistenceContext EntityManager em;
    @Observable Event<Product
> productEvent;
    public void delete(Product product) {
        em.delete(product);
        productEvent.fire(product, new AnnotationLiteral<Deleted
>(){});
    }
    
    public void persist(Product product) {
        em.persist(product);
        productEvent.fire(product, new AnnotationLiteral<Created
>(){});
    }
    
    ...
    
}

而現在,Catalog 可在交易成功完成之後觀察事件:

@ApplicationScoped @Singleton

public class Catalog {
    ...
    
    void addProduct(@AfterTransactionSuccess @Observes @Created Product product) {
        products.add(product);
    }
    
    void addProduct(@AfterTransactionSuccess @Observes @Deleted Product product) {
        products.remove(product);
    }
    
}

Web Bean 的第二個重點就是 strong typing(強類型)。有關於 Web Bean 的相依性、攔截器與裝飾器的相關資訊,以及有關於某個事件產生器的事件用戶的相關資訊都包含在編譯器可驗證的 typesafe Java 架構中。

您在 Web Bean 的程式碼中不會看見基於字串的識別符號,這不是因為 framework 透過使用預設的規則來將它隱藏起來 — 又稱為「configuration by convention」 — 而是因為一開始根本就沒有任何字串!

這個作法明顯的好處就是任何 IDE 都可在不使用特殊工具的情況下提供自動完成(autocompletion)、驗證(validation)和重整(refactoring)。還有一個較沒那麼明顯且直接的好處。當您希望透過標記來識別物件、事件或是攔截器而不是透過名稱時,您可提昇您程式碼的語意層級(semantic level)。

Web Bean 鼓勵您使用塑造概念的標記,例如

而不是使用像是

這些標記可重複使用。它們協助描述系統不同部份的通用要點(common qualities)。它們協助我們分類和理解我們的程式碼。它們協助我們利用通用的方式來處理一般的問題。它們讓我們的程式碼變得更文字化且更容易理解。

Web Bean stereotype 將這個概念帶領到了另一個更為進階的領域。固定刻板模式(stereotype model)是您應用程式架構中的一個通用角色(role)。它將該角色的各種屬性(包括 scope、攔截器綁定〔interceptor binding〕,建置類型〔deployment type〕等等)壓縮進了一個單獨、可重複使用的套件中。

就連 Web Bean XML 的 metadata 也屬於強類型!XML 沒有編譯器,因此 Web Bean 利用了 XML schema 來驗證出現在 XML 中的 Java 類型和屬性。這個方法使得 XML 變得更文字化,就和標記使得我們的 Java 程式碼變得更容易理解一樣。

我們現在已準備好談到一些更為進階的 Web Bean 功能。請記得,這些功能是為了使我們的程式碼變得易於驗證和較容易理解。實際上大部分的時候您都不會真的需要使用到這些功能,不過若您可有效地利用這些功能的話,您將可體會到這些功能的強大。

根據 Web Bean 規格:

在許多系統中,使用架構的模式會產生一組循環的 Web Bean 工作。主要探討模板能讓一個 framework 開發人員辨識出這樣的一項工作,並在一個中央位置中為含有該工作的 Web Bean 宣告一些共通 metadata。

主要探討模板包含著下列任意的各種組合:

  • 預設的建置類型、

  • 預設的 scope 類型、

  • 基於 Web Bean scope 的限制、

  • Web Bean 實做或延伸特定類型的需求,以及

  • 一組攔截器綁定標記。

主要探討模板亦可指定所有還有該主要探討模板的 Web Bean 都含有預設的 Web Bean 名稱。

Web Bean 可宣告零、一或多個主要探討模板。

主要探討模板為 Java 標記類型。這個主要探討模板可辨識一些 MVC 架構中的動作 class:

@Retention(RUNTIME)

@Target(TYPE)
@Stereotype
public @interface Action {}

我們藉由套用標記至 Web Bean 來使用主要探討模板。

@Action 

public class LoginAction { ... }

我們已經看過了 Web Bean 的依賴注入模型如何讓我們在建置時置換(override)了一個 API 的實做。比方說,下列企業級的 Web Bean 在生產時提供了 PaymentProcessor 這個 API 的一個實做:

@CreditCard @Stateless

public class CreditCardPaymentProcessor 
        implements PaymentProcessor {
    ...
}

不過在我們的中繼環境(staging environment)中,我們利用了不同的 Web Bean 來置換了 PaymentProcessor 的實做:

@CreditCard @Stateless @Staging

public class StagingCreditCardPaymentProcessor 
        implements PaymentProcessor {
    ...
}

我們嘗試要對 StagingCreditCardPaymentProcessor 進行的就是在系統的某個特定的 deployment 中完全地替換掉 AsyncPaymentProcessor。在該 deployment 中,@Staging 這個 deployment type 將會擁有比 @Production 這個預設 deployment type 還要高的優先權,因此含有下列注入點的客戶端:

@CreditCard PaymentProcessor ccpp

將會收到 StagingCreditCardPaymentProcessor 的一個 instance。

不過我們有幾點需要小心:

  • 優先權較高的 Web Bean 無法實做它所嘗試置換的 Web Bean 的所有 API 類型、

  • 優先權較高的 Web Bean 無法宣告它所嘗試置換的 Web Bean 的所有綁定類型、

  • 優先權較高的 Web Bean 無法擁有與它所嘗試置換的 Web Bean 相同的名稱,或是

  • 它所嘗試置換的 Web Bean 可能能夠宣告一個 producer method、disposal method 或是 observer method。

在各個情況下,我們所嘗試置換的 Web Bean 都還是可能在 runtime 時被調用。因此,置換可能會造成開發上的錯誤。

Web Bean 提供了一項特殊的功能稱為 specialization,它可協助開發人員避免發生這些錯誤。Specialization 一開始看起來似乎有些難懂,不過實際上卻相當易於使用,並且您將會慶幸擁有它所提供的額外安全性。

到目前為止,我們已看過了許多透過使用標記來宣告的 Web Bean 了。不過,在某些情況下我們無法透過使用標記來定義 Web Bean:

  • 當實做的 class 來自於一些現有的函式庫,或是

  • 當相同的實做 class 應該要有多重 Web Bean。

在這兩種情況下,Web Bean 會提供我們兩個選項:

  • 撰寫一個 producer method,或是

  • 使用 XML 來宣告 Web Bean。

許多架構都使用 XML 來提供與 Java class 相關的 metadata。不過,Web Bean 使用了非常不同的方式來將 Java class 的名稱、欄位,或 method 指定給大部分其它的架構。與其將 class 和成員名稱編寫為 XML 要素和屬性的 string value,Web Bean 能讓您使用 class 或是成員名稱來作為 XML 要素的名稱。

這種作法的好處就是您可編寫一個防止您 XML 文件中有拼字錯誤的 XML schema。甚至有工具可藉由已編譯的 Java 程式碼來自動地產生該 XML schema。或是,某個整合的開發環境也能在不需要明確的居中產生步驟的情況下進行相同的驗證。

Web Bean 的第三個主題為 integration(整合性)。Web Bean 本身就是設計來與其它技術整合用的,它可協助應用程式開發員將不同的技術統合在一起。Web Bean 是個開放式技術。它形成了 Java EE 生態系統的一部分,並且它本身身為一個 portable extension 的新生態系統基礎,同時也是和現有 framework 和技術整合上的一個根本。

我們已經看到了 Web Bean 如何協助整合 EJB 與 JSF,它讓 EJB 能夠直接地綁定至 JSF 頁面。那僅是一開始而已。Web Bean 也提供了能夠使其它技術(例如商業流程管理、其它網站 Framework 以及第三方元件模型)變得多變化的功能。Java EE 平台永遠也無法標準化全部使用於 Java 應用程式開發領域中的技術,不過 Web Bean 能使得在 Java EE 環境下使用還未屬於該平台的技術變得更為容易。

我們正要探討如何在一個使用 Web Bean 的應用程式中有效地利用 Java EE 平台。我們也將簡短地探討一組提供來讓 Web Bean 支援 portable extension 的 SPI。您可能永遠也不會需要直接使用到這些 SPI,不過知道它們的存在以備不時之需總是較好的。最重要的是,每當您使用第三方的 extension 時,您便會間接地利用到它們。

Web Bean 已完整整合入 Java EE 環境中。Web Bean 可存取 Java EE 資源以及 JPA persistence context。它們可能會被使用於 JSF 以及 JSP 網頁中的 Unified EL 表示式中。它們也可被注入一些物件中,例如 Servlets 以及訊息導向的 Bean 中(非 Web Beans)。

基於需要處理許多不同的物件,因此透過使用 JMS 來傳送訊息可能會相當複雜。針對於 queue,我們有 QueueQueueConnectionFactoryQueueConnectionQueueSession 以及 QueueSender。針對於 topic 我們有 TopicTopicConnectionFactoryTopicConnectionTopicSession 以及 TopicPublisher。針對於各個這些物件,我們都需要去顧及它們自己各別的生命週期和執行緒模型(threading model)。

Web Bean 會全部為我們處理。我們只需要在 web-beans.xml 中宣告 queue 或是 topic,指定關聯的綁定類型和連接因數(connection factory)。


<Queue>
    <destination
>java:comp/env/jms/OrderQueue</destination>
    <connectionFactory
>java:comp/env/jms/QueueConnectionFactory</connectionFactory>
    <myapp:OrderProcessor/>    
</Queue
>

<Topic>
    <destination
>java:comp/env/jms/StockPrices</destination>
    <connectionFactory
>java:comp/env/jms/TopicConnectionFactory</connectionFactory>
    <myapp:StockPrices/>    
</Topic
>

現在我們只要為 queue 注入 QueueQueueConnectionQueueSessionQueueSender,或是為 topic 注入 TopicTopicConnectionTopicSession 或是 TopicPublisher 即可。

@OrderProcessor QueueSender orderSender;

@OrderProcessor QueueSession orderSession;
public void sendMessage() {
    MapMessage msg = orderSession.createMapMessage();
    ...
    orderSender.send(msg);
}
@StockPrices TopicPublisher pricePublisher;

@StockPrices TopicSession priceSession;
public void sendMessage(String price) {
    pricePublisher.send( priceSession.createTextMessage(price) );
}

被注入的 JMS 物件的生命週期完全由 Web Bean 管理員所控制。

Web Bean 為架構的平台,它可用來進行其它技術的延伸與整合。因此,Web Bean 提供了一組讓 Web Bean 的 portable extension 開發人員可使用的 SPI。比方說,下列類型的延伸為 Web Bean 設計人員所設想的:

  • 和商業流程管理上的整合、

  • 和第三方架構(例如 Spring、Seam、GWT 或 Wicket)上的整合,以及

  • 基於 Web Bean 程式撰寫模型(Programming Model)的新技術。

延伸 Web Bean 的神經中樞為 Manager 這個物件。

Manager 介面能讓我們程式性地註冊並取得 Web Bean、攔截器(interceptor)、裝飾器(decorator)、觀察器(observer),以及 context。

public interface Manager

{
   public <T
> Set<Bean<T
>
> resolveByType(Class<T
> type, Annotation... bindings);
   public <T
> Set<Bean<T
>
> resolveByType(TypeLiteral<T
> apiType,
         Annotation... bindings);
   public <T
> T getInstanceByType(Class<T
> type, Annotation... bindings);
   public <T
> T getInstanceByType(TypeLiteral<T
> type,
         Annotation... bindings);
   public Set<Bean<?>
> resolveByName(String name);
   public Object getInstanceByName(String name);
   public <T
> T getInstance(Bean<T
> bean);
   public void fireEvent(Object event, Annotation... bindings);
   public Context getContext(Class<? extends Annotation
> scopeType);
   public Manager addContext(Context context);
   public Manager addBean(Bean<?> bean);
   public Manager addInterceptor(Interceptor interceptor);
   public Manager addDecorator(Decorator decorator);
   public <T
> Manager addObserver(Observer<T
> observer, Class<T
> eventType,
         Annotation... bindings);
   public <T
> Manager addObserver(Observer<T
> observer, TypeLiteral<T
> eventType,
         Annotation... bindings);
   public <T
> Manager removeObserver(Observer<T
> observer, Class<T
> eventType,
         Annotation... bindings);
   public <T
> Manager removeObserver(Observer<T
> observer,
         TypeLiteral<T
> eventType, Annotation... bindings);
   public <T
> Set<Observer<T
>
> resolveObservers(T event, Annotation... bindings);
   public List<Interceptor
> resolveInterceptors(InterceptionType type,
         Annotation... interceptorBindings);
   public List<Decorator
> resolveDecorators(Set<Class<?>
> types,
         Annotation... bindings);
}

我們可透過注入來取得 Manager 的一個 instance:

@Current Manager ��

Web Beans is the reference implementation of JSR-299, and is used by JBoss AS and Glassfish to provide JSR-299 services for Java Enterprise Edition applications. Web Beans also goes beyond the environments and APIs defined by the JSR-299 specification and provides support for a number of other environments (such as a servlet container such as Tomcat, or Java SE) and additional APIs and modules (such as logging, XSD generation for the JSR-299 XML deployment descriptors).

If you want to get started quickly using Web Beans with JBoss AS or Tomcat and experiment with one of the examples, take a look at 章 3, Web Beans, the Reference Implementation of JSR-299. Otherwise read on for a exhaustive discussion of using Web Beans in all the environments and application servers it supports, as well the Web Beans extensions.

Web Beans can be used in Tomcat 6.0.

Web Beans should be used as a web application library in Tomcat. You should place webbeans-tomcat.jar in WEB-INF/lib. webbeans-tomcat.jar is an "uber-jar" provided for your convenience. Instead, you could use its component jars:

You also need to explicitly specify the Tomcat servlet listener (used to boot Web Beans, and control its interaction with requests) in web.xml:

<listener>
   <listener-class
>org.jboss.webbeans.environment.servlet.Listener</listener-class>
</listener
>

Tomcat has a read-only JNDI, so Web Beans can't automatically bind the Manager. To bind the Manager into JNDI, you should add the following to your META-INF/context.xml:

<Resource name="app/Manager" 
          auth="Container"
          type="javax.inject.manager.Manager"
          factory="org.jboss.webbeans.resources.ManagerObjectFactory"/>

and make it available to your deployment by adding this to web.xml:

<resource-env-ref>
  <resource-env-ref-name>
    app/Manager
  </resource-env-ref-name>
  <resource-env-ref-type>
    javax.inject.manager.Manager
  </resource-env-ref-type>
</resource-env-ref>

Tomcat only allows you to bind entries to java:comp/env, so the Manager will be available at java:comp/env/app/Manager

Web Beans also supports Servlet injection in Tomcat. To enable this, place the webbeans-tomcat-support.jar in $TOMCAT_HOME/lib, and add the following to your META-INF/context.xml:

<Listener className="org.jboss.webbeans.environment.tomcat.WebBeansLifecycleListener" />

Apart from improved integration of the Enterprise Java stack, Web Beans also provides a state of the art typesafe, stateful dependency injection framework. This is useful in a wide range of application types, enterprise or otherwise. To facilitate this, Web Beans provides a simple means for executing in the Java Standard Edition environment independently of any Enterprise Edition features.

When executing in the SE environment the following features of Web Beans are available:

To make life easy for developers Web Beans provides a special module with a main method which will boot the Web Beans manager, automatically registering all simple Web Beans found on the classpath. This eliminates the need for application developers to write any bootstrapping code. The entry point for a Web Beans SE applications is a simple Web Bean which observes the standard @Deployed Manager event. The command line paramters can be injected using either of the following:

@Parameters List<String

> params;
@Parameters String[] paramsArray; // useful for compatability with existing classes

Here's an example of a simple Web Beans SE application:

@ApplicationScoped

public class HelloWorld
{
    @Parameters List<String
> parameters;
    public void printHello( @Observes @Deployed Manager manager )
    {
        System.out.println( "Hello " + parameters.get(0) );
    }
}

Web Beans SE applications are started by running the following main method.

java org.jboss.webbeans.environments.se.StartMain <args

>

If you need to do any custom initialization of the Web Beans manager, for example registering custom contexts or initializing resources for your beans you can do so in response to the @Initialized Manager event. The following example registers a custom context:

public class PerformSetup

{
    public void setup( @Observes @Initialized Manager manager )
    {
        manager.addContext( ThreadContext.INSTANCE );
    }
}

目前,Web Bean RI 只能在 JBoss AS 5 中執行;要將 RI 整合入其它 EE 環境中(比方說另一個像是 Glassfish 的應用程式伺服器)、整合入一個 servlet 容器(例如 Tomcat)中,或是和一個崁入式的 EJB3.1 實做整合都是相當容易的。在此附錄中,我們將簡略地討論所需的步驟。

The Web Beans SPI is located in webbeans-spi module, and packaged as webbeans-spi.jar. Some SPIs are optional, if you need to override the default behavior, others are required.

SPI 中所有的介面都支援裝飾器模式並提供了一個 Forwarding class。

Web Bean RI 也會委派 EJB3 bean discovery 至 container,因此它便無須掃描 EJB3 記號或剖析 ejb-jar.xml。針對於應用程式中的各個 EJB 都應該能發現一個 EJBDescriptor:

public interface EjbServices

{
   
   /**
    * Gets a descriptor for each EJB in the application
    * 
    * @return The bean class to descriptor map 
    */
   public Iterable<EjbDescriptor<?>> discoverEjbs();
public interface EjbDescriptor<T

> {
   
   /**
    * Gets the EJB type
    * 
    * @return The EJB Bean class
    */
   public Class<T
> getType();
   /**
    * Gets the local business interfaces of the EJB
    * 
    * @return An iterator over the local business interfaces
    */
   public Iterable<BusinessInterfaceDescriptor<?>
> getLocalBusinessInterfaces();
   
   /**
    * Gets the remote business interfaces of the EJB
    * 
    * @return An iterator over the remote business interfaces
    */
   public Iterable<BusinessInterfaceDescriptor<?>
> getRemoteBusinessInterfaces();
   
   /**
    * Get the remove methods of the EJB
    * 
    * @return An iterator over the remove methods
    */
   public Iterable<Method
> getRemoveMethods();
   /**
    * Indicates if the bean is stateless
    * 
    * @return True if stateless, false otherwise
    */
   public boolean isStateless();
   /**
    * Indicates if the bean is a EJB 3.1 Singleton
    * 
    * @return True if the bean is a singleton, false otherwise
    */
   public boolean isSingleton();
   /**
    * Indicates if the EJB is stateful
    * 
    * @return True if the bean is stateful, false otherwise
    */
   public boolean isStateful();
   /**
    * Indicates if the EJB is and MDB
    * 
    * @return True if the bean is an MDB, false otherwise
    */
   public boolean isMessageDriven();
   /**
    * Gets the EJB name
    * 
    * @return The name
    */
   public String getEjbName();
   
   
}

EjbDescriptor 本身已不解自明並且不需加以說明,它應會依照 EJB 規格中所定義地來回傳相關的 metadata。除了這兩個介面,還有個代表本地商業介面的 BusinessInterfaceDescriptor(包含了使用來查詢某個 EJB instance 的 interface class 以及 jndi 名稱)。

The resolution of @EJB and @Resource is delegated to the container. You must provide an implementation of org.jboss.webbeans.ejb.spi.EjbServices which provides these operations. Web Beans passes in the javax.inject.manager.InjectionPoint the resolution is for, as well as the NamingContext in use for each resolution request.

The Web Beans RI must delegate JTA activities to the container. The SPI provides a couple hooks to easily achieve this with the TransactionServices interface.

public interface TransactionServices

{
   /**
    * Possible status conditions for a transaction. This can be used by SPI
    * providers to keep track for which status an observer is used.
    */
   public static enum Status
   {
      ALL, SUCCESS, FAILURE
   }
   /**
    * Registers a synchronization object with the currently executing
    * transaction.
    * 
    * @see javax.transaction.Synchronization
    * @param synchronizedObserver
    */
   public void registerSynchronization(Synchronization synchronizedObserver);
   /**
    * Queries the status of the current execution to see if a transaction is
    * currently active.
    * 
    * @return true if a transaction is active
    */
   public boolean isTransactionActive();
}

The enumeration Status is a convenience for implementors to be able to keep track of whether a synchronization is supposed to notify an observer only when the transaction is successful, or after a failure, or regardless of the status of the transaction.

Any javax.transaction.Synchronization implementation may be passed to the registerSynchronization() method and the SPI implementation should immediately register the synchronization with the JTA transaction manager used for the EJBs.

To make it easier to determine whether or not a transaction is currently active for the requesting thread, the isTransactionActive() method can be used. The SPI implementation should query the same JTA transaction manager used for the EJBs.

Web Bean RI 對於 container 有些需求以便達到 API 實做之外的正確的功能。

Classloader 隔離

若您要將 Web Bean RI 整合入某個支援多重應用程式建置的環境中,您就必須為各個 Web Bean 應用程式透過自動的方式,或是透過用戶配置來啟用 classloader 隔離。

Servlet listener and filters

若您要將 Web Bean 整合入一個 Servlet 環境中,您就必須為各個使用 Servlet 的 Web Bean 應用程式透過自動的方式,或是用戶配置來將 org.jboss.webbeans.servlet.WebBeansListener 註冊為一個 Servlet listener,

If you are integrating the Web Beans into a JSF environment you must register org.jboss.webbeans.servlet.ConversationPropagationFilter as a Servlet listener, either automatically, or through user configuration, for each Web Beans application which uses JSF. This filter can be registered for all Servlet deployment safely.

Session Bean 攔截器

若您要將 Web Bean 整合入一個 EJB 環境中,您就必須針對於各個使用 enterprise bean 的 Web Bean 應用程式來為應用程式中的所有 EJB 透過自動的方式,或是透過用戶配置來將 org.jboss.webbeans.ejb.SessionBeanInterceptor 註冊為一個 EJB 攔截器。

The webbeans-core.jar

If you are integrating the Web Beans into an environment that supports deployment of applications, you must insert the webbeans-core.jar into the applications isolated classloader. It cannot be loaded from a shared classloader.