SeamFramework.orgCommunity Documentation
您是否已準備好開始編寫您的第一個 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 的各個 instance 的生命週期以及
哪些客戶端共享 Web Bean 的特定 instance 的參照。
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)的,因為:
它們透過了定義明確的公用 API 來進行互動
它們的生命週期已被完全地 decouple 了
我們能夠在不影響其它 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 類型,以及
一組綁定類型。
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) 中詳細討論。
Deployment type 可讓我們透過建置方案(deployment scenario)來為我們的 Web Bean 歸類。建置類型是個表示特定建置方案的標記,例如 @Mock
、@Staging
或 @AustralianTaxLaw
。我們會將標記套用至應透過該方案來建置的 Web Bean 中。建置類型能透過單行的配置來允許整組 Web Bean 被條件性地建置。
許多 Web Bean 只使用預設的建置類型 @Production
,在此情況下無須明確地指定建置類型。我們範例中的所有三個 Web Bean 都擁有 @Production
這個建置類型。
在測試環境中,我們可能會希望將 SentenceTranslator
這個 Web Bean 替換為一個「mock 物件」:
@Mock
public class MockSentenceTranslator implements Translator {
public String translate(String sentence) {
return "Lorem ipsum dolor sit amet";
}
}
我們將在測試環境中啟用 @Mock
這個 deployment type 來表示 MockSentenceTranslator
以及任何其它被標記為 @Mock
的 Web Bean 都應該被使用。
我們將在 節 4.2, “建置類型” 中更詳細地討論到這項獨特和強大的功能。
Scope 定義了 Web Bean instance 的生命週期以及可見度。Web Bean 的 context model 可延伸、並能包容任意的 scope。不過,有些重要的特定 scope 則會被內建在 Web Bean 規格中,並透過 Web Bean 管理員來提供。Scope 是以一個標記類型來表示的。
比方說,任何網站應用程式都能夠有 session scoped 的 Web Bean:
@SessionScoped
public class ShoppingCart { ... }
一個 session scoped 的 Web Bean 的 instance 會被綁定至一個用戶的 session,並且會被所有執行於該 session 的 context 中的請求共享。
就預設值,Web Bean 屬於一個稱為 dependent pseudo-scope 的特殊 scope。含有這個 scope 的 Web Bean 純粹就是它們所被注入的物件的相依物件,並且它們的生命週期綁定至該物件的生命週期。
我們將在 章 5, Scope 與 context 中詳細討論 scope。
Web Bean 能夠擁有一個名稱,這能允許它被使用於 Unified EL 表示式中。要指定 Web Bean 的名稱相當容易:
@SessionScoped @Named("cart")
public class ShoppingCart { ... }
現在我們能輕易地在任何 JSF 或 JSP 網頁中使用 Web Bean:
<h:dataTable value="#{cart.lineItems}" var="item"> .... </h:dataTable >
讓 Web Bean 管理員來預設這個名稱可能會更加容易:
@SessionScoped @Named
public class ShoppingCart { ... }
在此情況下,名稱將被預設為 shoppingCart
這是個不完整的 class 名稱,並且第一個字元已被改變為小寫。
Web Bean 支援 EJB 3 所定義的攔截器功能,不只是 EJB bean 支援,純 Java 的 class 也支援。另外,Web Bean 也提供了一個新的方法來將攔截器綁定至 EJB bean 和其它的 Web Bean。
您也能直接地透過使用 @Interceptors
標記來指定攔截器(interceptor)class:
@SessionScoped
@Interceptors(TransactionInterceptor.class)
public class ShoppingCart { ... }
不過,透過 interceptor binding type(攔截器綁定類型)來將攔截器綁定重定向會是較好的作法:
@SessionScoped @Transactional
public class ShoppingCart { ... }
我們將在 章 7, 攔截器(Interceptor) 和 章 8, 裝飾器(Decorators) 中更詳細地討論 Web Bean 攔截器與裝飾器。
我們已經看過 JavaBean、EJB 和一些其它的 Java class 都能屬於 Web Bean。不過 Web Bean 到底是哪種物件呢?
Web Bean 規格談到了一個具備下列條件的具體 Java class 就是個 simple(基本)Web Bean:
它不像 EJB、Servlet 或 JPA 實體都是個 EE container 所管理的元件、
它不是個非靜態的靜態內部類別(static inner class)、
它不是個被參數化的類型,並且
它有個無參數的 constructor,或一個標記為 @Initializer
的 constructor。
因此,幾乎所有 JavaBean 都屬於基本的 Web Bean。
基本 Web Bean 所直接或間接實做的所有介面都屬於基本 Web Bean 的 API 類型。Class 和它的 superclass 也都屬於 API 類型。
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 所提供的進階企業級服務時,例如:
method 層級的交易管理與安全性、
並行管理(concurrency management)、
有狀態的 session bean 的 instance 層級的 passivation 以及無狀態 session bean 的類別儲備(instance-pooling)、
遠端和網站服務調用,以及
計時器與非同步的 method,
當需要以上服務時我們便應使用企業級的 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 即可。
producer method 是個當目前 context 中沒有 instance 時會被 Web Bean 管理員調用來取得 Web Bean instance 的一個 method。Producer method 能讓應用程式完全掌控例示(instantiation)的程序,而不是留給 Web Bean 管理員去進行。例如:
@ApplicationScoped
public class Generator {
private Random random = new Random( System.currentTimeMillis() );
@Produces @Random int next() {
return random.nextInt(100);
}
}
producer method 的結果會和其它任何 Web Bean 一樣地被注入。
@Random int randomNumber
method 的回傳類型以及它所直接或間接延伸/實做的介面皆為 producer method 的 API 類型。若回傳的類型是個 class 的話,那麼所有的 superclass 也都會是 API 類型。
有些 producer method 會回傳需要明確銷毀的物件:
@Produces @RequestScoped Connection connect(User user) {
return createConnection( user.getId(), user.getPassword() );
}
這些 producer method 可定義符合的 disposal methods:
void close(@Disposes Connection connection) {
connection.close();
}
這個 disposal method 會在請求結束後自動地被 Web Bean 管理員調用。
我們將在 章 6, Producer method 中詳細討論 producer method。
最後,JMS 佇列(queue)或主題(topic)都能是個 Web Bean。Web Bean 能讓開發人員省去管理所有傳送訊息至佇列和主題所需的不同 JMS 物件生命週期的麻煩。我們將在 節 13.4, “JMS 端點” 中詳細討論 JMS 端點。