CDI Features

一、概念

   CDI,即JSR299,正式发布之前,大家习惯称呼为“web beans”。在spring、JBOSS、apache等多个JCP的支持下,由JBOSS的Gavin King 团队开发而成,你在源码中随处可以看到Gavin King的代码。CDI的思想来自spring,但是它的开发模式来自鼎鼎有名的Jboss seam,如果你熟悉jboss seam,那么CDI对你来说只是“他乡遇故知”。Spring IOC几乎是完美的,最起码使用起来是便捷的,但是javaEE是一个追求“官方规范”的地方,CDI并不是为了规范而产生,它事实上是为了解决EJB、javabean被web层组件引用困难的问题。当然它的大目标是为了提供java web开发中一种通用、快捷的途径。

   Contexts and Dependency Injection,顾名思义就是“上下文依赖注入”,“依赖注入”基本概念和spring一样,在此不再赘言。“上下文”就是我们熟知的context,对于CDI来说,具有“上下文”的特性,就以为它具有管理bean生命周期的能力,当然因为CDI是为web开发而成,这种“上下文”概念就以为每个web bean是有状态的,它的生命周期将有客户端(http client)的状态而决定,和无状态组件模型(POJO,stateless EJB)或者一个单例模型组件(例如Servlet或者单例Bean)不同,一个Web Bean的不同客户端看到的WebBean的状态是不同的。客户端所见的状态取决于这个客户端拥有的是哪一个Web Bean实例的引用。接下来的内容中你会慢慢知道它的含义。

   二、@Inject

   注入(Inject) 依赖注入是 CDI 的核心功能,他是 web bean 得以使用或者融合其他技术的最核心特性。依赖注入,和 spring IOC 基本原理一致,即为实例化 bean 或者对产生 bean 调用行为时,根据 bean 的需要,依次查找、实例化、并传递被依赖 bean 或者其他数据的能力,当然依赖注入的双方(注入与被注入者)必须能够被容器所管理,即其生命周期可控。在 CDI 中,管理 bean 的类为 BeanManager,此类提供了几个可以用来获取 bean 信息的方法。
   @Inject 可以注释在构造器,按照 CDI 规范,web bean 构造器包含参数,但是如果想使用有参构造器初始化 bean,那么需要在构造器上使用@Inject 注释。
   @Inject 可以注释在普通方法上,此方法的目的就是在“初始化“功能,即对于实例的创建做一些额外的操作。
   一个 web bean 中,属性、构造器、方法都可以使用@Inject 注入,他们注入的顺序大致如下:
   @Inject 注释的构造器,当然构造器的参数可以使用注入。
   @Inject 注释的成员变量
   @Inject 注释的初始化方法(普通方法),至此实例化过程结束。

   三、demo实现

   主要是通过一个小demo,讲解一下CDI基本的Inject注入:

   1、在eclipse中先创建一个常规的maven Dynamic Web项目下面是完整的项目截图:

    CDI Features

   2、配置pom.xml文件

   3、创建model包,新建 Product类

   4、service包下,建一个ProductService接口

   5、service包下,再来几个实现

   CDI Features

   BaseProductServiceImpl是实现类的基类,注意这里私有成员上打了一个注解@Inject,表示运行时将动态注入(实例化)一个Product。在新建几个具体的实现类,BookProductServiceImpl,TeletephoneProductServiceImpl。

   6、controller包下,添加IndexController类,为了能跟JSF的前台页面交互,这里需要添加一个Controller

   7、以上步骤是模拟了分层架构:
   model - 代表了业务模型层(本例中,为了简单起见,没有细分 业务模型、实体模型、以及web中的ViewModel)
   service - 代表了服务层(为了简单起见,我们把接口+实现都放在一起了,实际中,可能会把这二个分开来)
   controller - 这是web层MVC中的控制器层

   8、webapp下,新建一个index.xhtml文件,内容如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets">
 
<h:head>
	<title>CDI Sample</title>
</h:head>
<body>
	Book:
	<br /> #{Index.bookProductService.newProduct.toString()}
	<br />
	<br /> Telephone:
	<br /> #{Index.telephoneProductService.newProduct.toString()}
	<br />
	<br /> Inject Method Test:
	<br /> #{Index.product.toString()}
	<br />
	<br /> Inject Constructor Test:
	<br /> #{Index.clothProductService.newProduct.toString()}
</body>
</html>

   页面里几乎没啥代码,就是调用IndexController实例中的getBookProductService、getTelephoneProductService方法,进而得到相应的"服务实现类实例",最终输出产品信息。

   9、Inject用在什么地方了?

   a) 页面显示时,IndexController里,bookProductService和telephoneProductService这二个私有成员上,都加了@Inject注解,所以运行时,这二个成员都能被实例化,但是问题来了,它们都是ProductService的接口类型,而这个接口有二个具体的实现(BookProductServiceImpl和TeletephoneProductServiceImpl),最终运行时,应该实例化哪一个呢?

    关键在于另一个注解@Book和@Telephone,观察一下:BookProductServiceImpl类上我们也加了@Book,而TeletephoneProductServiceImpl上加了@Telephone,这样正好可以跟IndexControll中这二个私成成员的注释“匹配”上,所以最终系统知道私有成员bookProductService应该被实例化成BookProductServiceImpl,telephoneProductService被实例化成TeletephoneProductServiceImpl

    b) BaseProductServiceImpl中,在私成成员product上加了@Inject,这样运行时,能自动实例化Product对象。

   jboss中部署后,浏览http://localhost:8080/cdi-web-sample/faces/index.xhtml 或http://localhost:8080/cdi-web-sample/index.jsf

   

   10、拓展

   刚才我们看到的都是在Field(成员)上注入,除了这种方式,也可以在Method或Constructor上注入:

   a.Method注入:

private Product product ;
	
	/**
	 * 演示在方法上使用@Inject注入
	 * @param p
	 */
	@Inject
	public void setProduct(Product p){
		product = p;		
	}
	
	public Product getProduct(){
		return product;		
	}

   b.构造器注入,我们再新建一个ClothProductServiceImpl用于生产服装

 

@Cloth
public class ClothProductServiceImpl implements ProductService {
 
	private Product product;
	
	/**
	 * 构造器注入
	 * @param p
	 */
	
	@Inject
	public ClothProductServiceImpl(Product p ){
		p.setProductName("A New Dress");
		p.setProductNo("SPRIT-001");
		product = p;
		
	}
	
	public Product getNewProduct() {		
		return product;
	}
 
}

   运行时,系统会自动给构造器ClothProductServiceImpl传递一个实例化的Product对象作为参数,以实现Product实例的注入。