(翻譯)JavaEE6規范 CDI教程第一部分
(譯)JavaEE6規范 CDI教程第一部分
引言
此教程講述 DI (依賴注入),并且涵蓋了 CDI (上下文依賴注入)的一些特性,比如類型安全注解配置、替換選擇等內容。
CDI 是依賴注入 (DI) 和攔截 (AOP) 的Java標準規范。DI 和AOP有著很高的知名度,Java需要處理DI和AOP以便在此之上構建其它的標準。DI和AOP是很多Java框架的基礎。
CDI是JavaEE 6的基礎。它很快就得到了 Caucho’s Resin、 IBM’s WebSphere、 Oracle’s Glassfish、 Red Hat’s JBoss和眾多應用服務器的支持。CDI與Spring和Guice框架非常相似,就像JPA很像ORM。CDI簡化了對于DI和AOP的API。如果你使用過Spring或者Guice,你會發現CDI更容易學習和使用。如果你是依賴注入(DI)的新手,那么CDI能讓你迅速理解DI。CDI更容易學習和使用。
CDI能夠獨立使用也能嵌入的任何應用中。
這個教程在發布三年之久的Spring 2.5 DI 教程 (使用Spring “new” DI 注解)之后出現并不奇怪。它將有趣的對比三年前縮寫的Spring DI注解。
本教程設計目標
本教程的目標是描述和解讀不包含復雜的EJB3.1和JSF的DI和CDI。
CDI的優勢是能夠在EJB和JSF之外。本教程只關注CDI。再次聲明在本教程中沒有JSF2和EJB3.1的內容。很多文章和教程都涵蓋如何使用CDI(JEE6規范)。本教程并不是,這里只是CDI。
本教程有完整的代碼示例,你可以下載試用。
我們將放緩速度,逐步的從基礎開始。一旦你理解了基本原理,我們會適當的加快腳步。
所有的示例代碼都以確保能夠運行。我們不會鍵入臨時代碼,如果代碼不能運行,那它就不屬于本教。
示例代碼都有清晰的標題,所以你可以把教程看做一個菜單,將來你如果想使用CDI DI的某些特性,可以方便的在菜單目錄中查找示例。
裝飾器、擴展、攔截器、范圍都不在本教程的范圍之內。
如果這個教程通過google討論組收到足夠的反饋和評論,我將加入CDI AOP(裝飾器和攔截器)綜合教程還有擴展。
更多的建議和反饋會鼓舞我做的更好。
依賴注入
依賴注入(DI)是為軟件組件提供擴展依賴的過程。DI能夠讓你的代碼架構很簡潔。
它幫助你用測試驅動開發的方式設計接口,提供統一的方式注入依賴。例如,一個數據訪問對象(DAO)可能依賴一個數據庫連接。
取而代之,使用JNDI查找數據庫連接,你不需要注入它。
考慮到JNDI是徹底的翻查,DI框架取代對象查找其它準備好的對象(依賴的),一個DI容器能注入這些依賴的對象。這被成為“好萊塢原則”,“不要給我打電話(查找對象),我會打給你(注入對象)”。
如果你接觸過CRC卡,你能想象出一個依賴就像一個合作者。一個合作者是一個對象,另一個對象需要執行它的角色。例如,就像DAO(數據訪問對象)需要一個JDBC連接對象。
依賴注入-自動柜員機 不用CDI或Spring或Guice版
比如說你有一個自動柜員機(ATM,在其它國家也叫自動銀行機)并需要能夠和銀行通話。它需要調用一個傳輸對象來做此事。在這個例子中,傳輸對象掌控對銀行的底層通訊。
這個例子可以用下面兩個接口來描述:
// AutomatedTellerMachine接口
package org.cdi.advocacy;
import java.math.BigDecimal;
public interface AutomatedTellerMachine {
public abstract void deposit(BigDecimal bd); //存錢
public abstract void withdraw(BigDecimal bd); //取錢
}
// ATMTransport接口
package org.cdi.advocacy;
public interface ATMTransport {
public void communicateWithBank(byte[] datapacket);
}
現在 AutomatedTellerMachine 需要一個傳輸器來執行它的意圖,也就是存錢和取錢。要執行這個任務,AutomatedTellerMachine 可能會依賴很多對象和與這些依賴合作才能完成工作。
一個 AutomatedTellerMachine 的實現可能看起來像這樣:
// AutomatedTellerMachineImpl類
package org.cdi.advocacy;
...
public class AutomatedTellerMachineImpl implements AutomatedTellerMachine {
private ATMTransport transport;
...
public void deposit(BigDecimal bd) {
System.out.println("deposit called");
transport.communicateWithBank(...);
}
public void withdraw(BigDecimal bd) {
System.out.println("withdraw called");
transport.communicateWithBank(...);
}
}
AutomatedTellerMachineImpl 不需要關系傳輸器如何從銀行進行存款和取款操作。這是一個中間層允許我們用不同的傳輸器實現來替換,例如下面的例子:
三個傳輸器例子:SoapAtmTransport、StandardAtmTransport和JsonAtmTransport
//StandardAtmTransport
package org.cdi.advocacy;
public class StandardAtmTransport implements ATMTransport {
public void communicateWithBank(byte[] datapacket) {
System.out.println("communicating with bank via Standard transport");
...
}
}
//SoapAtmTransport
package org.cdi.advocacy;
public class SoapAtmTransport implements ATMTransport {
public void communicateWithBank(byte[] datapacket) {
System.out.println("communicating with bank via Soap transport");
...
}
}
//JsonRestAtmTransport
package org.cdi.advocacy;
public class JsonRestAtmTransport implements ATMTransport {
public void communicateWithBank(byte[] datapacket) {
System.out.println("communicating with bank via JSON REST transport");
}
}
注意 ATMTransport 接口的可能實現。AutomatedTellerMachineImpl 不需要關心使用的是那個傳輸器。并且,對于測試和開發,需要替換通話的真實銀行,你可以容易的通過 Mockito和EasyMock 實現,甚至你能夠編寫一個SimulationAtmTransport模擬實現用來測試。 DI的概念超越 CDI、Guice 和 Spring 。因此,你不用 CDI、Guice 或 Spring就能夠實現 DI,比如下面的例子:
// AtmMain: 不使用CDI, Spring或Guice的DI實現
package org.cdi.advocacy;
public class AtmMain {
public void main (String[] args) {
AutomatedTellerMachine atm = new AutomatedTellerMachineImpl();
ATMTransport transport = new SoapAtmTransport();
/* Inject the transport. */
((AutomatedTellerMachineImpl)atm).setTransport(transport);
atm.withdraw(new BigDecimal("10.00"));
atm.deposit(new BigDecimal("100.00"));
}
}
注入不同的傳輸器只不過是調用了不同的setter方法,如下所示:
//不使用CDI, Spring或Guice的DI實現 : setTransport
ATMTransport transport = new SimulationAtmTransport();
((AutomatedTellerMachineImpl)atm).setTransport(transport);
假定在前面我們為 AutomateTellerMachineImpl 添加了一個 setTransport 方法。注意,你只要使用構造器參數就能替換setter方法。因此,保持 AutomateTellerMachineImpl 的接口簡潔。
運行例子
為了馬上能運行例子,我們為你準備了一些pom.xml文件。這里是運行例子的指令說明。
依賴注入-自動柜員機 使用CDI版
要使用CDI管理依賴,需要做如下工作:
在META-INF資源目錄下創建一個空的bean.xml
在 AutomatedTellerMachineImpl 內的 setTransport 方法上使用 @Inject 注解
在 StandardAtmTransport 上標注 @Default 注解
在 SoapAtmTransport 和 JsonRestAtmTransport 上標注 @Alternative 注解
在 AutomatedTellerMachineImpl 上標注 @Named 注解一遍其容易被查找;給它一個命名“atm”
使用CDI beanContainer查找atm,執行存款和取款
在META-INF資源目錄下創建一個空的bean.xml
CDI 需要有一個 bean.xml 文件放置在 META-INF 內,META-INF 可以是jar文件或classpath或web應用 WEB-INF 下的。這個文件完全可以是空的(大小為0 bytes)。如果你的war或jar內的 META-INF 目錄下沒有這個beans.xml,那么CDI將不會處理它。另外CDI將會檢索jar和war文件內的beans.xml,甚至它為0 bytes。
META-INF/beans.xml,可能是空文件
<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">
</beans>
注意,我們在 beans.xml 文件中使用<beans>作為根元素并帶有命名空間。盡管 beans.xml 可以完全是空的,但是加入這個起始元素是個好的習慣。這同樣可以避免IDE對0 byte的 beans.xml 發出警告。(我憎恨IDE警告,它使我分散精力)
在AutomatedTellerMachineImpl內的setTransport方法上使用@Inject注解
@Inject 注解用來標記要注入的位置。你可以對構造器參數、實例變量和setter方法的屬性使用此注解。在這個例子中,我們注解在setTransport 方法上(transport屬性的setter方法)。
// AutomatedTellerMachineImpl使用@Inject注入一個transport
package org.cdi.advocacy;
...
import javax.inject.Inject;
public class AutomatedTellerMachineImpl implements AutomatedTellerMachine {
private ATMTransport transport;
@Inject
public void setTransport(ATMTransport transport) {
this.transport = transport;
}
...
}
默認情況下, CDI 將會尋找 ATMTransport 接口的實現類,一旦找到就會創建一個實例并用setter方法setTransport注入這個實例到 ATMTransport 中。如果我們只有一個 ATMTransport 實例在classpath中,那么我們就不需要注解其它的 ATMTransport 實現。現在我們有三個實現,分別命名為 StandardAtmTransport , SoapAtmTransport 和 JsonAtmTransport ,這就需要我們把其中兩個注解為 @Alternatives ,還有一個注解為 @Default 。
在StandardAtmTransport上標注@Default注解
當前的例子里, StandardAtmTransport 是transport的默認實現,所以我們給他加上 @Default 注解,如下:
//StandardAtmTransport使用注解@Default
package org.cdi.advocacy;
import javax.enterprise.inject.Default;
@Default
public class StandardAtmTransport implements ATMTransport {
...
在SoapAtmTransport和JsonRestAtmTransport上標注@Alternative注解
如果我們沒有給他們使用 @Alternative 注解,那只有等到給他們注解為 @DefaultCDI 才會關注它們。讓我們來給JsonRestAtmTransport 和 SoapRestAtmTransport 加上 @Alternative 注解以便 CDI 不會感到迷惑。
//JsonRestAtmTransport使用注解@Alternative
package org.cdi.advocacy;
import javax.enterprise.inject.Alternative;
@Alternative
public class JsonRestAtmTransport implements ATMTransport { ... }
// SoapAtmTransport使用注解@Alternative
package org.cdi.advocacy;
import javax.enterprise.inject.Alternative;
@Alternative
public class SoapAtmTransport implements ATMTransport { ... }
在AutomatedTellerMachineImpl上標注@Named注解以便其容易被查找;給它一個命名“atm”
我們不在 Java EE6 應用中使用 AutomatedTellerMachineImpl ,而只是通過 beanContainer 來查找它。讓我們給它一個容易理解的名字,比如"atm"。使用 @Name 注解來給他命名。在 JavaEE 6 應用中同樣可以使用 @Name 注解來讓bean可以通過統一EL語言(表達式語言標準,用來在JSP和JSF組件中使用)。
下面是使用 @Named 給 AutomatedTellerMachineImpl 起名為"atm"的代碼:
//AutomatedTellerMachineImpl使用注解@Name [source,java] package org.cdi.advocacy; import java.math.BigDecimal; import javax.inject.Inject; import javax.inject.Named; @Named("atm") public class AutomatedTellerMachineImpl implements AutomatedTellerMachine { ... }
注意,如果你沒有在 @Name 注解中提供名字,那么默認名字就是類名把第一個字母小寫,如下:
//名字默認值 @Named public class AutomatedTellerMachineImpl implements AutomatedTellerMachine { ... }
這時候名字默認就是automatedTellerMachineImpl。
使用CDI beanContainer查找atm,執行存款和取款
最后我們使用beanContainer查找atm并執行一些存款操作。
// AtmMain通過名字查找atm
package org.cdi.advocacy;
...
public class AtmMain {
...
...
public static void main(String[] args) throws Exception {
AutomatedTellerMachine atm = (AutomatedTellerMachine) beanContainer
.getBeanByName("atm");
atm.deposit(new BigDecimal("1.00"));
}
}
如果你在命令行運行它,你將得到如下輸出
Output
deposit called
communicating with bank via Standard transport
你同樣可以通過類型查找AtmMain。
//AtmMain通過類型查找atm
package org.cdi.advocacy;
...
public class AtmMain {
...
...
public static void main(String[] args) throws Exception {
AutomatedTellerMachine atm = beanContainer.getBeanByType(AutomatedTellerMachine.class);
atm.deposit(new BigDecimal("1.00"));
}
}
自從CDI注入是類型安全的,通過名字查找就可能失效。注意我們有一個向下轉型在應用 Java泛型 的時候。
如果你去掉 StandardATMTransport 上的 @Default 注解,你將會得到同樣的輸出。但是如果你去掉其它兩個 transport JsonATMTransport 和 SoapATMTransport 上的 @Alternative,CDI 將會報出如下錯誤信息:
Output
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: javax.enterprise.inject.AmbiguousResolutionException: org.cdi.advocacy.AutomatedTellerMachineImpl.setTransport:
Too many beans match, because they all have equal precedence.
See the @Stereotype and <enable> tags to choose a precedence. Beans:
ManagedBeanImpl[JsonRestAtmTransport, {@Default(), @Any()}]
ManagedBeanImpl[SoapAtmTransport, {@Default(), @Any()}]
ManagedBeanImpl[StandardAtmTransport, {@javax.enterprise.inject.Default(), @Any()}]
...
CDI期望找到一個并且只有一個適合的注入。后面我們將講述如何使用替換選擇(alternative)。
待續..
posted on 2011-06-16 14:03 kuuyee 閱讀(3782) 評論(1) 編輯 收藏 所屬分類: CDI 、JEE