天天看點

JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension

前面幾節學習到的CDI内容,基本上都是hard-code,以寫死的方式在代碼裡指定注入類型,這并非依賴注入的本意,依賴注入的優勢之一在于“解耦”,這一節我們将學習如何利用配置來動态注入的類型及屬性初始化。

一、@Alternative/@Default/@Any

當一個服務接口(也稱契約)有多個實作時,可以在代碼裡指定一個預設的實作類型(即:标注成@Default或@Any),其它實作類标注成@Alternative,以後如果需要動态切換實作類,隻要在webapp/WEB-INF/beans.xml中配置即可。

1.1 建立二個示例接口

JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension

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

Connection

該接口模拟db連接配接,裡面有一個connect方法,用來連接配接db.

JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
1 package contract;
2 
3 public interface DriveService {
4     
5     String drive();
6 
7 }      

DriveService

該接口模拟遊戲應用中,有些人物具有駕駛技能。

1.2 提供接口實作

假設Connection有二個實作,一個用來連接配接到Oracle Database,另一個用來連接配接Microsoft Sql Server

JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
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

JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
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的實作,我們提供三種實作:駕駛汽車、機車、拖拉機

JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
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

JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
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

JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
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類

JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
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

JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any & Extension
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層

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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

運作結果:

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension

修改beans.xml的内容如下:

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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 CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension

在不修改java源代碼的前提下,僅通過配置檔案beans.xml的修改,就動态切換了接口的實作類。

二、Extension

不僅注入的類型可以由配置檔案來動态切換,也可以由配置檔案來直接初始化注入對象的屬性值(雖然我個人認為這種場景在實際開發中其實并不多見)

2.1 先來定義幾個類:

BaseDto.java

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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

以及枚舉:

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
1 package dto;
2 
3 public enum ProductType {
4     Product,Computer,Cloth
5 }      

ProductType

2.2 BaseDtoExtension

為了實作注入配置化,我們還需要對BaseDto寫一個擴充類:

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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,該類還使用了另一個輔助類:

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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 控制器

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension
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

JAVA CDI 學習(4) - @Alternative/@Default/@Any &amp; Extension

跟預期結果完全一樣,不過正如文中指出的一樣,父類的屬性被忽略了,如果父類成員也需要初始化,需要大家自行修改XmlWrappedInjection類

最後附示例源代碼:cdi-alternative-sample.zip

作者:菩提樹下的楊過

繼續閱讀