SeamFramework.orgCommunity Documentation
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 服務提供了:
狀態元件上的生命週期改善,並綁定至明確定義的 contexts、
一個用來進行 dependency injection(依賴注入)的 typesafe(類型安全列舉)方式、
透過 event notification(事件通知)功能來進行的互動,以及
一個將 interceptors(攔截器)綁定至元件的較佳方式,以及一個稱為 decorator(裝飾器)的新型攔截器,它適用於解決商業上的相關問題。
依賴注入和 contextual 生命週期管理這兩者組合起來可讓一個不熟悉的 API 的用戶無須過問下列問題:
這個物件的生命週期為何?
它一次能同時有幾個客戶端?
它是否為多執行緒(multithread)?
我能從哪裡取得呢?
我是否需要明確地將它毀掉呢?
當我沒有在直接使用它時,我應該將它的參照保留在哪裡呢?
我該如何新增一個 indirection layer 來使這個物件在建置時的實做可呈多樣化?
我該如何在其它物件之間共享這個物件?
Web Bean 只會指定它所依賴的 Web Bean 的類型和語意。它無須知道實際的生命週期、具體的實做、執行緒模型,或是任何它所依賴的 Web Bean 的其它客戶端。更好的是,它所依賴的 Web Bean 的具體實做、生命週期和執行緒模型可根據建置的情況來改變,並且不影響到任何的客戶端。
事件、攔截器以及裝飾器可增強在此模型中所繼承的 loose-coupling(鬆散結合性):
event notifications(事件通知)會 decouple producer 和 event consumer(事件用戶),
interceptors(攔截器)可由商業邏輯去 decouple 技術問題,並且
decorators(裝飾器)可將商業問題分為若干部分。
最重要的是,Web Bean 以一種 typesafe 的方式來提供了所有的這些功能。Web Bean 從不使用基於字串的識別符號(identifier)來斷定共同協作的物件如何相輔相成。雖然 XML 也是選項之一不過卻很少被使用到。取而代之,Web Bean 使用了 Java 物件模型中可使用的 typing 資訊並統合了一個稱為 binding annotations(綁定標記)的新格式,它可將 Web Bean、它們的相依性、它們的攔截器和裝飾器以及它們的事件用戶聯繫在一起。
Web Bean 服務為通用的並且適用於下列位於 Java EE 環境中的元件類型:
所有 JavaBean、
所有 EJB,以及
所有 Servlet。
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 標準。
內容目錄
您是否已準備好開始編寫您的第一個 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 端點。
現在我們將採用一個完整的範例來描述這些概念。我們將要為一個使用 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.bat
script.
請等待應用程式的建置,然後在 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.bat
script.
請等待應用程式的建置,然後在 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
:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" 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-app_2_5.xsd"> <display-name >Web Beans Numbergues example</display-name> <!-- JSF --><servlet> <servlet-name >Faces Servlet</servlet-name> <servlet-class >javax.faces.webapp.FacesServlet</servlet-class> <load-on
-startup >1</load-on-startup> </servlet> <servlet-mapping> <servlet
-name >Faces Servlet</servlet-name> <url-pattern >*.jsf</url-pattern> </servlet-mapping>
<context-param> <param-name >javax.faces.DEFAULT_SUFFIX</param-name> <param-v
alue >.xhtml</param-value> </context-param> <session-config> <session-timeout >10</session-timeout> </session-config> </web-app >
![]() | Enable and load the JSF servlet |
![]() | Configure requests to |
![]() | Tell JSF that we will be giving our source files (facelets) an extension of |
![]() | Configure a session timeout of 10 minutes |
Whilst this demo is a JSF demo, you can use Web Beans with any Servlet based web framework.
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:composition template="template.xhtml"> <ui:define name="content"> <h1 >Guess a number...</h1> <h:form
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
> I'm thinking of a number between #{game.smallest} and #{game.biggest}. You have #{game.remainingGuesses} guesses. </div> <div> Y
our guess: <h:inputText id="inputGuess" value="#{game.guess}" required="true" size="3"
disabled="#{game.number eq game.guess}"> <f:validateLongRange maximum="#{game.biggest}" minimum="#{game.smallest}"/> <
/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 >
![]() | Facelets is a templating language for JSF, here we are wrapping our page in a template which defines the header. |
![]() | There are a number of messages which can be sent to the user, "Higher!", "Lower!" and "Correct!" |
![]() | 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. |
![]() | This input field is bound to a Web Bean, using the value expression. |
![]() | 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. |
![]() | 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
>(){});
}
}
The numberguess for Tomcat differs in a couple of ways. Firstly, Web Beans should be deployed as a Web Application library in WEB-INF/lib
. For your convenience we provide a single jar suitable for running Web Beans on Tomcat webbeans-tomcat.jar
.
Of course, you must also include JSF and EL, as well common annotations (jsr250-api.jar
) which a JEE server includes by default.
Secondly, we need to explicitly specify the Tomcat servlet listener (used to boot Web Beans, and control it's interaction with requests) in web.xml
:
<listener> <listener-class>org.jboss.webbeans.environment.tomcat.Listener</listener-class> </listener>
轉譯器範例能接受您所輸入的任何句子,然後將它們翻譯成拉丁文。
轉換器範例被建置為一個 ear 並包含著 EJB。正因如此,它的結構比 numberguess 範例要複雜得多。
EJB 3.1 和 Jave EE 6 能讓您將 EJB 封裝為 war,並使得該結構變得更為簡樸!
首先,讓我們先來看一下 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,SentenceParser
和 TextTranslator
,以及兩個企業級的 bean,TranslatorControllerBean
和 SentenceTranslator
。到了現在您應該已經很熟悉 Web Bean 長得如何了,因此我們在此將只著重於其它較有趣的部份。
SentenceParser
和 TextTranslator
兩者皆為相依性的 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;
}
綁定標記能夠有 member:
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayBy {
PaymentType value();
}
在此情況下,member value 非常重要:
@PayBy(CHEQUE) PaymentProcessor chequePaymentProcessor;
@PayBy(CREDIT_CARD) PaymentProcessor creditCardPaymentProcessor;
您可藉由標記 @NonBinding
這個 member 來讓 Web Bean 管理員忽略某個綁定標記類型的成員。
一個注入點甚至有可能會指定多重綁定標記:
@Asynchronous @PayByCheque PaymentProcessor paymentProcessor
在這種情況下,只有擁有這兩個綁定標記的 Web Bean 可被注入。
就連 producer method 也能指定綁定標記:
@Produces
@Asynchronous @PayByCheque
PaymentProcessor createAsyncPaymentProcessor(@PayByCheque PaymentProcessor processor) {
return new AsynchronousPaymentProcessor(processor);
}
所有 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 定義了兩個內建的 deployment type:@Production
和 @Standard
。就預設值,當系統被建置時,只有含有內建 deployment type 的 Web Bean 會被啟用。我們可藉由將特定 deployment type 列在 web-beans.xml
中來識別在特定 deployment 中可啟用的額外 deployment type。
回到我們的範例,當我們建置我們的整合測試時,我們希望我們所有的 @Mock
物件都會被安裝:
<WebBeans>
<Deploy>
<Standard/>
<Production/>
<test:Mock/>
</Deploy>
</WebBeans
>
現在,Web Bean 管理員將會在 deployment time 找出並安裝所有標記為 @Production
、@Standard
或 @Mock
的 Web Bean。
@Standard
這個 deployment type 只會被用於 Web Bean 規格所定義的特定特殊 Web Bean。我們無法將它使用於我們自己的 Web Bean 上,並且我們無法將它停用。
@Production
這個 deployment type 屬於未明確定義 deployment type 的 Web Bean 的預設 deployment type,並且可被停用。
若您有在細心注意的話,您應該會疑惑 Web Bean 管理員如何決定哪個實做 ExternalPaymentProcessor
和 MockPaymentProcessor
中選擇哪一個。請思考當管理員遇上了這個注入點時會如何:
@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)。
就所有實做某個注入點的 API 類型的 Web Bean 綁定標記和 deployment type 來講,若 Web Bean 管理員無法辨識出正好一個需被注入的 Web Bean 為何,那麼 typesafe 解析演算法便會失敗。
要修正一個 UnsatisfiedDependencyException
或 AmbiguousDependencyException
通常相當容易。
若要修正一項 UnsatisfiedDependencyException
,只要提供一個實做 API 類型並且擁有注入點的綁定類型的 Web Bean 即可 或是啟用一個已實做 API 類型並且擁有綁定類型的 Web Bean 的 deployment type 即可。
若要修正一項 AmbiguousDependencyException
,您可加入一個綁定類型來在兩個 API 類型的實做之間進行分辨,或是更改其中一個實做的 deployment type,這樣一來 Web Bean 管理員便可透過使用 deployment type 優先權來在它們之間作選擇。AmbiguousDependencyException
只會在有兩個 Web Bean 共享一個綁定類型並擁有相同 deployment type 的情況下才會發生。
當您在 Web Bean 中使用依賴注入時,您還需要注意一個問題。
一個已注入的 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 管理員代理:
所有被宣告 final
或是含有一個 final
method 的 class、
沒有非私密 constructor 的無參數 class,以及
陣列(array)與基本型別(primitive type)。
要修正 UnproxyableDependencyException
通常相當容易。只要將一個無參數的 constructor 附加至注入的 class、採用一個介面,或將已注入的 Web Bean 的 scope 更改為 @Dependent
即可。
應用程式可透過注入來取得 Manager
這個介面的一個 instance:
@Current Manager manager;
Manager
這個物件提供了一組用來程式性地取得 Web Bean instance 的 method。
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class);
綁定標記能被透過建立 helper class 的 subclass AnnotationLiteral
來指定,否則在 Java 中很難例示一個標記類型。
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class,
new AnnotationLiteral<CreditCard
>(){});
若綁定類型有個標記成員,我們便無法使用 AnnotationLiteral
的一個匿名 subclass 我們需要建立一個有命名的 subclass:
abstract class CreditCardBinding
extends AnnotationLiteral<CreditCard
>
implements CreditCard {}
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class,
new CreditCardBinding() {
public void value() { return paymentType; }
} );
企業級的 Web Bean 支援 EJB 規格所定義的所有 lifecycle callback:@PostConstruct
、@PreDestroy
、@PrePassivate
和 @PostActivate
。
基本的 Web Bean 只支援 @PostConstruct
和 @PreDestroy
callback。
企業級和基本的 Web Bean 皆支援使用 @Resource
、@EJB
和 @PersistenceContext
來相應地注入 Java EE 資源、EJB 和 JPA 的 persistence context。基本的 Web Bean 不支援使用 @PersistenceContext(type=EXTENDED)
。
@PostConstruct
callback 一定會在所有相依性都被注入後才會發生。
有幾種特定相依物件 含有 @Dependent
這個 scope 的 Web Bean 需要知道有關於物件或是它們被注入的注入點相關資訊才能進行它們本應進行的工作。比方說:
Logger
的 log category 取決於擁有它的物件的 class。
HTTP 參數或是 header value 的注入取決於在注入點所指定的參數或標頭名稱。
EL 運算式評估(expression evaluation)結果的注入取決於在注入點所指定的運算式。
含有 @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 <T extends Annotation
> T getAnnotation(Class<T
> annotation);
public Set<T 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 含有著一個 extensible context model(可延伸的 context 模型)。您可藉由建立新的 scope 類型標記來定義新的 scope:
@Retention(RUNTIME)
@Target({TYPE, METHOD})
@ScopeType
public @interface ClusterScoped {}
當然,那是這項工作中最簡單的部份。若要此 scope 類型能被有效使用,我們還需要定義一個能夠實做該 scope 的 Context
物件!實做一個 Context
通常是一項非常技術性的工作,這只應使用於架構開發(framework development)上。
我們可套用一個 scope 類型標記至一個 Web Bean 實做 class 來指定 Web Bean 的 scope:
@ClusterScoped
public class SecondLevelCache { ... }
您通常會使用到其中一個 Web Bean 的內建 scope。
Web Bean 定義了四個內建的 scope:
@RequestScoped
@SessionScoped
@ApplicationScoped
@ConversationScoped
使用 Web Bean 的網站應用程式:
任何 servlet 請求都可存取 active request、session 和應用程式 scope,另外
任何 JSF 請求也都能存取一個現行的 conversation scope。
請求和應用程式 scope 在下列情況中會是有效的:
當調用 EJB 的遠端 method 時、
當 EJB 逾時時、
當訊息傳送給訊息導向的 bean 時,以及
當進行網路服務調用時。
若應用程式嘗試透過一個沒有有效 context 的 scope 來引動 Web Bean 的話,Web Bean 管理員便會在 runtime 時回傳一項 ContextNotActiveException
。
四個內建 scope 中有其中的三個對於所有 Java EE 開發人員來說應該都不陌生,所以我們將不花時間在此討論它們。不過在這之中有個 scope 則是新的。
Web Bean 的 conversation scope 和傳統的 session scope 類似,它們都持有著有關於系統用戶的狀態,並發出多重請求至伺服器。conversation scope 和 session scope 不同的地方在於:
conversation scope 已被應用程式明確區分,並且
它在 JSF 應用程式中會持有和特定網站瀏覽器分頁相關的狀態。
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 的生命週期完全取決於另一個物件。
conversation context 會隨著任何 JSF face 的請求(JSF form submission)自動地傳播。它不會隨著 non-face 的請求(例如透過連結來瀏覽)自動地傳播。
我們能夠透過包含 conversation 的唯一識別碼(unique identifier)來作為一個請求參數以強制 conversation 隨著 non-face 的請求進行傳播。Web Bean 規格保留了一個名為 cid
的請求參數以用於此情況下。conversation 的唯一識別碼可藉由 Conversation
這個物件取得,並且它的 Web Bean 名稱為 conversation
。
因此,下列連結會傳播 conversation:
<a href="/addProduct.jsp?cid=#{conversation.id}" >Add Product</a >
Web Bean 管理員也必須將 conversation 在任何重定向作業之間進行傳播,儘管該 conversation 沒有被標記為 long-running。這使得實做一般的 POST-then-redirect 模式變得非常簡單,這樣便無須使用一些像是「flash」物件的脆弱 construct。在此情況下,Web Bean 管理員會自動地新增一個請求參數至重定向 URL。
Web Bean 管理員能夠隨時隨地刪除它 context 中的 conversation 以及所有狀態以保留資源。一個 Web Bean 管理員實做一般會利用某種形式的 timeout 來這麼作 儘管這在 Web Bean 規格中是非必要的。Timeout 代表 conversation 在被刪除之前所經過的休止狀態時間。
Conversation
這個物件提供了一個能設置 timeout 的 method。這是個給 Web Bean 管理員的提示,您可忽略該設定。
conversation.setTimeout(timeoutInMillis);
除了這四個內建的 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。
內建的 @New
綁定標記允許在注入點能夠有暗示性的相依 Web Bean 定義。假設我們宣告下列被注入的欄位:
@New Calculator calculator;
如此一來有個含有 scope @Dependent
、綁定類型 @New
、API 類型 Calculator
、實做 class Calculator
以及建置類型 @Standard
的 Web Bean 就被暗示性地定義了。
就算 Calculator
已經被宣告為不同 scope type,這還是有效的,比方說:
@ConversationScoped
public class Calculator { ... }
因此下列已注入的屬性都會各得到一個 Calculator
的不同 instance:
public class PaymentCalc {
@Current Calculator calculator;
@New Calculator newCalculator;
}
calculator
這個欄位中注入了一個 Calculator
的 conversation-scoped instance。newCalculator
欄位中有個新的 Calculator
的 instance 被注入了,並且它的生命週期取決於擁有它的 PaymentCalc
。
這項功能對於 producer method 來講特別有幫助,我們將在下個章節中討論到。
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.
.producer method 的預設 scope 為 @Dependent
,所以每當 Web Bean 管理員注入此欄位或是其它任何會解析為相同 producer method 的欄位時,@Dependent
就會被調用。因此,各個用戶 session 都可能會有多個 PaymentStrategy
物件的 instance 出現。
若要更改此特性,我們可將 @SessionScoped
這個標記附加至 method。
@Produces @Preferred @SessionScoped
public PaymentStrategy getPaymentStrategy() {
...
}
現在,當 producer method 被調用時,回傳的 PaymentStrategy
將會被綁定至 session context。Producer method 將不會在相同的 session 中再次被調用。
上述程式碼有個潛在的問題。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
綁定標記。
考慮下列 producer method:
@Produces @Preferred @SessionScoped
public PaymentStrategy getPaymentStrategy(@New CreditCardPaymentStrategy ccps,
@New ChequePaymentStrategy cps,
@New PayPalPaymentStrategy ppps) {
switch (paymentStrategy) {
case CREDIT_CARD: return ccps;
case CHEQUE: return cps;
case PAYPAL: return ppps;
default: return null;
}
}
接著有個新的 CreditCardPaymentStrategy
的 dependent instance 將會被建立、傳送至 producer method、被 producer method 回傳然後最後被綁定至 session context。在 Preferences
物件於 session 結束時被毀掉之前,dependent 物件不會被毀掉。
Web Bean 的第一個重點就是 loose coupling(鬆散結合性)。我們已經看過了三種 loose coupling 的方式:
deployment types(建置類型)可啟用建置時間多型性(deployment time polymorphism)、
producer methods 可啟用 runtime Polymorphism(執行期多型性),以及
contextual lifecycle management 可 decouple Web Bean 的生命週期。
這些技巧都可用來啟用客戶端以及伺服器的 loose coupling。客戶端已不再固定綁定至一個特定 API 的實做,並且它亦無須管理伺服器物件的生命週期。這個方法能讓有狀態的物件被視為服務一般地來進行互動。
Loose coupling 會使系統變得更加動態式。系統可透過充足定義的方式來回應變更。過去,有許多 framework 嘗試了提供以上所列出的功能,不過卻都是藉由犧牲了 type safety 來達成的。Web Bean 是第一個以 typesafe 的方式來實現此層級的 loose coupling 的技術。
Web Bean 提供了三個額外的重要功能以用來達成 loose coupling:
interceptors(攔截器)可區分技術問題與商業邏輯,
decorators(裝飾器)可被用來區分一些商業問題,並且
event notifications(事件通知)可區分事件產生器(event producer)與事件用戶(event consumer)。
讓我們先來探討攔截器(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。
假設我們希望宣告我們有些 Web Bean 屬於交易性的 Web Bean。我們首先需要的就是一個 攔截器綁定標記(interceptor binding annotation)來明確指定我們要專注的 Web Bean 是哪個:
@InterceptorBindingType
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Transactional {}
現在我們能輕易地指定我們的 ShoppingCart
屬於一個交易性的物件:
@Transactional
public class ShoppingCart { ... }
或是,若我們想要的話,我們能夠指定只有一個 method 屬於交易性:
public class ShoppingCart {
@Transactional public void checkout() { ... }
}
我們必須在某個階段實際地實做提供此交易管理功能的攔截器。我們只需要建立一個標準的 EJB 攔截器並將它標記為 @Interceptor
和 @Transactional
。
@Transactional @Interceptor
public class TransactionInterceptor {
@AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
}
所有的 Web Bean 攔截器皆為基本的 Web Bean,並且能夠有效利用依賴注入(dependency injection)以及 contextual lifecycle 管理。
@ApplicationScoped @Transactional @Interceptor
public class TransactionInterceptor {
@Resource Transaction transaction;
@AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
}
多個攔截器可能會使用相同的攔截器綁定類型。
最後,我們需要在 web-beans.xml
中啟用我們的攔截器。
<Interceptors>
<tx:TransactionInterceptor/>
</Interceptors
>
為何會有尖括弧?
XML 宣告可解決兩項問題:
它可讓我們為我們系統中的所有攔截器指定全排序(total ordering),並確保決定型的特性,並且
它讓我們能夠在進行建置時啟用或停用攔截器 class。
比方說,我們能夠指定我們的安全性攔截器能比我們的 TransactionInterceptor
還要早執行。
<Interceptors>
<sx:SecurityInterceptor/>
<tx:TransactionInterceptor/>
</Interceptors
>
或是我們可在測試環境中將它們兩者同時關閉!
假設我們希望新增一些額外的資訊至我們的 @Transactional
標記中:
@InterceptorBindingType
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Transactional {
boolean requiresNew() default false;
}
Web Bean 將會使用 requiresNew
的值來在兩個不同的攔截器(TransactionInterceptor
與 RequiresNewTransactionInterceptor
)之間作選擇。
@Transactional(requiresNew=true) @Interceptor
public class RequiresNewTransactionInterceptor {
@AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
}
現在我們可如下使用 RequiresNewTransactionInterceptor
:
@Transactional(requiresNew=true)
public class ShoppingCart { ... }
當綁定攔截器時,若我們只有一個攔截器而我們希望管理員忽略 requiresNew
的值時該怎麼辦呢?這時我們可使用 @NonBinding
標記:
@InterceptorBindingType
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Secure {
@NonBinding String[] rolesAllowed() default {};
}
通常,我們會使用不同攔截器綁定類型的組合來將多重攔截器綁定至 Web Bean。比方說,下列宣告會被用來將 TransactionInterceptor
和 SecurityInterceptor
綁定至相同的 Web Bean:
@Secure(rolesAllowed="admin") @Transactional
public class ShoppingCart { ... }
不過,在非常複雜的情況下,攔截器本身會指定一些攔截器綁定類型的組合:
@Transactional @Secure @Interceptor
public class TransactionalSecureInterceptor { ... }
然後這個攔截器便可透過使用下列任何一個組合來綁定至 checkout()
這個 method:
public class ShoppingCart {
@Transactional @Secure public void checkout() { ... }
}
@Secure
public class ShoppingCart {
@Transactional public void checkout() { ... }
}
@Transactionl
public class ShoppingCart {
@Secure public void checkout() { ... }
}
@Transactional @Secure
public class ShoppingCart {
public void checkout() { ... }
}
Java 語言對於標記上的支援有個限制,那就是缺少了標記的 inheritance。實際上,標記應該要內建 reuse,以便允許這類型的功能有效:
public @interface Action extends Transactional, Secure { ... }
幸運的是,Web Bean 有方法來解決 Java 所缺少的這項功能上的問題。我們可利用其它攔截器綁定類型來標記某個攔截器綁定類型。攔截器綁定為可轉變的 任何含有第一個攔截器綁定的 Web Bean 都會繼承宣告為 meta-annotation 的攔截器綁定。
@Transactional @Secure
@InterceptorBindingType
@Target(TYPE)
@Retention(RUNTIME)
public @interface Action { ... }
任何標記為 @Action
的 Web Bean 都會被綁定至 TransactionInterceptor
和 SecurityInterceptor
。(甚至是 TransactionalSecureInterceptor
,若它存在的話。)
企業級與基本的 Web Bean 皆支援 EJB 規格所定義的 @Interceptors
標記,例如:
@Interceptors({TransactionInterceptor.class, SecurityInterceptor.class})
public class ShoppingCart {
public void checkout() { ... }
}
不過,這個方式有下列缺點:
攔截器的實做會被 hardcode 在 business code 之中、
攔截器無法在進行建置時輕易地被停用,以及
攔截器的順序會是非全域性的 會由攔截器被列於 class 層級的順序來取決。
因此,我們建議使用 Web Bean 形式的攔截器綁定。
呼叫攔截器(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。
所有的裝飾器都有個 delegate 屬性。delegate 屬性的類型以及綁定類型可斷定裝飾器應綁定至哪些 Web Bean。delegate 屬性類型必須實做或是延伸裝飾器所實做的所有介面。
下列 delegate 屬性指出裝飾器已綁定至所有實做 Account
的 Web Bean:
@Decorates Account account;
Delegate 屬性可指定一個 binding annotation(綁定註解)。如此一來裝飾器便只會綁定至含有相同 binding 的 Web Bean。
@Decorates @Foreign Account account;
一個裝飾器會綁定至任何符合以下條件的 Web Bean:
擁有一個 API 類型的 delegate 屬性類型,以及
擁有 delegate 屬性所宣告的所有綁定類型。
裝飾器能夠引動 delegate 屬性,這和透過一個攔截器調用 InvocationContext.proceed()
的效果大致相同。
Web Bean 事件通知功能能讓 Web Bean 以一個完全 decouple 的方式來進行互動。事件產生器(producers)會產生事件並且之後會被透過 Web Bean 管理員來傳送給事件觀察器(observers)。這個基本的 schema 可能看起來和熟悉的觀察器/可觀察的格式類似,不過卻有幾點不大相同:
不只是事件產生器由觀察器被 decouple;觀察器也完全地由產生器被 decouple 了,
觀察器可指定一組「選擇器(selectors)」的組合來過濾並減少它們所將會收到的事件通知數量,並且
觀察器可即刻地被通知,或是您亦可指定將事件的傳送延遲到目前交易動作(又稱為工作邏輯單元)結束之後
observer method 是個 Web Bean 的 method 並有個標記為 @Observes
的參數。
public void onAnyDocumentEvent(@Observes Document document) { ... }
這個被標記的參數稱為事件參數(event parameter)。這個事件參數的 type 相當於被觀察到的事件類型(event type)。Observer method 也能指定「selector」,它們只不過是 Web Bean 綁定類型(binding types)的 instance。當有個綁定類型被用來作為一個事件選擇器時,它就會被稱為是一個事件綁定類型(event binding type)。
@BindingType
@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Updated { ... }
我們可藉由標記事件參數來指定 observer method 的事件綁定:
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }
一個 observer method 不需要指定任何的事件綁定 在此情況下它會專注於特定 type 的所有事件。若它指定了事件綁定的話,它就只會專注於也具有這些事件綁定的事件。
Observer method 能夠含有額外的參數,這些參數會被根據一般的 Web Bean method 參數注入語意(parameter injection semantic)來注入:
public void afterDocumentUpdate(@Observes @Updated Document document, User user) { ... }
事件產生器可透過注入來取得一個事件通知器(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:
含有可指派 event object 的事件參數,以及
沒有指定事件綁定的 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:
含有可指派 event object 的事件參數,以及
不會指定任何的事件綁定,除了傳送給 fire()
的事件綁定。
另外,事件綁定亦可透過標記事件通知器注入點來指定:
@Observable @Updated Event<Document
> documentUpdatedEvent
若是如此,所有透過 Event
的 instance 來產生的事件都會含有被標記的事件綁定。事件會被傳送給符合下列條件的每個觀察器 method:
含有可指派 event object 的事件參數,以及
不會指定任何的事件綁定,除了傳送給 fire()
的事件綁定或是被標記的事件通知器注入點的事件綁定。
通常以動態式的方式來註冊事件觀察器相當地有幫助。應用程式可實做 Observer
介面然後藉由調用 observe()
method 來以一個事件通知器去註冊某個 instance。
documentEvent.observe( new Observer<Document
>() { public void notify(Document doc) { ... } } );
事件綁定類型能由事件通知器注入點來指定,或是透過將事件綁定類型的 instance 傳送至 observe()
method:
documentEvent.observe( new Observer<Document
>() { public void notify(Document doc) { ... } },
new AnnotationLiteral<Updated
>(){} );
事件綁定類型能夠有下列標記成員:
@BindingType
@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Role {
RoleType value();
}
member value(這是個編譯期常數)會被使用來過濾並減少傳送至觀察器的訊息數量:
public void adminLoggedIn(@Observes @Role(ADMIN) LoggedIn event) { ... }
事件綁定類型的成員可由事件產生器在事件通知器的注入點透過標記來靜態地指定:
@Observable @Role(ADMIN) Event<LoggedIn
> LoggedInEvent;}}
另外,事件綁定類型成員的值亦可藉由事件產生器來動態式地判斷出。我們首先先從編寫一個 AnnotationLiteral
的 abstract subclass 開始:
abstract class RoleBinding
extends AnnotationLiteral<Role
>
implements Role {}
事件產生器會將這個 class 的一個 instance 傳送至 fire()
:
documentEvent.fire( document, new RoleBinding() { public void value() { return user.getRole(); } } );
事件綁定類型可被合併,例如:
@Observable @Blog Event<Document
> blogEvent;
...
if (document.isBlog()) blogEvent.fire(document, new AnnotationLiteral<Updated
>(){});
當這個事件發生時,下列所有 observer method 都會被通知:
public void afterBlogUpdate(@Observes @Updated @Blog Document document) { ... }
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }
public void onAnyBlogEvent(@Observes @Blog Document document) { ... }
public void onAnyDocumentEvent(@Observes Document document) { ... }}}
交易觀察器會在事件被產生後,於交易完成之前或之後收到它們的事件通知。比方說,下列 observer method 需要更新一組快取儲存於應用程式 context 中的查詢結果,不過只有更新了 Category
tree 的交易會成功:
public void refreshCategoryTree(@AfterTransactionSuccess @Observes CategoryUpdateEvent event) { ... }
交易觀察器分為三種類型:
@AfterTransactionSuccess
觀察器會在交易完成之後的階段才會被調用,不過僅限於交易成功完成的情況下
@AfterTransactionFailure
觀察器會在交易完成之後的階段才會被調用,不過僅限於交易無法成功完成的情況下
@AfterTransactionCompletion
觀察器會在交易完成之後的階段才會被調用
@BeforeTransactionCompletion
觀察器會在交易完成之前的階段被調用
交易觀察器在一個像是 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 鼓勵您使用塑造概念的標記,例如
@Asynchronous
、
@Mock
、
@Secure
或
@Updated
、
而不是使用像是
asyncPaymentProcessor
、
mockPaymentProcessor
、
SecurityInterceptor
或是
DocumentUpdatedEvent
的複合式名稱。
這些標記可重複使用。它們協助描述系統不同部份的通用要點(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 指定預設的 scope 與/或預設的建置類型。比方說,若 @WebTier
這個建置類型將只應在系統被執行時才被建置的 Web Bean 視為一個網站應用程式的話,我們可為 action class 指定下列預設值:
@Retention(RUNTIME)
@Target(TYPE)
@RequestScoped
@WebTier
@Stereotype
public @interface Action {}
當然,若有必要的話,有個特定的動作也可置換這些預設值:
@Dependent @Mock @Action
public class MockLoginAction { ... }
若我們希望強制所有動作依照某個特定 scope 的話,我們也可以這麼作。
假設我們希望防止動作去宣告特定 scope,Web Bean 能讓我們透過特定主要探討模板來明確地為 Web Bean 指定一組被允許的 scope。例如:
@Retention(RUNTIME)
@Target(TYPE)
@RequestScoped
@WebTier
@Stereotype(supportedScopes=RequestScoped.class)
public @interface Action {}
若有個特定 action class 嘗試指定一個非 Web Bean 所請求的 scope 的話,Web Bean 管理員便會在初始化時丟出一個 exception。
我們也能強制所有含有特定主要探討模板的 Web Bean 實做一個介面或延伸一個 class:
@Retention(RUNTIME)
@Target(TYPE)
@RequestScoped
@WebTier
@Stereotype(requiredTypes=AbstractAction.class)
public @interface Action {}
若某個特定 action class 沒有延伸 AbstractAction
這個 class 的話,Web Bean 管理員便會在初始化時丟出一個 exception。
主要探討模板可指定一組攔截器綁定來被所有含有該主要探討模板的 Web Bean 繼承。
@Retention(RUNTIME)
@Target(TYPE)
@RequestScoped
@Transactional(requiresNew=true)
@Secure
@WebTier
@Stereotype
public @interface Action {}
這可協助我們減少 business code 中技術上的問題!
最後,我們能夠指定所有含有特定主要探討模板的 Web Bean 都擁有一組 Web Bean 名稱,並且由 Web Bean 管理員來預設。動作通常參照於 JSP 頁面中,因此它們針對於此功能為完美的 use case。我們只需要新增一個空的 @Named
標記即可:
@Retention(RUNTIME)
@Target(TYPE)
@RequestScoped
@Transactional(requiresNew=true)
@Secure
@Named
@WebTier
@Stereotype
public @interface Action {}
現在,LoginAction
將會被命名為 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 一開始看起來似乎有些難懂,不過實際上卻相當易於使用,並且您將會慶幸擁有它所提供的額外安全性。
Specialization 是一項基於基本和企業級 Web Bean 的功能。若要有效使用 specialization,擁有較高優先權的 Web Bean 必須:
是個它所置換的 Web Bean 的直接 subclass,並且
若它所置換的 Web Bean 是個基本的 Web Bean,它就必須是個基本 Web Bean,或是若它所置換的 Web Bean 是個企業級的 Web Bean,那它就必須是個企業級的 Web Bean,並且
需被標記 @Specializes
。
@Stateless @Staging @Specializes
public class StagingCreditCardPaymentProcessor
extends CreditCardPaymentProcessor {
...
}
優先權較高的 Web Bean 會 specializes 它的 superclass。
當使用了 specialization 時:
superclass 的綁定類型就會自動地被標記為 @Specializes
的 Web Bean 繼承,並且
superclass 的 Web Bean 名稱會自動地被標記為 @Specializes
的 Web Bean 繼承,並且
superclass 所宣告的 producer method、disposal method 以及 observer method 會被一個標記了 @Specializes
的 Web Bean instance 調用。
在我們的範例中,CreditCardPaymentProcessor
的 @CreditCard
綁定類型會被 StagingCreditCardPaymentProcessor
繼承。
另外,Web Bean 管理員會針對於以下情形進行驗證:
superclass 的所有 API 類型都會是標記為 @Specializes
的 Web Bean 的 API 類型(superclass enterprise bean 的所有本地介面也都屬於 subclass 的本地介面)、
標記為 @Specializes
的 Web Bean 的 deployment type 擁有比 superclass 的 deployment type 還要高的優先權,以及
沒有其它已啟用的 Web Bean 會 specialize 該 superclass。
若任何的這些條件被違反的話,Web Bean 管理員便會在初始化時回傳一個 exception。
因此,我們能夠確定當標記為 @Specializes
的 Web Bean 被建置並啟用的情況下,superclass 絕不會在系統的任何 deployment 中被調用。
到目前為止,我們已看過了許多透過使用標記來宣告的 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 會針對於各個 Java 套件來定義相應的 XML namespace。這個 namespace 是藉由將 urn:java:
放置在 Java 套件名稱之前所形成的。com.mydomain.myapp
這個套件的 XML namespace 會是 urn:java:com.mydomain.myapp
。
屬於套件的 Java type 會被參照使用和該套件相應的 namespace 中的一個 XML 要素。該要素的名稱就是 Java type 的名稱。這個 type 的欄位和 method 會由相同 namespace 中的子要素所指定。若這個 type 為標記的話,成員則會由要素的屬性所指定。
比方說,下列這段 XML 中的 <util:Date/>
要素代表 java.util.Date
這個 class:
<WebBeans xmlns="urn:java:javax.webbeans"
xmlns:util="urn:java:java.util">
<util:Date/>
</WebBeans
>
並且這些為我們需要宣告的所有程式碼,Date
是個基本的 Web Bean!Date
的 instance 現在已能被任何其它 Web Bean 注入:
@Current Date date
我們能直接透過使用 Web Bean 宣告的子要素來宣告功能、建置類型以及攔截器綁定類型:
<myapp:ShoppingCart>
<SessionScoped/>
<myfwk:Transactional requiresNew="true"/>
<myfwk:Secure/>
</myapp:ShoppingCart
>
我們能使用完全相同的方式來指定名稱與綁定類型:
<util:Date>
<Named
>currentTime</Named>
</util:Date>
<util:Date>
<SessionScoped/>
<myapp:Login/>
<Named
>loginTime</Named>
</util:Date>
<util:Date>
<ApplicationScoped/>
<myapp:SystemStart/>
<Named
>systemStartTime</Named>
</util:Date
>
@Login
和 @SystemStart
為綁定標記類型(binding annotations type)。
@Current Date currentTime;
@Login Date loginTime;
@SystemStart Date systemStartTime;
就和平常一樣,Web Bean 可支援多重綁定類型:
<myapp:AsynchronousChequePaymentProcessor>
<myapp:PayByCheque/>
<myapp:Asynchronous/>
</myapp:AsynchronousChequePaymentProcessor
>
攔截器與裝飾器只是基本的 Web Bean,所以它們能像其它任何基本的 Web Bean 一樣地被宣告:
<myfwk:TransactionInterceptor>
<Interceptor/>
<myfwk:Transactional/>
</myfwk:TransactionInterceptor
>
Web Bean 允許我們於注入點定義一個 Web Bean。例如:
<myapp:System>
<ApplicationScoped/>
<myapp:admin>
<myapp:Name>
<myapp:firstname
>Gavin</myapp:firstname>
<myapp:lastname
>King</myapp:lastname>
<myapp:email
>gavin@hibernate.org</myapp:email>
</myapp:Name>
</myapp:admin>
</myapp:System
>
<Name>
這個要素能透過使用一組初始欄位值來宣告 @Dependent
這個 scope 以及 Name
class 的基本 Web Bean。
這項簡易卻強大的功能可允許 Web Bean XML 格式被使用來指定 Java 物件的整個 graph。它固然不是完整的 databinding 解決方案,不過卻也差得不遠!
若我們希望我們的 XML 文件格式能由 Java 開發人員以外的人員或是無法存取我們的程式碼的人員來進行編寫的話,我們將需要提供一個 schema。針對於 Web Bean,編寫或使用 schema 並無任何特殊情況。
<WebBeans xmlns="urn:java:javax.webbeans"
xmlns:myapp="urn:java:com.mydomain.myapp"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:java:javax.webbeans http://java.sun.com/jee/web-beans-1.0.xsd
urn:java:com.mydomain.myapp http://mydomain.com/xsd/myapp-1.2.xsd">
<myapp:System>
...
</myapp:System>
</WebBeans
>
編寫 XML schema 非常地冗長。因此,Web Bean RI 專案將會提供一個能夠由已編譯的 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)。
所有基本與企業的 Web Bean 都能透過 @Resource
、@EJB
以及 @PersistenceContext
來有效利用 Java 依賴注入(dependency injection)。我們早已見過了這方面的範例,不過當時我們並未詳細討論到:
@Transactional @Interceptor
public class TransactionInterceptor {
@Resource Transaction transaction;
@AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
}
@SessionScoped
public class Login {
@Current Credentials credentials;
@PersistenceContext EntityManager userDatabase;
...
}
所有基本與企業的 Web Bean 也都支援 Java EE @PostConstruct
和 @PreDestroy
的 callback。@PostConstruct
這個 method 會在所有注入被進行後才會被調用。
在此有項需要注意的限制:基本的 Web Bean 並不支援 @PersistenceContext(type=EXTENDED)
。
在 Java EE 6 中透過 Servlet 來使用 Web Bean 相當地簡單。只要使用 Web Bean 欄位或是 initializer method 注入來注入 Web Bean 即可。
public class Login extends HttpServlet {
@Current Credentials credentials;
@Current Login login;
@Override
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
credentials.setUsername( request.getAttribute("username") ):
credentials.setPassword( request.getAttribute("password") ):
login.login();
if ( login.isLoggedIn() ) {
response.sendRedirect("/home.jsp");
}
else {
response.sendRedirect("/loginError.jsp");
}
}
}
Web Bean 的客戶端 proxy 能為目前的請求以及 HTTP session 處理由 Servlet 至 Credentials
和 Login
的正確事例的 routing method 調用。
Web Bean 注入適用於所有 EJB,儘管它們不是由 Web Bean 管理員所控制(比方說若它們是透過直接的 JNDI 搜尋或是透過使用 @EJB
來被取得的情況下)。特別是,您可在訊息導向的 Bean 中使用 Web Bean 注入,不過這些 Bean 並不被視為是 Web Bean 因為您無法注入它們。
針對於訊息導向的 Bean,您甚至可使用 Web Bean 攔截器綁定。
@Transactional @MessageDriven
public class ProcessOrder implements MessageListener {
@Current Inventory inventory;
@PersistenceContext EntityManager em;
public void onMessage(Message message) {
...
}
}
因此,在 Web Bean 環境下,取得訊息是相當地簡單的。不過請注意,當訊息提交至一個訊息導向的 Bean 時,不會有可用的 session 或是對話 context。只有 @RequestScoped
和 @ApplicationScoped
Web Bean 可使用。
透過使用 Web Bean 來傳送訊息也相當地簡單。
基於需要處理許多不同的物件,因此透過使用 JMS 來傳送訊息可能會相當複雜。針對於 queue,我們有 Queue
、QueueConnectionFactory
、QueueConnection
、QueueSession
以及 QueueSender
。針對於 topic 我們有 Topic
、TopicConnectionFactory
、TopicConnection
、TopicSession
以及 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 注入 Queue
、QueueConnection
、QueueSession
或 QueueSender
,或是為 topic 注入 Topic
、TopicConnection
、TopicSession
或是 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 不會定義任何特殊的建置 archive。您可將 Web Bean 封裝在 JARs、EJB-JARs 或是 WARs 中,應用程式 classpath 中的任何建置位置上。不過,各個包含著 Web Bean 的 archive 在 META-INF
或是 WEB-INF
目錄中都必須包含著一個稱為 web-beans.xml
的檔案。該檔案能是空的。建置於一個沒有 web-beans.xml
檔案的 archive 中的 Web Bean 將無法使用於應用程式中。
對於 Java SE 的執行來說,Web Bean 可被建置於任何位置上,同時 EJB 亦可被建置來讓可崁入的 EJB Lite container 執行。再次強調,各個位置都必須包含著一個 web-beans.xml
檔案。
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 ��
Bean
這個 abstract class 的 instance 代表 Web Bean。應用程式中的每個 Web Bean 都會有個與 Manager
物件註冊的 Bean
instance。
public abstract class Bean<T> {
private final Manager manager;
protected Bean(Manager manager) {
this.manager=manager;
}
protected Manager getManager() {
return manager;
}
public abstract Set<Class> getTypes();
public abstract Set<Annotation> getBindingTypes();
public abstract Class<? extends Annotation> getScopeType();
public abstract Class<? extends Annotation> getDeploymentType();
public abstract String getName();
public abstract boolean isSerializable();
public abstract boolean isNullable();
public abstract T create();
public abstract void destroy(T instance);
}
您可藉由調用 Manager.addBean()
來延伸 Bean
class 並註冊 instance 以便提供較新、未經 Web Bean 規格定義(基本與企業級 Web Bean、producer method 以及 JMS 端點)的 Web Bean 上的支援。比方說,我們可使用 Bean
class 來允許另一個架構所管理的物件被注入 Web Bean 之中。
Web Bean 規格定義了 Bean
的兩個 subclass:Interceptor
和 Decorator
。
因為 Web Beans 非常地新,因此還尚未有大量線上資訊以供參閱。
當然,Web Beans 的規格為有關於 Web Beans 的最佳來源資訊。該資訊的數量約為 100 頁,只比此文件的長度多出兩倍,並且一樣地容易閱讀。不過,當然它亦包含了許多我們所跳過的細節。您可藉由 http://jcp.org/en/jsr/detail?id=299
來取得該規格資訊。
Web Beans Reference 的實做開發於 http://seamframework.org/WebBeans
。RI 開發團隊以及 Web Beans spec lead 的 blog 位於 http://in.relation.to
。本文件大體上基於發佈於該網頁上的一系列 blog 項目。
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.
No special configuration of your application, beyond adding either META-INF/beans.xml
or WEB-INF/beans.xml
is needed.
If you are using JBoss AS 5.0.1.GA then you'll need to install Web Beans as an extra. First we need to tell Web Beans where JBoss is located. Edit jboss-as/build.properties
and set the jboss.home
property. For example:
jboss.home=/Applications/jboss-5.0.1.GA
Now we can install Web Beans:
$ cd webbeans-$VERSION/jboss-as $ ant update
A new deployer, webbeans.deployer
is added to JBoss AS. This adds supports for JSR-299 deployments to JBoss AS, and allows Web Beans to query the EJB3 container and discover which EJBs are installed in your application.
Web Beans is built into all releases of JBoss AS from 5.1 onwards.
Web Beans can be used in Tomcat 6.0.
Web Beans doesn't support deploying session beans, injection using @EJB
, or @PersistenceContext
or using transactional events on Tomcat.
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:
jsr299-api.jar
webbeans-api.jar
webbeans-spi.jar
webbeans-core.jar
webbeans-logging.jar
webbeans-tomcat-int.jar
javassist.jar
dom4j.jar
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:
Simple Web Beans (POJOs)
Typesafe Dependency Injection
Application and Dependent Contexts
Binding Types
Stereotypes
Decorators
(TODO: Interceptors ?)
Typesafe Event Model
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 );
}
}
The command line parameters do not become available for injection until the @Deployed Manager
event is fired. If you need access to the parameters during initialization you can do so via the public static String getParameters()
method in StartMain
.
These modules are usable on any JSR-299 implementation, not just Web Beans!
目前,Web Bean RI 只能在 JBoss AS 5 中執行;要將 RI 整合入其它 EE 環境中(比方說另一個像是 Glassfish 的應用程式伺服器)、整合入一個 servlet 容器(例如 Tomcat)中,或是和一個崁入式的 EJB3.1 實做整合都是相當容易的。在此附錄中,我們將簡略地討論所需的步驟。
您可在一個 SE 環境下執行 Web Bean,不過您必須進行較多步驟,包括新增您自己的 context 和生命週期。Web Bean RI 目前並不會顯示生命週期的延伸點,因此您必須針對於 Web Bean RI 的 class 來直接地進行程式撰寫。
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。
public interface WebBeanDiscovery {
/**
* Gets list of all classes in classpath archives with web-beans.xml files
*
* @return An iterable over the classes
*/
public Iterable<Class<?>
> discoverWebBeanClasses();
/**
* Gets a list of all web-beans.xml files in the app classpath
*
* @return An iterable over the web-beans.xml files
*/
public Iterable<URL
> discoverWebBeansXml();
}
Web Bean 類別和 web-bean.xml
檔案的搜尋相當明顯(演算法描述於 JSR-299 規格的章節 11.1 中,在此不重複)。
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.
Just as resolution of @EJB
is delegated to the container, so is resolution of @PersistenceContext
.
OPEN ISSUE: Web Beans also requires the container to provide a list of entities in the deployment, so that they aren't discovered as simple beans.
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 Beans expects the Application Server or other container to provide the storage for each application's context. The org.jboss.webbeans.context.api.BeanStore
should be implemented to provide an application scoped storage. You may find org.jboss.webbeans.context.api.helpers.ConcurrentHashMapBeanStore
useful.
The org.jboss.webbeans.bootstrap.api.Bootstrap
interface defines the bootstrap for Web Beans. To boot Web Beans, you must obtain an instance of org.jboss.webbeans.bootstrap.WebBeansBootstrap
(which implements Boostrap
), tell it about the SPIs in use, and then request the container start.
The bootstrap is split into phases, bootstrap initialization and boot and shutdown. Initialization will create a manager, and add the standard (specification defined) contexts. Bootstrap will discover EJBs, classes and XML; add beans defined using annotations; add beans defined using XML; and validate all beans.
The bootstrap supports multiple environments. Different environments require different services to be present (for example servlet doesn't require transaction, EJB or JPA services). By default an EE environment is assumed, but you can adjust the environment by calling bootstrap.setEnvironment()
.
To initialize the bootstrap you call Bootstrap.initialize()
. Before calling initialize()
, you must register any services required by your environment. You can do this by calling bootstrap.getServices().add(JpaServices.class, new MyJpaServices())
. You must also provide the application context bean store.
Having called initialize()
, the Manager
can be obtained by calling Bootstrap.getManager()
.
To boot the container you call Bootstrap.boot()
.
To shutdown the container you call Bootstrap.shutdown()
. This allows the container to perform any cleanup operations needed.
The Web Beans RI implements JNDI binding and lookup according to standards, however you may want to alter the binding and lookup (for example in an environment where JNDI isn't available). To do this, implement org.jboss.webbeans.resources.spi.NamingContext
:
public interface NamingContext extends Serializable {
/**
* Typed JNDI lookup
*
* @param <T
> The type
* @param name The JNDI name
* @param expectedType The expected type
* @return The object
*/
public <T
> T lookup(String name, Class<? extends T
> expectedType);
/**
* Binds an item to JNDI
*
* @param name The key to bind under
* @param value The item to bind
*/
public void bind(String name, Object value);
}
Web Beans RI 需要在各個時段由 classpath 載入類別和資源。就預設值,它們會被由和使用來載入 RI 相同的 classloader 所載入,不過這對於某些環境來說可能不是不正確的。若是如此,您可實做 org.jboss.webbeans.spi.ResourceLoader
:
public interface ResourceLoader {
/**
* Creates a class from a given FQCN
*
* @param name The name of the clsas
* @return The class
*/
public Class<?> classForName(String name);
/**
* Gets a resource as a URL by name
*
* @param name The name of the resource
* @return An URL to the resource
*/
public URL getResource(String name);
/**
* Gets resources as URLs by name
*
* @param name The name of the resource
* @return An iterable reference to the URLS
*/
public Iterable<URL
> getResources(String name);
}
Java EE / Servlet does not provide any hooks which can be used to provide injection into Servlets, so Web Beans provides an API to allow the container to request JSR-299 injection for a Servlet.
To be compliant with JSR-299, the container should request servlet injection for each newly instantiated servlet after the constructor returns and before the servlet is placed into service.
To perform injection on a servlet call WebBeansManager.injectServlet()
. The manager can be obtained from Bootstrap.getManager()
.
Web Bean RI 對於 container 有些需求以便達到 API 實做之外的正確的功能。
若您要將 Web Bean RI 整合入某個支援多重應用程式建置的環境中,您就必須為各個 Web Bean 應用程式透過自動的方式,或是透過用戶配置來啟用 classloader 隔離。
若您要將 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.
若您要將 Web Bean 整合入一個 EJB 環境中,您就必須針對於各個使用 enterprise bean 的 Web Bean 應用程式來為應用程式中的所有 EJB 透過自動的方式,或是透過用戶配置來將 org.jboss.webbeans.ejb.SessionBeanInterceptor
註冊為一個 EJB 攔截器。
You must register the SessionBeanInterceptor
as the inner most interceptor in the stack for all EJBs.
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.