JAVA CDI 學習- @Produces及@Disposes

johnychen發表於2021-09-09

上一節學習了注入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 html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ""> 
 2  
 6 
 7 
 8     Connection Test
 9  
10  
11     #{Conn.connection.doSomething()}
12  
13 

conn.xhtml

eclipse裡部署到jboss下,瀏覽

圖片描述

跟預想的完全一樣! 條條道路通羅馬,解決問題的途徑往往不止一條,或許有些人不喜歡在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 }

@MyConnection

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 html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
 2 
 7 
 8 
 9     CDI Sample Test
10 
11 #{newProduct.toString()}
12 
13 

注意:這裡我們並沒有任何的Controller,Resouces類本身也沒有使用@Named之類的註解,只是在方法getNewProduct上使用了 @Produces、 @Named,頁面上就可以直接使用資源池中的物件了.
圖片描述

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4301/viewspace-2800927/,如需轉載,請註明出處,否則將追究法律責任。