上一節學習了注入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 }
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 }
注:留意一下@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 }
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>
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 }
注:關注一下@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 }
7、修改ConnectionController

1 @Inject 2 @MyConnection 3 private Connection connection;
在原來的@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 }
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 }
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>
注意:這里我們并沒有任何的Controller,Resouces類本身也沒有使用@Named之類的注解,只是在方法getNewProduct上使用了 @Produces、 @Named,頁面上就可以直接使用資源池中的對象了.
文章列表