JAVA CDI 学习(4) - @Alternative/@Default/@Any & Extension

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

一、@Alternative/@Default/@Any

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

1.1 新建二个示例接口

1 package contract;
2
3 public interface Connection {
4
5     String connect();
6
7 }

Connection

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

1 package contract;
2
3 public interface DriveService {
4
5     String drive();
6
7 }

DriveService

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

1.2 提供接口实现

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

 1 package contract.impl;
 2
 3 import javax.enterprise.inject.Default;
 4
 5 import contract.Connection;
 6
 7 @Default
 8 public class OracleConnection implements Connection {
 9
10     @Override
11     public String connect() {
12
13         return "Oracle Database is connecting...";
14     }
15
16 }

OracleConnection

 1 package contract.impl;
 2
 3 import javax.enterprise.inject.Alternative;
 4
 5 import contract.Connection;
 6
 7 @Alternative
 8 public class SqlServerConnection implements Connection {
 9
10     @Override
11     public String connect() {
12
13         return "Microsoft SqlServer is connecting...";
14     }
15
16 }

SqlServerConnection

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

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

 1 package contract.impl;
 2
 3 import contract.DriveService;
 4
 5 public class CarDriveImpl implements DriveService {
 6
 7     @Override
 8     public String drive() {
 9         String msg = "Drive a car...";
10         System.out.println(msg);
11         return msg;
12     }
13
14 }

CarDriveImpl

 1 package contract.impl;
 2
 3 import javax.enterprise.inject.Alternative;
 4
 5 import contract.DriveService;
 6
 7 @Alternative
 8 public class MotorcycleDriveImpl implements DriveService {
 9
10     @Override
11     public String drive() {
12         String msg = "Drive a motocycle...";
13         System.out.println(msg);
14         return msg;
15     }
16
17 }

MotorcycleDriveImpl

 1 package contract.impl;
 2
 3 import javax.enterprise.inject.Alternative;
 4
 5 import contract.DriveService;
 6
 7 @Alternative
 8 public class TractorDriveImpl implements DriveService {
 9
10     @Override
11     public String drive() {
12         String msg = "Drive a tractor...";
13         System.out.println(msg);
14         return msg;
15     }
16
17 }

TractorDriveImpl

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

1.3 编写Controller类

 1 package controller;
 2
 3 import javax.inject.Inject;
 4 import javax.inject.Named;
 5
 6 import contract.Connection;
 7
 8 @Named("Conn")
 9 public class ConnectionController {
10
11     @Inject
12     private Connection conn;
13
14     public Connection getConn() {
15         return conn;
16     }
17
18 }

ConnectionController

 1 package controller;
 2
 3 import javax.enterprise.inject.*;
 4 import javax.inject.Inject;
 5 import javax.inject.Named;
 6
 7 import contract.DriveService;
 8
 9 @Named("Drive")
10 public class DriveController {
11
12     @Inject
13     private DriveService driveService;
14
15     public DriveService getDriveService() {
16         return driveService;
17     }
18
19     @Inject
20     @Any
21     private Instance<DriveService> anySerInstance;
22
23     public DriveService getAnySerInstance() {
24         return anySerInstance.get();
25     }
26
27 }

DriveController

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

1.4 UI层

 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>CDI - Alternative/Default/Any</title>
 9 </h:head>
10 <body>
11     #{Drive.driveService.drive()}
12     <br />
13     <br />#{Drive.anySerInstance.drive()}
14     <br />
15     <br /> #{Conn.conn.connect()}
16 </body>
17 </html>

Index.xhtml

运行结果:

修改beans.xml的内容如下:

1 <?xml version="1.0" encoding="UTF-8"?>
2
3 <beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4     xsi:schemaLocation="         http://java.sun.com/xml/ns/javaee          http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
5     <alternatives>
6         <class>contract.impl.SqlServerConnection</class>
7         <class>contract.impl.TractorDriveImpl</class>
8     </alternatives>
9 </beans>

beans.xml

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

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

二、Extension

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

2.1 先来定义几个类:

BaseDto.java

 1 package dto;
 2
 3 import java.io.Serializable;
 4
 5 public class BaseDto implements Serializable {
 6
 7     private static final long serialVersionUID = 804047416541420712L;
 8
 9     public BaseDto() {
10         System.out.println("BaseDto's constructor is called...");
11
12     }
13
14 }

BaseDto

 1 package dto;
 2
 3 @DtoType(ProductType.Product)
 4 public class Product extends BaseDto {
 5
 6     public Product() {
 7         System.out.println("Product's constructor is called...");
 8
 9     }
10
11     private static final long serialVersionUID = 7364741422914624828L;
12     private String productNo;
13     private String productName;
14
15     public String getProductName() {
16         return productName;
17     }
18
19     public void setProductName(String productName) {
20         this.productName = productName;
21     }
22
23     public String getProductNo() {
24         return productNo;
25     }
26
27     public void setProductNo(String productNo) {
28         this.productNo = productNo;
29     }
30
31     @Override
32     public String toString() {
33         return "productNo:" + productNo + " , productName: " + productName
34                 + " , serialVersionUID:" + serialVersionUID;
35     }
36 }

Product

 1 package dto;
 2
 3 //@DtoType(ProductType.Computer)
 4 public class Computer extends Product {
 5
 6     public Computer() {
 7         System.out.println("Computer's constructor is called...");
 8     }
 9
10     private static final long serialVersionUID = -5323881568748028893L;
11
12     private String cpuType;
13
14     private int hardDiskCapacity;
15
16     public String getCpuType() {
17         return cpuType;
18     }
19
20     public void setCpuType(String cpuType) {
21         this.cpuType = cpuType;
22     }
23
24     public int getHardDiskCapacity() {
25         return hardDiskCapacity;
26     }
27
28     public void setHardDiskCapacity(int hardDiskCapacity) {
29         this.hardDiskCapacity = hardDiskCapacity;
30     }
31
32     @Override
33     public String toString() {
34         return "productNo:" + getProductNo() + " , productName: "
35                 + getProductName() + " , cpuType:" + getCpuType()
36                 + " , hardDiskCapacity: " + getHardDiskCapacity()
37                 + " , serialVersionUID:" + serialVersionUID;
38     }
39 }

Computer

 1 package dto;
 2
 3 //@DtoType(ProductType.Cloth)
 4 public class Cloth extends Product {
 5
 6     private static final long serialVersionUID = -8799705022666106476L;
 7     private String brand;
 8
 9     public String getBrand() {
10         return brand;
11     }
12
13     public void setBrand(String brand) {
14         this.brand = brand;
15     }
16
17     @Override
18     public String toString() {
19         return "productNo:" + getProductNo() + " , productName: "
20                 + getProductName() + " , brand:" + getBrand()
21                 + " , serialVersionUID:" + serialVersionUID;
22     }
23
24 }

Cloth

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

 1 package dto;
 2
 3 import javax.inject.Qualifier;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6
 7 @Qualifier
 8 @Retention(RetentionPolicy.RUNTIME)
 9 public @interface DtoType {
10
11     public ProductType value();
12
13 }

@DtoType

以及枚举:

1 package dto;
2
3 public enum ProductType {
4     Product,Computer,Cloth
5 }

ProductType

2.2 BaseDtoExtension

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

 1 package dto.extension;
 2
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 import java.util.logging.Logger;
 6
 7 import javax.enterprise.event.Observes;
 8
 9 import javax.enterprise.inject.spi.*;
10 import javax.xml.parsers.*;
11
12 import dto.*;
13 import org.w3c.dom.*;
14
15 import org.xml.sax.SAXException;
16
17
18 public class BaseDtoExtension implements Extension {
19     private final Document document;
20     private final Logger log = Logger.getLogger(BaseDtoExtension.class
21             .getName());
22
23     public BaseDtoExtension() {
24         try {
25             InputStream creatureDefs = BaseDtoExtension.class.getClassLoader()
26                     .getResourceAsStream("inject-beans.xml");
27             DocumentBuilderFactory factory = DocumentBuilderFactory
28                     .newInstance();
29             DocumentBuilder builder = factory.newDocumentBuilder();
30             document = builder.parse(creatureDefs);
31         } catch (ParserConfigurationException e) {
32             throw new RuntimeException("Error building xml parser, aborting", e);
33         } catch (SAXException e) {
34             throw new RuntimeException("SAX exception while parsing xml file",
35                     e);
36         } catch (IOException e) {
37             throw new RuntimeException("Error reading or parsing xml file", e);
38         }
39     }
40
41
42     <X extends BaseDto> void processInjectionTarget(
43             @Observes ProcessInjectionTarget<X> pit) {
44         Class<? extends BaseDto> klass = pit.getAnnotatedType().getJavaClass();
45         log.info("Setting up injection target for " + klass);
46         final Element entry = (Element) document.getElementsByTagName(
47                 klass.getSimpleName().toLowerCase()).item(0);
48         pit.setInjectionTarget(new XmlWrappedInjection<X>(pit
49                 .getInjectionTarget(), entry));
50     }
51 }

BaseDtoExtension

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

 1 package dto.extension;
 2
 3 import java.lang.reflect.Field;
 4 import java.util.Set;
 5
 6 import javax.enterprise.context.spi.CreationalContext;
 7 import javax.enterprise.inject.InjectionException;
 8 import javax.enterprise.inject.spi.*;
 9
10 import dto.*;
11 import org.w3c.dom.Element;
12
13 public class XmlWrappedInjection<X extends BaseDto> implements
14         InjectionTarget<X> {
15     private final InjectionTarget<X> wrapped;
16     private final Element xmlBacking;
17
18     public XmlWrappedInjection(InjectionTarget<X> it, Element xmlElement) {
19         wrapped = it;
20         xmlBacking = xmlElement;
21     }
22
23     @Override
24     public void inject(X instance, CreationalContext<X> ctx) {
25         wrapped.inject(instance, ctx);
26
27         final Class<? extends BaseDto> klass = instance.getClass();
28         //yjm注:出于演示目的,这里仅反射了本类中声明的field,所以注入时,父类中的field会被忽略,大家可以自行修改,逐层向上反射,直到BaseDto类为止
29         for (Field field : klass.getDeclaredFields()) {
30             field.setAccessible(true);
31             final String fieldValueFromXml = xmlBacking.getAttribute(field
32                     .getName());
33             try {
34                 //System.out.println("the filed name is :" + field.getName());
35                 if (field.getName().toLowerCase().equals("serialversionuid")) {
36                     continue;
37                 }
38                 //注:出于演示目的,这里只处理了int、long、String这三种类型,其它类型大家可自行扩展
39                 if (field.getType().isAssignableFrom(Integer.TYPE)) {
40                     field.set(instance, Integer.parseInt(fieldValueFromXml));
41                 } else if (field.getType().isAssignableFrom(Long.TYPE)) {
42                     field.set(instance, Long.parseLong(fieldValueFromXml));
43                 } else if (field.getType().isAssignableFrom(String.class)) {
44                     field.set(instance, fieldValueFromXml);
45                 } else {
46                     throw new InjectionException("Cannot convert to type "
47                             + field.getType());
48                 }
49             } catch (IllegalAccessException e) {
50                 throw new InjectionException("Cannot access field " + field);
51             }
52         }
53     }
54
55     @Override
56     public void postConstruct(X instance) {
57         wrapped.postConstruct(instance);
58     }
59
60     @Override
61     public void preDestroy(X instance) {
62         wrapped.preDestroy(instance);
63     }
64
65     @Override
66     public X produce(CreationalContext<X> ctx) {
67         return wrapped.produce(ctx);
68     }
69
70     @Override
71     public void dispose(X instance) {
72         wrapped.dispose(instance);
73     }
74
75     @Override
76     public Set<InjectionPoint> getInjectionPoints() {
77         return wrapped.getInjectionPoints();
78     }
79 }

XmlWrappedInjection

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

2.3 控制器

 1 package controller;
 2
 3 import javax.inject.Inject;
 4 import javax.inject.Named;
 5
 6 import dto.Cloth;
 7 import dto.Computer;
 8 import dto.DtoType;
 9 import dto.Product;
10 import dto.ProductType;
11
12 @Named("Ext")
13 public class ExtensionController {
14
15     @Inject
16     //@DtoType(ProductType.Computer)
17     private Computer computer;
18
19     @Inject
20     //@DtoType(ProductType.Cloth)
21     private Cloth cloth;
22
23     @Inject
24     @DtoType(ProductType.Product)
25     private Product product;
26
27     public Computer getComputer() {
28         return computer;
29     }
30
31     public Cloth getCloth() {
32         return cloth;
33     }
34
35     public Product getProduct() {
36         return product;
37     }
38
39 }

ExtensionController

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

2.4 ext.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>Extension Test</title>
 9 </h:head>
10 <body>
11     #{Ext.product.toString()}
12     <br /> #{Ext.computer.toString()}
13     <br /> #{Ext.cloth.toString()}
14
15 </body>
16 </html>

ext.xhtml

2.5 inject-beans.xml

1 <?xml version="1.0" encoding="UTF-8"?>
2 <basedtos>
3     <product productNo="001" productName="A Unknown New Product" />
4     <computer productNo="T60" productName="ThinkPad" cpuType="2-cores"
5         hardDiskCapacity="320" />
6     <cloth productNo="XX" productName="Underware" brand="JackJohns" />
7 </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

时间: 2024-10-01 21:35:07

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

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

CDI(Contexts and Dependency Injection 上下文依赖注入),是JAVA官方提供的依赖注入实现,可用于Dynamic Web Module中,先给3篇老外的文章,写得很不错 1.Java EE CDI Dependency Injection (@Inject) tutorial2.Java EE CDI Producer methods tutorial3.Java EE CDI bean scopes 此外,还有jboss官方的参考文档:http://docs

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

在上一节中,我们已经知道了如何用@Inject实现基本注入,这一节研究Bean实例注入后的"生命周期",web application中有几种基本的生命周期(不管哪种编程语言都类似) 1.Application 生命周期 即:web application启动后,处于该生命周期级别的对象/变量,将一直存在,可以被所有web应用的用户共同访问,通常用来做网站计数器,实现流量访问之类.直到web 应用停止或重新启动,该对象才被销毁.简单来说:只要web application处于激活状态,

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

RESTFul Service中如果要注入EJB实例,常规的@Inject将不起作用,在Jboss中,应用甚至都启动不起来(因为@Inject注入失败),解决方法很简单:将@Inject换成@EJB 参考代码: CityInvoker是一个Stateless的EJB package test; import javax.ejb.Stateless; import ... @Stateless public class CityInvoker { public CityResponse getCi

Java语言学习的要点

    Java的学习是比较复杂的,主要表现在相关的一系列平台.规范和协议上.有经验的Java程序员都知道,只掌握了Java语言本身很难开发应用程序.本文不讨论这些复杂的概念,而是从初学者的角度,对于Java语言本身的学习提出自己的见解和建议.本文的讨论基于以下假设:? 学习的目的是为了将来进行应用程序的开发,而不是进行语言理论研究? 将来的应用开发是在成熟的平台上展开,而不是自己从底层开发平台掌握静态方法和属性静态方法和属性用于描述某一类对象群体的特征,而不是单个对象的特征.java中大量应用

JAVA/JSP学习系列之八(改写MySQL翻页例子)

js|mysql|翻页 一.前言 其实,改写后的JDBC Data-Source是运行在Servlet中的,通过JNDI去查找数据源.我用Orion试的,将本站<JAVA/JSP学习系列之六(MySQL翻页例子) > 简单改写了一下. 二.配置 (1)JDBC 需要将用到的JDBC驱动Copy到[ORION]/lib目录下 (2)data-source 在[ORION]/config/data-sources.xml文件中加入如下: 〈data-source class="com.e

【Java面向对象学习】一张图搞定Java面向对象

刚开始学习Java的时候,一个人跌跌撞撞摸索着往前走,很多东西理解的也懵懵懂懂,后来实践的多了,才慢慢清楚:许多东西虽然很基础但是却很重要,是需要反复思考和加深理解的,[Java面向对象学习]一张图搞定Java面向对象,分享一些归纳总结后的技术干货,一张图能搞定的事,绝对不长篇大论. 大家都知道,Java面向对象是Java的一个核心,也是初学者的一个难点,所以我们从这里开始,Let's go ! Java面向对象 面向对象(Oriented Object) 是相对于面向过程而言的,过程其实就是函

java系统学习指导与规划

很多同学刚刚学习java,不清楚java应该如何系统学习,今天我就给各位同学做一个java系统学习指导和规划,为大家讲讲如何系统的学习java. 第一:学java首先你要学 J2SE,它是java体系的基础,也是重中之重.很多人往往不重视基础,其实这是舍本逐末的做法.说这么多就是希望大家能重视基础,能在这条路上走的更远. 学j2se有下面几个目标: 1.你要能真正理解面向对象的优势,理解为什么不是面向过程. 2.掌握java语法基础.包括异常处理.多线程.网络编程.GUI编程等 3.如果你对sw

final-Thinking in Java关于学习匿名内部类遇到的问题

问题描述 Thinking in Java关于学习匿名内部类遇到的问题 // An anonymous inner class that performs initialization.//A briefer version of Parcel5.java. public interface Destination{ String readLabel();}public class Parcel9 { // Argument must be final to use inside anonymo

Java自学能学会吗?Java新手学习路线

作为一种灵活多变前景可瞻,又易学习的编程语言--Java备受年轻人关注,很多开发爱好者,或者想从事Java开发的年轻人放出豪言"我要自学Java,拿高薪,走上人生巅峰" 但是:Java自学能学会吗? 就好像一千个人心中有一千个哈利波特一样,这个答案不绝对也不唯一,Java好学但自学的确阻力有些大,作为一门技术语言,它所蕴含的智慧绝对不是靠几本XX入门,XX框架之类的书所能概括,自学Java你要做好一种英语0基础达到4级水平的准备!在这里中软卓越Java培训为大家整理出了一些自学Java