天天看點

【轉】面向Java EE 6平台的上下文和依賴性注入

【51CTO精選譯文】Java EE 6平台的釋出帶來了幾個新的技術亮點。在前幾周,我們介紹了Java EE平台的主要目标以及Java EE 6的RESTful Web Services Java API (JAX-RS)特性,本文将介紹面向Java EE平台的上下文和依賴性注入(CDI)。

JSR 299是一種為Java EE元件提供強大服務的技術,這些服務允許Java EE元件,包括EJB會話Bean和JavaServer Faces(JSF)托管的Bean,綁定到生命周期上下文,注入,并以松耦合的方式互動。最重要的也許是,CDI統一和簡化了EJB和JSF程式設計模型,允許企業Bean替換JSF應用程式中JSF托管的Bean。

本質上,CDI是Java EE平台的Web層和企業層之間的一座橋梁,企業層通過如EJB和JPA等技術,已經對事務性資源提供了強有力的支援。例如,使用EJB和JPA(51CTO編輯推薦:EJB應用開發專欄),你可以輕松建構與資料庫互動的應用程式,在資料上送出或復原事務,以及持久化資料。相比之下,Web層重點是展示。Web技術如JSF和JSP提供使用者界面,顯示它的内容,沒有內建處理事務資源的工具。51CTO編輯推薦您閱讀《Java EE 6平台指南》專題了解更多。

通過它的服務,CDI使Web層也支援事務,這樣在Web應用程式中通路事務資源就更容易了。例如,CDI使得建構一個用JPA提供的持久化通路資料庫的Java EE Web應用程式就更容易了。

讓我們再看看使用CDI服務的Web應用程式的關鍵部分,處理使用者登入和登出的應用程式同時包括JSF和EJB元件。下面是一個顯示登入提示JSF頁面中的輸入窗體代碼:

1. <f:view>

2. <h:form>

3. <h:panelGrid columns="2" rendered="#{!login.loggedIn}">

4. <h:outputLabel for="username">Username:h:outputLabel>

5. <h:inputText id="username" value="#{credentials.username}"/>

6. <h:outputLabel for="password">Password:h:outputLabel>

7. <h:inputText id="password" value="#{credentials.password}"/>

8. h:panelGrid>

9. <h:commandButton value="Login" action="#{login.login}" rendered="#{!login.loggedIn}"/>

10. <h:commandButton value="Logout" action="#{login.logout}" rendered="#{login.loggedIn}"/>

11. h:form>

12. f:view>

13.

你可以從代碼中看到,登入提示顯示區域包括輸入使用者名和密碼,同時還顯示了一個登入按鈕和退出按鈕。注意統一表達式語言(EL)表達式,如# {credentials.username}和#{login.login},這些表達式引用了名叫credentials 和 login的Beans。

請注意CDI是建構在Java EE 6引入的新概念托管Beans之上的,其目的是統一Java EE 6中所有類型的Beans。一個托管Bean就是一個Java類,被視為由Java EE容器托管的元件,另外,你可以為其指定一個與EJB元件同名的命名空間,一個托管Bean也可以依賴少量的容器提供的服務,主要與生命周期管理資源注入有關,其它Java EE技術,如JSF,EJB和基于托管Bean建構的CDI,一個JSF托管Bean添加到生命周期範圍,一個EJB會話Bean添加如支援事務的服務,CDI添加入依賴性注入的服務,在CDI中,一個托管Bean或一個簡單的Bean是可以被其它元件,關聯的上下文或通過EL表達式注入的Java EE元件。

使用javax.annotation.ManagedBean注解或CDI注解,如範圍注解或限定注解,注解它的類來聲明一個托管Bean,後面将會介紹範圍注解和限定注解。基于注解的程式設計模型使得一個Bean開始是一個POJO,然後又轉換成另一種Java EE元件,如EJB元件成為可能,也許要使用更進階的功能,如事務和安全注解,或由EJB容器提供的執行個體,例如,你可以向對象添加一個@Stateful 注解将一個POJO轉換成一個狀态會話,使用CDI的用戶端通路Bean不受影響,因為POJO已經轉換成EJB了。

這裡列舉的應用程式中,一個名叫Credentials的Bean有一個綁定到JSF請求的生命周期,Credentials Bean是象下面這樣實作的一個JavaBean:

1. @Model

2. public class Credentials {

3.

4. private String username;

5. private String password;

6.

7. public String getUsername() { return username; }

8. public void setUsername(String username) { this.username = username; }

9.

10. public String getPassword() { return password; }

11. public void setPassword(String password) { this.password = password; }

12. }

13.

為了請求一個CDI服務,可以使用CDI注解注解一個Java EE元件,@Model注解是一個CDI注解,它将Credentials Bean作為模型-視圖-控制器(MVC)架構中的模型對象,内置于CDI中的注解是一種固定模式的注解,固定模式注解将類标記為滿足應用程式内的特定角色。

應用程式還包括一個Login Bean,它的生命周末是和HTTP會話綁定到一起的,Login Bean是作為一個EJB狀态會話Bean實作的,代碼如下:

1. @Stateful

2. @SessionScoped

3. @Model

4. public class Login {

5.

6. @Inject Credentials credentials;

7. @Inject EntityManager userDatabase;

8.

9. private User user;

10.

11. @TransactionAttribute(REQUIRES_NEW)

12. @RolesAllowed("guest")

13. public void login() {

14. ...

15. }

16.

17. public void logout() {

18. user = null;

19. }

20.

21. public boolean isLoggedIn() {

22. return user!=null;

23. }

24.

25. @RolesAllowed("user")

26. @Produces @LoggedIn User getCurrentUser() {

27. ...

28. }

29. }

30.

@Stateful注解是一個EJB注解,它指定這個Bean是一個EJB狀态會話Bean,@TransactionAttribute 和@RolesAllowed也是EJB注解,它們聲明EJB事務劃分和注解方法的安全屬性。

@SessionScoped注解是一個CDI注解,它給Bean指定一個範圍,所有的Bean都有一個範圍确定其執行個體的生命周期和這個執行個體對其它 Bean的執行個體是否可見,這是一個很重要的特性,因為EJB元件沒有定義良好的範圍,尤其是EJB元件不能感覺請求,會話和應用程式Web層元件,如 JSF托管Bean的上下文,也不能通路與這些上下文關聯的狀态。此外,狀态EJB元件的生命周期不能作用到Web層上下文。

相比之下,CDI中的作用域對象有一個定義良好的由Java EE容器托管的生命周期上下文,作用域對象可能是按需自動建立的,當上下文建立完畢後又自動銷毀的,值得注意的是,作用域狀态在相同上下文中執行的用戶端之間是自動共享的,這意味着用戶端,如其它在相同上下文中執行的Beans,會被當作相同的對象執行個體看待,但在不同上下文中的用戶端看到的是不同的執行個體。 @SessionScoped注解指定Login Bean的作用域類型是會話作用域。對象通常不會與作用域關聯,一般依賴于它們的所有者,這些依賴對象的生命周期是和它們的所有者聯系在一起的,一個依賴對象當它的所有者被銷毀後它也自動銷毀。

Beans通常通過依賴性注入引用其它Beans,依賴性注入機制是一個完全的類型安全,CDI使用JSR 330-Java依賴性注入中指定的注解進行依賴性注入,@Inject就是其中一個注解,它指出Java類或接口上哪個依賴點可以被注入,容器然後提供需要的資源,在這個例子中,Login Bean指定了兩個注入點,第一個使用@Inject注解在Credentials Bean上注入一個依賴,容器将會把Credentials Bean注入到這個上下文中建立的所有Login執行個體上,第二個@Inject注解在JPA EntityManager上注入一個依賴,容器将會注入EntityManager管理持久化上下文。

@Produces注解将getCurrentUser()方法認為是一個生産者方法,每當系統中的另一個Bean需要指定類型的注入對象時就會調用生産者方法,在這個例子中,注入對象是目前登入的使用者,它是通過限定注解@LoggedIn注入的,為了使用限定注解,你首先需要将它的類型定義為一個限定器,再使用@Qualifier注解,如:

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

2. @Retention(RUNTIME)

3. @Documented

4. @Qualifier

5. public @interface LoggedIn {...}

6.

* Java EE 6新特性嘗鮮:EJB 3.1重要變化..

* Java EE 6,貌似民主下的虛僞公平

* Sun的技術爆發 Java EE 6等三重量級産品..

* Java EE 6總覽:平台的主要目标

* Java EE 6引入的三大新技術之JAX-RS

讓我們再回到前面讨論的登入提示,當使用者響應提示并點選了送出按鈕後,CDI技術開始付諸行動,Java EE容器(51CTO編輯推薦:Java EE容器調查:Tomcat大受歡迎 WebLogic成時間殺手)自動執行個體化Credentials Bean和Login bean的一個上下文執行個體,一個綁定上下文的Bean的執行個體叫做上下文執行個體,JSF指定輸入給Credentials Bean上下文執行個體的使用者名和密碼,接下來JSF調用Login上下文執行個體中的login()方法。這個執行個體對于相同HTTP會話中的其它請求繼續存在并可繼續使用,并為其它請求它的Bean提供表示目前使用者的User對象。

這個例子隻說明了這個技術的一部分功能,其它功能如可以讓Bean産生或消費事件,定義截取者跨所有Bean類型綁定額外的功能,或者定義裝飾者在指定Bean類型上應用額外的功能。