前面几节学习到的CDI内容,基本上都是hard-code,以硬编码的方式在代码里指定注入类型,这并非依赖注入的本意,依赖注入的优势之一在于“解耦”,这一节我们将学习如何利用配置来动态注入的类型及属性初始化。

一、@Alternative/@Default/@Any

当一个服务接口(也称契约)有多个实现时,可以在代码里指定一个缺省的实现类型(即:标注成@Default或@Any),其它实现类标注成@Alternative,以后如果需要动态切换实现类,只要在webapp/WEB-INF/beans.xml中配置即可。

1.1 新建二个示例接口

 package contract;

 public interface Connection {

     String connect();

 }

Connection

该接口模拟db连接,里面有一个connect方法,用来连接db.

 package contract;

 public interface DriveService {

     String drive();

 }

DriveService

该接口模拟游戏应用中,有些人物具有驾驶技能。

1.2 提供接口实现

假设Connection有二个实现,一个用来连接到Oracle Database,另一个用来连接Microsoft Sql Server

 package contract.impl;

 import javax.enterprise.inject.Default;

 import contract.Connection;

 @Default
 public class OracleConnection implements Connection {

     @Override
     public String connect() {

         return "Oracle Database is connecting...";
     }

 }

OracleConnection

 package contract.impl;

 import javax.enterprise.inject.Alternative;

 import contract.Connection;

 @Alternative
 public class SqlServerConnection implements Connection {

     @Override
     public String connect() {

         return "Microsoft SqlServer is connecting...";
     }

 }

SqlServerConnection

注:OracleConnection上应用了注解@Default,表示这是接口Connection的默认实现类(@Default实质上是系统的默认注解,其实也可以省略,系统会自动默认为@Default);SqlServerConnection上应用了注解@Alternative,表示它是候选项,俗称:备胎:),所有非@Default的实现类,都必须标识@Alternative,否则注入时,会提示“不明确的类型”

再来看DriveService的实现,我们提供三种实现:驾驶汽车、摩托车、拖拉机

 package contract.impl;

 import contract.DriveService;

 public class CarDriveImpl implements DriveService {

     @Override
     public String drive() {
         String msg = "Drive a car...";
         System.out.println(msg);
         return msg;
     }

 }

CarDriveImpl

 package contract.impl;

 import javax.enterprise.inject.Alternative;

 import contract.DriveService;

 @Alternative
 public class MotorcycleDriveImpl implements DriveService {

     @Override
     public String drive() {
         String msg = "Drive a motocycle...";
         System.out.println(msg);
         return msg;
     }

 }

MotorcycleDriveImpl

 package contract.impl;

 import javax.enterprise.inject.Alternative;

 import contract.DriveService;

 @Alternative
 public class TractorDriveImpl implements DriveService {

     @Override
     public String drive() {
         String msg = "Drive a tractor...";
         System.out.println(msg);
         return msg;
     }

 }

TractorDriveImpl

注:MotocycleDriveImpl、TractorDriveImpl这二个类使用了@Alternative,即它们俩是候选,剩下的CarDriveImpl上未使用任何注解,即默认的@Default

1.3 编写Controller类

 package controller;

 import javax.inject.Inject;
 import javax.inject.Named;

 import contract.Connection;

 @Named("Conn")
 public class ConnectionController {

     @Inject
     private Connection conn;

     public Connection getConn() {
         return conn;
     }

 }

ConnectionController

 package controller;

 import javax.enterprise.inject.*;
 import javax.inject.Inject;
 import javax.inject.Named;

 import contract.DriveService;

 @Named("Drive")
 public class DriveController {

     @Inject
     private DriveService driveService;

     public DriveService getDriveService() {
         return driveService;
     }

     @Inject
     @Any
     private Instance<DriveService> anySerInstance;

     public DriveService getAnySerInstance() {
         return anySerInstance.get();
     }

 }

DriveController

注:DriveController中anySerInstance成员上使用了@Any,从本例最终使用的效果上看,它跟@Default一样,只不过细节要留意一下,需要使用Instance<T>接口,这点跟@Default有点不同。

1.4 UI层

 <!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 - Alternative/Default/Any</title>
 </h:head>
 <body>
     #{Drive.driveService.drive()}
     <br />
     <br />#{Drive.anySerInstance.drive()}
     <br />
     <br /> #{Conn.conn.connect()}
 </body>
 </html>

Index.xhtml

运行结果:

修改beans.xml的内容如下:

 <?xml version="1.0" encoding="UTF-8"?>

 <beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="         http://java.sun.com/xml/ns/javaee          http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
     <alternatives>
         <class>contract.impl.SqlServerConnection</class>
         <class>contract.impl.TractorDriveImpl</class>
     </alternatives>
 </beans>

beans.xml

重新在Jboss里部署、运行,结果如下:

在不修改java源代码的前提下,仅通过配置文件beans.xml的修改,就动态切换了接口的实现类。

二、Extension

不仅注入的类型可以由配置文件来动态切换,也可以由配置文件来直接初始化注入对象的属性值(虽然我个人认为这种场景在实际开发中其实并不多见)

2.1 先来定义几个类:

BaseDto.java

 package dto;

 import java.io.Serializable;

 public class BaseDto implements Serializable {

     private static final long serialVersionUID = 804047416541420712L;

     public BaseDto() {
         System.out.println("BaseDto's constructor is called...");

     }

 }

BaseDto

 package dto;

 @DtoType(ProductType.Product)
 public class Product extends BaseDto {

     public Product() {
         System.out.println("Product's constructor is called...");

     }

     private static final long serialVersionUID = 7364741422914624828L;
     private String productNo;
     private String productName;

     public String getProductName() {
         return productName;
     }

     public void setProductName(String productName) {
         this.productName = productName;
     }

     public String getProductNo() {
         return productNo;
     }

     public void setProductNo(String productNo) {
         this.productNo = productNo;
     }

     @Override
     public String toString() {
         return "productNo:" + productNo + " , productName: " + productName
                 + " , serialVersionUID:" + serialVersionUID;
     }
 }

Product

 package dto;

 //@DtoType(ProductType.Computer)
 public class Computer extends Product {

     public Computer() {
         System.out.println("Computer's constructor is called...");
     }

     private static final long serialVersionUID = -5323881568748028893L;

     private String cpuType;

     private int hardDiskCapacity;

     public String getCpuType() {
         return cpuType;
     }

     public void setCpuType(String cpuType) {
         this.cpuType = cpuType;
     }

     public int getHardDiskCapacity() {
         return hardDiskCapacity;
     }

     public void setHardDiskCapacity(int hardDiskCapacity) {
         this.hardDiskCapacity = hardDiskCapacity;
     }

     @Override
     public String toString() {
         return "productNo:" + getProductNo() + " , productName: "
                 + getProductName() + " , cpuType:" + getCpuType()
                 + " , hardDiskCapacity: " + getHardDiskCapacity()
                 + " , serialVersionUID:" + serialVersionUID;
     }
 }

Computer

 package dto;

 //@DtoType(ProductType.Cloth)
 public class Cloth extends Product {

     private static final long serialVersionUID = -8799705022666106476L;
     private String brand;

     public String getBrand() {
         return brand;
     }

     public void setBrand(String brand) {
         this.brand = brand;
     }

     @Override
     public String toString() {
         return "productNo:" + getProductNo() + " , productName: "
                 + getProductName() + " , brand:" + getBrand()
                 + " , serialVersionUID:" + serialVersionUID;
     }

 }

Cloth

Product上使用了一个自定义的注解:@DtoType

 package dto;

 import javax.inject.Qualifier;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;

 @Qualifier
 @Retention(RetentionPolicy.RUNTIME)
 public @interface DtoType {

     public ProductType value();

 }

@DtoType

以及枚举:

 package dto;

 public enum ProductType {
     Product,Computer,Cloth
 }

ProductType

2.2 BaseDtoExtension

为了实现注入配置化,我们还需要对BaseDto写一个扩展类:

 package dto.extension;

 import java.io.IOException;
 import java.io.InputStream;
 import java.util.logging.Logger;

 import javax.enterprise.event.Observes;

 import javax.enterprise.inject.spi.*;
 import javax.xml.parsers.*;

 import dto.*;
 import org.w3c.dom.*;

 import org.xml.sax.SAXException;

 public class BaseDtoExtension implements Extension {
     private final Document document;
     private final Logger log = Logger.getLogger(BaseDtoExtension.class
             .getName());

     public BaseDtoExtension() {
         try {
             InputStream creatureDefs = BaseDtoExtension.class.getClassLoader()
                     .getResourceAsStream("inject-beans.xml");
             DocumentBuilderFactory factory = DocumentBuilderFactory
                     .newInstance();
             DocumentBuilder builder = factory.newDocumentBuilder();
             document = builder.parse(creatureDefs);
         } catch (ParserConfigurationException e) {
             throw new RuntimeException("Error building xml parser, aborting", e);
         } catch (SAXException e) {
             throw new RuntimeException("SAX exception while parsing xml file",
                     e);
         } catch (IOException e) {
             throw new RuntimeException("Error reading or parsing xml file", e);
         }
     }

     <X extends BaseDto> void processInjectionTarget(
             @Observes ProcessInjectionTarget<X> pit) {
         Class<? extends BaseDto> klass = pit.getAnnotatedType().getJavaClass();
         log.info("Setting up injection target for " + klass);
         final Element entry = (Element) document.getElementsByTagName(
                 klass.getSimpleName().toLowerCase()).item(0);
         pit.setInjectionTarget(new XmlWrappedInjection<X>(pit
                 .getInjectionTarget(), entry));
     }
 }

BaseDtoExtension

该扩展,将读取resources/inject-beans.xml文件的内容,并完成BaseDto以及所有子类的加载,包括Inject,该类还使用了另一个辅助类:

 package dto.extension;

 import java.lang.reflect.Field;
 import java.util.Set;

 import javax.enterprise.context.spi.CreationalContext;
 import javax.enterprise.inject.InjectionException;
 import javax.enterprise.inject.spi.*;

 import dto.*;
 import org.w3c.dom.Element;

 public class XmlWrappedInjection<X extends BaseDto> implements
         InjectionTarget<X> {
     private final InjectionTarget<X> wrapped;
     private final Element xmlBacking;

     public XmlWrappedInjection(InjectionTarget<X> it, Element xmlElement) {
         wrapped = it;
         xmlBacking = xmlElement;
     }

     @Override
     public void inject(X instance, CreationalContext<X> ctx) {
         wrapped.inject(instance, ctx);

         final Class<? extends BaseDto> klass = instance.getClass();
         //yjm注:出于演示目的,这里仅反射了本类中声明的field,所以注入时,父类中的field会被忽略,大家可以自行修改,逐层向上反射,直到BaseDto类为止
         for (Field field : klass.getDeclaredFields()) {
             field.setAccessible(true);
             final String fieldValueFromXml = xmlBacking.getAttribute(field
                     .getName());
             try {
                 //System.out.println("the filed name is :" + field.getName());
                 if (field.getName().toLowerCase().equals("serialversionuid")) {
                     continue;
                 }
                 //注:出于演示目的,这里只处理了int、long、String这三种类型,其它类型大家可自行扩展
                 if (field.getType().isAssignableFrom(Integer.TYPE)) {
                     field.set(instance, Integer.parseInt(fieldValueFromXml));
                 } else if (field.getType().isAssignableFrom(Long.TYPE)) {
                     field.set(instance, Long.parseLong(fieldValueFromXml));
                 } else if (field.getType().isAssignableFrom(String.class)) {
                     field.set(instance, fieldValueFromXml);
                 } else {
                     throw new InjectionException("Cannot convert to type "
                             + field.getType());
                 }
             } catch (IllegalAccessException e) {
                 throw new InjectionException("Cannot access field " + field);
             }
         }
     }

     @Override
     public void postConstruct(X instance) {
         wrapped.postConstruct(instance);
     }

     @Override
     public void preDestroy(X instance) {
         wrapped.preDestroy(instance);
     }

     @Override
     public X produce(CreationalContext<X> ctx) {
         return wrapped.produce(ctx);
     }

     @Override
     public void dispose(X instance) {
         wrapped.dispose(instance);
     }

     @Override
     public Set<InjectionPoint> getInjectionPoints() {
         return wrapped.getInjectionPoints();
     }
 }

XmlWrappedInjection

注:这里仅只是演示,所以处理得相对比较简单,如果一个类继承自父类,Inject时,上面的代码,只反射了子类本身声明的field,对于父类的属性,未逐层向上反射,大家可以自行改进。

2.3 控制器

 package controller;

 import javax.inject.Inject;
 import javax.inject.Named;

 import dto.Cloth;
 import dto.Computer;
 import dto.DtoType;
 import dto.Product;
 import dto.ProductType;

 @Named("Ext")
 public class ExtensionController {

     @Inject
     //@DtoType(ProductType.Computer)
     private Computer computer;

     @Inject
     //@DtoType(ProductType.Cloth)
     private Cloth cloth;

     @Inject
     @DtoType(ProductType.Product)
     private Product product;

     public Computer getComputer() {
         return computer;
     }

     public Cloth getCloth() {
         return cloth;
     }

     public Product getProduct() {
         return product;
     }

 }

ExtensionController

注:这里思考一下,为什么Product上必须使用注解@DtoType(ProductType.Product),而其它二个Inject的field不需要?如果暂时没想明白的朋友,建议回到第一节 ,看下1.7节的内容,因为Computer、Cloth都继承自Product类,所以在实例Product类时,系统有3个选择:Computer、Cloth、Product,它不知道该选哪一个?所以运行时,系统会罢工,so,需要额外的注释给它一点提示。

2.4 ext.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>Extension Test</title>
 </h:head>
 <body>
     #{Ext.product.toString()}
     <br /> #{Ext.computer.toString()}
     <br /> #{Ext.cloth.toString()}

 </body>
 </html>

ext.xhtml

2.5 inject-beans.xml

 <?xml version="1.0" encoding="UTF-8"?>
 <basedtos>
     <product productNo="001" productName="A Unknown New Product" />
     <computer productNo="T60" productName="ThinkPad" cpuType="2-cores"
         hardDiskCapacity="320" />
     <cloth productNo="XX" productName="Underware" brand="JackJohns" />
 </basedtos>

inject-beans.xml

该文件设计时,要放在main/java/resources/目录下,部署时,会自动复制到webapp/resources/

2.6 javax.enterprise.inject.spi.Extension

/main/java/resources/META-INF/services目录下,新建一个文件:javax.enterprise.inject.spi.Extension,内容如下:

dto.extension.BaseDtoExtension

该文件的作用是在运行时,告诉系统根据BaseDtoExtension类的定义去找inject-beans.xml,它相当于入口。

2.7 运行效果:浏览地址 http://localhost:8080/cdi-alternative-sample/ext.jsf

跟预期结果完全一样,不过正如文中指出的一样,父类的属性被忽略了,如果父类成员也需要初始化,需要大家自行修改XmlWrappedInjection类

最后附示例源代码:cdi-alternative-sample.zip

JAVA CDI 学习(4) - @Alternative/@Default/@Any & Extension的更多相关文章

  1. JAVA CDI 学习(1) - @Inject基本用法

    CDI(Contexts and Dependency Injection 上下文依赖注入),是JAVA官方提供的依赖注入实现,可用于Dynamic Web Module中,先给3篇老外的文章,写得很 ...

  2. JAVA CDI 学习(3) - @Produces及@Disposes

    上一节学习了注入Bean的生命周期,今天再来看看另一个话题: Bean的生产(@Produces)及销毁(@Disposes),这有点象设计模式中的工厂模式.在正式学习这个之前,先来看一个场景: 基于 ...

  3. JAVA CDI 学习(2) - Scope 生命周期

    在上一节中,我们已经知道了如何用@Inject实现基本注入,这一节研究Bean实例注入后的“生命周期”,web application中有几种基本的生命周期(不管哪种编程语言都类似) 1.Applic ...

  4. JAVA CDI 学习(5) - 如何向RESTFul Service中注入EJB实例

    RESTFul Service中如果要注入EJB实例,常规的@Inject将不起作用,在Jboss中,应用甚至都启动不起来(因为@Inject注入失败),解决方法很简单:将@Inject换成@EJB ...

  5. [原创]java WEB学习笔记66:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) 使用 paramsPrepareParamsStack 重构代码 ,PrepareInterceptor拦截器,paramsPrepareParamsStack 拦截器栈

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  6. Java Web 学习路线

    实际上,如果时间安排合理的话,大概需要六个月左右,有些基础好,自学能力强的朋友,甚至在四个月左右就开始找工作了.大三的时候,我萌生了放弃本专业的念头,断断续续学 Java Web 累计一年半左右,总算 ...

  7. Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问

    本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这 ...

  8. Android(java)学习笔记205:网易新闻RSS客户端应用编写逻辑过程

    1.我们的项目需求是编写一个新闻RSS浏览器,RSS(Really Simple Syndication)是一种描述和同步网站内容的格式,是使用最广泛的XML应用.RSS目前广泛用于网上新闻频道,bl ...

  9. Android(java)学习笔记204:自定义SmartImageView(继承自ImageView,扩展功能为自动获取网络路径图片)

    1.有时候Android系统配置的UI控件,不能满足我们的需求,Android开发做到了一定程度,多少都会用到自定义控件,一方面是更加灵活,另一方面在大数据量的情况下自定义控件的效率比写布局文件更高. ...

随机推荐

  1. C++标准库实现WAV文件读写

    在上一篇文章RIFF和WAVE音频文件格式中对WAV的文件格式做了介绍,本文将使用标准C++库实现对数据为PCM格式的WAV文件的读写操作,只使用标准C++库函数,不依赖于其他的库. WAV文件结构 ...

  2. call,apply,bind

    一.call&apply call, apply都属于Function.prototype的方法,因为属于Function.prototype,所以每个Function对象实例,也就是每个方法 ...

  3. 报错mongoose.connection.db.collectionnames is not a function

    mongoose.connection.db.collectionNames方法已经无效 建议使用mongoose.connection.db.listCollections()

  4. java 猜数字游戏

    作用:猜数字游戏.随机产生1个数字(1~10),大了.小了或者成功后给出提示. 语言:java 工具:eclipse 作者:潇洒鸿图 时间:2016.11.10 >>>>> ...

  5. Windows Azure - Troubleshooting &amp; Debugging: Role Recycling

    每年总会碰到几次Role Recycling,处理完记录下 :) 1. 和往常一样先排查系统日志,修复异常.> 没效果 :( 2. 排查Event Viewer中的Logs,没有发现比较奇怪Lo ...

  6. POJ 2826 An Easy Problem?! --计算几何,叉积

    题意: 在墙上钉两块木板,问能装多少水.即两条线段所夹的中间开口向上的面积(到短板的水平线截止) 解法: 如图: 先看是否相交,不相交肯定不行,然后就要求出P与A,B / C,D中谁形成的向量是指向上 ...

  7. Allegro 中手动制作螺丝孔封装

    以直径2.5mm的螺丝孔为例: 添加过孔,通常过孔的尺寸稍大于实际的螺丝直径,这里设置为2.8mm的直径. 添加过孔焊盘的其他属性. 制作边上的小焊盘. 新建Package Symbol然后点击Lay ...

  8. Deep Learning 1_深度学习UFLDL教程:Sparse Autoencoder练习(斯坦福大学深度学习教程)

    1前言 本人写技术博客的目的,其实是感觉好多东西,很长一段时间不动就会忘记了,为了加深学习记忆以及方便以后可能忘记后能很快回忆起自己曾经学过的东西. 首先,在网上找了一些资料,看见介绍说UFLDL很不 ...

  9. Leetcode 206 Reverse Linked List 链表

    将单向链表反转 完成如图操作,依次进行即可 1 2 3 /** * Definition for singly-linked list. * struct ListNode { * int val; ...

  10. 【Linux】——sleep无法正常休眠

    最近在开发项目的时候遇到一个问题,当使用 sleep(2) 的时候,程序居然没有按照指定的时间去休眠,但是连续执行两次 sleep(2) 的时候,程序可以正常的休眠 2 秒.真是见鬼了.最后查看了以下 ...