前面幾節學習到的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 }
該接口模擬db連接,里面有一個connect方法,用來連接db.

1 package contract; 2 3 public interface DriveService { 4 5 String drive(); 6 7 }
該接口模擬游戲應用中,有些人物具有駕駛技能。
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 }

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 }
注: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 }

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 }

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 }
注: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 }

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中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>
運行結果:
修改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>
重新在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 }

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 }

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 }

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 }
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 }
以及枚舉:

1 package dto; 2 3 public enum ProductType { 4 Product,Computer,Cloth 5 }
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 }
該擴展,將讀取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 }
注:這里僅只是演示,所以處理得相對比較簡單,如果一個類繼承自父類,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 }
注:這里思考一下,為什么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>
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>
該文件設計時,要放在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
文章列表