前面几节学习到的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类