前面几节学习到的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. Kotlin for Java Developers 学习笔记

    Kotlin for Java Developers 学习笔记 ★ Coursera 课程 Kotlin for Java Developers(由 JetBrains 提供)的学习笔记 " ...

  7. Java 8 学习记录

    Java 8 学习记录 官方文档 https://docs.oracle.com/javase/8/ https://docs.oracle.com/javase/8/docs/index.html ...

  8. Java Web 学习路线

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

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

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

随机推荐

  1. 开始对函数式编程 产生了尊崇感,因为Spring4.x ,Grooxy,Lisp,网易出来伞哥和他的博客

    1  无意看到"丢弃重口味的xml配置--spring4用groovy配置bean",这篇文章,里面说到spring4开始可以使用Groovy进行配置,可以取代xml方式和注解方式 ...

  2. dynamic-load-apk插件原理整理

    因为当前项目功能越来越多,编译速度越来越慢(公司电脑配置也挺差的...),并且方法数已超出65535的限制了,虽然通过multidex暂时解决了,但是这并不是一个好的解决方式.所以通过插件来加快编译速 ...

  3. [CMD]oracle数据库的导出导入

    除了推荐使用PL/SQL Developer 工具对oracle进行导出导入(http://www.cnblogs.com/whylaughing/p/5983490.html )之外,比较常用的还有 ...

  4. 将dll程序集添加到缓存里

    1.点击开始---所有程序---...如下图 并以管理员身份运行. 2.输入命令行 gacutil.exe /i D:\Word\CRS_BPM_Sln\SourceCode\BPM\Referenc ...

  5. Symantec Backup Exec 报"Access denied to directory xxx" Error Code E0008488

    使用Symantec Backup Exec将几台Linux服务器上的RMAN备份收带时,偶尔会遇到作业备份失败的情况,检查Job History,就会发现有“Access denied to dir ...

  6. linux vi编辑器操作手册

    简介 Linux下的文本编辑器有很多种,vi 是最常用的,也是各版本Linux的标配.注意,vi 仅仅是一个文本编辑器,可以给字符着色,可以自动补全,但是不像 Windows 下的 word 有排版功 ...

  7. Windows Azure 负载均衡会话保持

    Windows Azure的负载均衡器默认是5元组的hash:源地址,源端口,目的地址,目的端口,协议.即:只有上述五个元组完全一致的会话数据包才会被转发到同一个后端服务器.显然,对于绝大多数通过NA ...

  8. vmware虚拟机 32位Ubuntu 安装matlab_R2012A问题小记

    最近由于学校课程的需要,所以在Ubuntu上捣腾了一下matlab,经历曲折,记录一下,希望能让大家少跳点坑.先分享一个有效的下载链接: http://www.matlab.org.cn/Downlo ...

  9. Stanford机器学习笔记-6. 学习模型的评估和选择

    6. 学习模型的评估与选择 Content 6. 学习模型的评估与选择 6.1 如何调试学习算法 6.2 评估假设函数(Evaluating a hypothesis) 6.3 模型选择与训练/验证/ ...

  10. 小机房的树 codevs 2370

    2370 小机房的树  时间限制: 1 s  空间限制: 256000 KB  题目等级 : 钻石 Diamond 题解  查看运行结果     题目描述 Description 小机房有棵焕狗种的树 ...