文章出處

上一節學習了注入Bean的生命周期,今天再來看看另一個話題: Bean的生產(@Produces)及銷毀(@Disposes),這有點象設計模式中的工廠模式。在正式學習這個之前,先來看一個場景:

基于web的db應用開發中,經常要在一個頁面上連接db,然后干點啥,最后關閉連接。下面用之前二節前到的CDI技能來演練一下:

1、先建一個Connection的接口

 1 package conn;
 2 
 3 public interface Connection {
 4     
 5     void connect();
 6     
 7     void closeConnection();
 8     
 9     String doSomething();
10 
11 }
Connection Interface

2、再來一個實現

 1 package conn;
 2 
 3 import javax.annotation.PostConstruct;
 4 import javax.annotation.PreDestroy;
 5 
 6 public class ConnectionImpl implements Connection {
 7     /**
 8      * Servlet構造函數調用后,會自動執行帶有@PostConstruct的方法
 9      */
10     @PostConstruct
11     private void initConn(){
12         System.out.println("initConn is called...");
13         connect();
14     }
15     
16     /**
17      * Servlet卸載前,會自動執行帶有@PreDestroy的方法
18      */
19     @PreDestroy
20     private void destroyConn(){        
21         System.out.println("destroyConn is called...");
22         closeConnection();
23     }
24 
25     @Override
26     public void connect() {
27         System.out.println("Connecting...");
28 
29     }
30 
31     @Override
32     public void closeConnection() {
33         System.out.println("Closing connection...");
34 
35     }
36 
37     @Override
38     public String doSomething() {
39         String msg = "do something...";
40         System.out.println(msg);
41         return msg;
42 
43     }
44 
45 }
ConnectionImpl

注:留意一下@PostConstruct@PreDestroy這二個特殊的注解。我們知道所有jsf/jsp頁面,最終運行時,實際上執行的是背后對應的Servlet,整個Servlet的生命周期在加入了這二個注解后,其執行順序如下:

所以,當ConnectionImpl最終被注入到Controller中時,會自動先調用initConn方法建立連接,在整個Request結束前,自動調用destroyConn關閉連接。

3、創建Controller類

 1 package controller;
 2 
 3 import javax.inject.Inject;
 4 import javax.inject.Named;
 5 
 6 import conn.Connection;
 7 import conn.TestConnection;
 8 
 9 @Named("Conn")
10 public class ConnectionController {
11 
12     @Inject    
13     private Connection connection;
14 
15     public Connection getConnection() {
16         return connection;
17     }
18 
19 }
ConnectionController

4、新建conn.xhtml用于顯示

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
 2 <html xmlns="http://www.w3.org/1999/xhtml"
 3       xmlns:h="http://java.sun.com/jsf/html"
 4       xmlns:f="http://java.sun.com/jsf/core"
 5       xmlns:ui="http://java.sun.com/jsf/facelets"> 
 6 
 7 <h:head>
 8     <title>Connection Test</title>
 9 </h:head> 
10 <body> 
11     #{Conn.connection.doSomething()}
12 </body> 
13 </html>
conn.xhtml

eclipse里部署到jboss下,瀏覽http://localhost:8080/cdi-scope-sample/conn.jsf,觀察console的輸出:

跟預想的完全一樣! 條條道路通羅馬,解決問題的途徑往往不止一條,或許有些人不喜歡在ConnectionImpl里參雜太多其它的職責(比如:自動打開連接、自動關閉連接),可以考慮用CDI的produces及disposes.

5、創建ConnectionFactory

回想一下設計模式中的工廠模式,對象的創建(銷毀)通常放在一個單獨的工廠類來處理(單一職責原則),我們也來建一個工廠:

 1 package conn;
 2 
 3 import javax.enterprise.context.RequestScoped;
 4 import javax.enterprise.inject.*;
 5 
 6 public class ConnectionFactory {
 7 
 8     @Produces
 9     @RequestScoped
10     @MyConnection
11     public Connection getConn() {
12         System.out.println("ConnectionFactory.getConn is called...");
13         Connection conn = new ConnectionImpl();
14         conn.connect();
15         return conn;
16 
17     }
18 
19     
20     public void closeConn(@Disposes @MyConnection Connection conn) {
21         System.out.println("ConnectionFactory.closeConn is called...");
22         conn.closeConnection();
23     }
24 
25 }
ConnectionFactory

注:關注一下@Produces這個注解,這表示應用該注解的方法,是一個Bean的生成器(或理解成工廠的某些產品生產流水線),在需要Inject的時候,會自動通過該方法產生對象實例;而@Disposes注解,正好與@Produces對應,用于人道毀滅@Produces產生的對象,此消彼漲,這樣世界才會遵從守恒定律!

@RequestScoped不用多解釋了,表示工廠里產生的Bean其生命周期僅存在于單次Http請求中。

but,just wait a minute,@MyConnection ? what is this ? why we need it ?

讓我們將思維方式,從人類大腦切換成計算機電腦的模式,ConnectionImpl繼承自Connection,對于系統來講,這二個是都是兼容Connection類型的,在產生對象時,這還好說,因為目前Connection只有一個實現類ConnectionImpl,計算機可以足夠智能的推斷出應該用ConnectionImpl來創建對象實例,但是對象銷毀的時候呢?這時傳入的參數類型是Connection接口類型,這時它并不知道該對象具體是何種實現?

所以,我們自己創建了一個@MyConnection注解,在@Produces與@Disposes上都應用該注解,這樣對象銷毀時,就能根據該注解精確的知道是要銷毀何種類型的哪個對象.

6、@MyConnection代碼如下:

 1 package conn;
 2 
 3 import java.lang.annotation.Retention;
 4 import java.lang.annotation.Target;
 5 
 6 import static java.lang.annotation.ElementType.FIELD;
 7 import static java.lang.annotation.ElementType.TYPE;
 8 import static java.lang.annotation.ElementType.METHOD;
 9 import static java.lang.annotation.ElementType.PARAMETER;
10 import static java.lang.annotation.RetentionPolicy.RUNTIME;
11 
12 import javax.inject.Qualifier;
13 
14 @Qualifier
15 @Retention(RUNTIME)
16 @Target({ FIELD, TYPE, METHOD, PARAMETER })
17 public @interface MyConnection {
18 
19 }
@MyConnection

7、修改ConnectionController

1     @Inject    
2     @MyConnection
3     private Connection connection;
View Code

在原來的@Inject下,增加@MyConnection,否則Controller感受不到Factory的存在(系統將只是簡單的注入一個ConnectionImpl實例而已,不會自動創建連接/關閉連接),感興趣的同學可以先不加這個注釋,然后運行試試,體會一下

編譯、部署、運行,觀察Console的輸出:

Perfect!

8、@Produces當成資源池使用

@Produces還有一個用途,可以把一些其它地方需要用到的注入對象,統一放在一起先“生產”好,形成一個"資源池",在需要使用的地方,直接從池里拿來用即可.

下面演示了這種用法:

8.1 先定義一個Product POJO類:

 1 package model;
 2 
 3 public class Product {
 4 
 5     private String productName;
 6 
 7     private String productNo;
 8 
 9     public String getProductName() {
10         return productName;
11     }
12 
13     public void setProductName(String productName) {
14         this.productName = productName;
15     }
16 
17     public String getProductNo() {
18         return productNo;
19     }
20 
21     public void setProductNo(String productNo) {
22         this.productNo = productNo;
23     }
24 
25     @Override
26     public String toString() {
27         return productNo + "," + productName;
28     }
29 
30 }
Product

8.2 再創建一個Resocues.java,用來統一管理需要用到的資源

 1 package resource;
 2 
 3 import javax.enterprise.inject.Produces;
 4 import javax.inject.Named;
 5 
 6 import model.Product;
 7 
 8 public class Resouces {
 9 
10     @Produces
11     @Named
12     public Product getNewProduct() {
13         Product product = new Product();
14         product.setProductName("new product");
15         product.setProductNo("000000");
16         return product;
17     }
18 
19 }
Resouces

8.3 然后在頁面上就可以直接使用了

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2 <html xmlns="http://www.w3.org/1999/xhtml"
 3     xmlns:h="http://java.sun.com/jsf/html"
 4     xmlns:f="http://java.sun.com/jsf/core"
 5     xmlns:ui="http://java.sun.com/jsf/facelets"
 6     xmlns:c="http://java.sun.com/jsp/jstl/core">
 7 
 8 <h:head>
 9     <title>CDI Sample Test</title>
10 </h:head>
11 <body>#{newProduct.toString()}
12 </body>
13 </html>
index.xhtml

注意:這里我們并沒有任何的Controller,Resouces類本身也沒有使用@Named之類的注解,只是在方法getNewProduct上使用了 @Produces、 @Named,頁面上就可以直接使用資源池中的對象了.


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()