上一节学习了注入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!