JAVA CDI 学习(4) - @Alternative/@Default/@Any & Extension
前面几节学习到的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的更多相关文章
- JAVA CDI 学习(1) - @Inject基本用法
CDI(Contexts and Dependency Injection 上下文依赖注入),是JAVA官方提供的依赖注入实现,可用于Dynamic Web Module中,先给3篇老外的文章,写得很 ...
- JAVA CDI 学习(3) - @Produces及@Disposes
上一节学习了注入Bean的生命周期,今天再来看看另一个话题: Bean的生产(@Produces)及销毁(@Disposes),这有点象设计模式中的工厂模式.在正式学习这个之前,先来看一个场景: 基于 ...
- JAVA CDI 学习(2) - Scope 生命周期
在上一节中,我们已经知道了如何用@Inject实现基本注入,这一节研究Bean实例注入后的“生命周期”,web application中有几种基本的生命周期(不管哪种编程语言都类似) 1.Applic ...
- JAVA CDI 学习(5) - 如何向RESTFul Service中注入EJB实例
RESTFul Service中如果要注入EJB实例,常规的@Inject将不起作用,在Jboss中,应用甚至都启动不起来(因为@Inject注入失败),解决方法很简单:将@Inject换成@EJB ...
- [原创]java WEB学习笔记66:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) 使用 paramsPrepareParamsStack 重构代码 ,PrepareInterceptor拦截器,paramsPrepareParamsStack 拦截器栈
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- Kotlin for Java Developers 学习笔记
Kotlin for Java Developers 学习笔记 ★ Coursera 课程 Kotlin for Java Developers(由 JetBrains 提供)的学习笔记 " ...
- Java 8 学习记录
Java 8 学习记录 官方文档 https://docs.oracle.com/javase/8/ https://docs.oracle.com/javase/8/docs/index.html ...
- Java Web 学习路线
实际上,如果时间安排合理的话,大概需要六个月左右,有些基础好,自学能力强的朋友,甚至在四个月左右就开始找工作了.大三的时候,我萌生了放弃本专业的念头,断断续续学 Java Web 累计一年半左右,总算 ...
- Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问
本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这 ...
随机推荐
- php设计模式 工厂、单例、注册树模式
Source Code Pro字体 easyphp 命名空间:隔离类和函数,php5.3以后 //test5.php<?php namespace Test5;//命名空间必须是程序脚本的第一 ...
- drop和delete的区别是什么
当你不再需要该表时, 用 drop;当你仍要保留该表,但要删除所有记录时, 用 truncate;当你要删除部分记录时(always with a WHERE clause), 用 delete.
- ORACLE 查看RMAN的备份信息总结
关于Oracle数据库的RMAN备份,除了邮件外,是否能通过其它方式检查RMAN备份的成功与失败呢?其实我们可以通过下面SQL脚本来检查某个时间段备份失败的记录: SELECT * FROM V$RM ...
- sql server 小记——分区表(上)
我们知道很多事情都存在一个分治的思想,同样的道理我们也可以用到数据表上,当一个表很大很大的时候,我们就会想到将表拆 分成很多小表,查询的时候就到各个小表去查,最后进行汇总返回给调用方来加速我们的查询速 ...
- PHP 类型判断和NULL,空值检查
PHP是一种宽松类型的编程语言,在函数中对传入的参数值的“类型”以及”值是否为空或者NULL“进行检查是不可缺少的步骤. 类型检查 从PHP5开始,PHP允许对函数的参数进行类型约束,即可以约束参数的 ...
- Android搭建junit测环境
在AndroidManifest.xml文件中增加两个东西,分别是: 1.uses-library ,位于application里面. 2.instrumentation,与application同级 ...
- PHP用mb_string函数库处理与windows相关中文字符
昨天想批处理以前下载的一堆文件,把文件里的关键内容用正则匹配出来,集中处理.在操作文件时遇到一个问题,就是windows操作系统中的编码问题. 我们都知道windows中(当然是中文版),文件名和文件 ...
- 安装使用ubuntu问题汇总
很早以前就安装了ubuntu系统,可是一直没怎么用,也没有深入研究.这两天重装了一下windows,顺带着也重新装了一遍最新的ubuntu14.04.期间碰到了不少问题,一个个解决也花费了不少时间.所 ...
- 烂泥:mysql5.5多实例部署
本文由秀依林枫提供友情赞助,首发于烂泥行天下. mysql5.5数据库多实例部署,我们可以分以下几个步骤来完成. 1. mysql多实例的原理 2. mysql多实例的特点 3. mysql多实例应用 ...
- ubuntu14.04下的NVIDIA Tesla K80显卡驱动的安装教程
搞深度学习如何能够不与浑身是“核”的显卡打交道呢? 人工智能的兴起除了数据量的大量提升,算法的不断改进,计算能力的逐步提高,还离不开软件基础设施的逐步完善.当下的主流的深度学习工具软件无论是Caffe ...