Posted on 2009-03-23 18:49
小強摩羯座 閱讀(1027)
評論(0) 編輯 收藏 所屬分類:
Java
EJB3.0中的依賴注入,截獲器及其在WebLogic Server 10中的擴展2007-09-05 來自:lizhe1985 [收藏到我的網摘]
1 前言
與EJB2.1相比,EJB3.0規范引入了兩個重要概念:依賴注入(DI:Dependency Injection)和截獲器(Interceptor),本文首先介紹了這兩個概念并給出相關示例,然后分析了EJB3.0規范在這兩方面的不足之處,最終深入探討了WebLogic Server 10對它們的支持和擴展。
2 依賴注入
2.1 基本概念
依賴注入是從開源社區的一些著名輕量級容器(如Spring、Pico container)中所發展出來的概念,其主要思想就是由容器而不是對象本身來負責處理對象之間的依賴關系。與傳統的服務定位器相比,依賴注入具有易測試、弱侵入性等優點,這也就是為什么在最新的Java EE 5規范中引入它的原因。
對于EJB3.0來說,依賴注入就是由容器負責查找被依賴的對象并注入到依賴bean中,而bean本身不再需要進行JNDI或者context查詢。此外,依賴注入發生在任何業務方法被調用之前,而且支持setter方法注入和域注入兩種方式。
通過與標注結合使用,在bean類中聲明依賴注入是非常簡單的(當然,也可以在部署描述符文件中聲明依賴注入):
@EJB用于注入EJB業務對象
@PersistenceUnit 用于注入EntityManagerFactory
@PersistenceContext 用于注入EntityManager
@Resource 用于注入其它資源對象,如連接工廠、消息目標等
2.2 示例
@Stateless
public class ServiceBean implements Service {
private javax.sql.DataSource myDS;
@Resource(mappedName=“LocalDataSource")
public void setMyDS(javax.sql.DataSource ds) {this.myDS = ds; }
@EJB(beanName=“AccountBean")
private Account account;
}
在無狀態會話bean ServiceBean中,聲明了兩個依賴:一個是數據源,一個是業務接口。在運行期間,EJB3.0容器一旦創建了ServiceBean的實例,就會分別通過方法注入和域注入將數據源對象和業務對象注入到ServiceBean中。
3 截獲器
3.1 基本概念
作為EJB3.0中提出的新概念,截獲器是可以對bean的業務方法和生命周期事件進行攔截的組件。截獲器需要由@Interceptors 或發布描述符文件中相關的標簽指定。截獲器可以帶有狀態而且可以進行依賴注入,其生命周期與其所綁定的EJB bean實例的生命周期一致。
定義在截獲器中用于攔截目的的方法被稱為截獲器方法,其中,針對業務方法的截獲器方法通過@AroundInvoke標注或發布描述符文件中相關的標簽指定;針對生命周期回調的截獲器方法通過@PostConstruct, @PreDestroy等標注或發布描述符文件中對應的標簽指定。
截獲器分為四類:
缺省截獲器:可作用于ejb-jar中定義的所有EJB bean。缺省截獲器只能定義在DD中,不存在相應的標注
類級截獲器:只能作用于所指定的EJB bean
方法級截獲器:只能作用于所指定的某個EJB bean業務方法,方法級截獲器不能用于攔截bean的生命周期事件
bean 級截獲器:又被稱為自我截獲器,因為截獲器同時就是EJB bean本身,此時相關的截獲器方法只能作用于該EJB
3.2 截獲器鏈的調用順序
因為可以為EJB定義多個截獲器,所以存在截獲器鏈的調用順序問題,缺省情況下,以下原則被遵循:
調用順序依次是缺省截獲器、類級截獲器、方法級截獲器以及bean級截獲器
對于同類別的截獲器,按照聲明的順序調用
總是優先調用父類的截獲器方法。
此外,EJB3.0規范還提供了更靈活的選項,詳細信息請參考EJB3.0規范中“截獲器”一節:
在發布描述符文件中設置“exclude-default-interceptors” 可以取消對缺省截獲器的調用,而應用“exclude-class-interceptors”則取消對類級截獲器的調用
為了替換缺省的截獲器鏈調用順序,可以設置發布描述符文件的“interceptor-order”。
3.3 示例
@Stateless(name="Trader")
@Interceptors(ClassInterceptor.class)
public class TraderBean implements Trader {
@Interceptors(MethodInterceptor.class)
public String sell(String stockSymbol) {
…
}
@AroundInvoke
public Object selfInterceptor(InvocationContext) throws Exception {
…
}
}
在無狀態會話bean TraderBean中,聲明了兩個截獲器:一個類級截獲器,一個方法級截獲器,此外,通過使用@AroundInvoke標注,TraderBean的方法selfInterceptor被聲明為截獲器方法,所以TraderBean就是bean級截獲器。
4 EJB3.0規范在依賴注入和截獲器方面的不足
從依賴注入方面來說,EJB3.0目前支持的注入類型是非常有限的,諸如List、Map之類的集合類型都不能被注入。
另一方面,EJB3.0的截獲器可被看作是一種AOP模型,截獲器類相當于方面(Aspect),而截獲器方法相當于通知(Advice),但這種AOP是非常簡單的:
不支持引入(Introduction);
不支持切入點(Pointcut)模式
通過標注告訴容器某個EJB需要使用截獲器,從而使得這種AOP模型具有一定的侵入性
5 WebLogic Server 10 EJB3.0容器簡介
5.1 Pitchfork 簡介
Pitchfork是由Interface21與BEA合作開發的一個具有Apache License的開源項目,作為Spring的“增值”項目,開發Pitchfork有兩個目的:
當前應用服務器可使用Pitchfork實現Java EE 5中的依賴注入,標注處理以及EJB3.0中的截獲器。
在Spring容器中支持Java EE 5的標注
5.2 WebLogic Server 10 EJB3.0容器的架構
WebLogic Server 10完全支持Java EE 5和EJB3.0,其EJB3.0 容器是基于Pitchfork來實現依賴注入與截獲器的,而Pitchfork又利用了Spring的依賴注入和AOP。此時,WebLogic Server 10的用戶有兩種選擇:
使用EJB3.0標準模型: EJB bean實例由EJB容器創建,然后由Pitchfork實施Java EE 5 的依賴注入和截獲器,這是WebLogic Server 10的缺省配置。
使用Spring擴展模型:EJB bean實例由Spring容器創建并實施Spring的依賴注入,然后由Pitchfork實施Java EE 5的依賴注入,EJB3.0截獲器和Spring AOP。
6 Spring擴展模型
6.1 使用Spring擴展模型的主要開發步驟
下載某個版本的Spring類庫(最好是由BEA指定的版本),至少需要包括以下jar文件:spring.jar, aspectjweaver.jar, commons-logging.jar, log4j-1.2.14.jar,如果需要,還可以增加其它jar文件。
將這些jar文件加入到WebLogic Server 10的類路徑中。
創建文本文件Weblogic.application.ComponentFacotry以標識當前應用將使用Spring擴展模型,其內容只有一行: Weblogic.application.SpringComponentFactory。
創建/META-INF/services子目錄,然后放入前面創建的文件Weblogic.application.ComponentFacotry
建立Spring 配置文件,并放入到/META-INF目錄下,如果是EJB應用程序,Spring配置文件的名字應該是spring-ejb-jar.xml,如果是Web應用程序,Spring配置文件的名字則是spring-web.xml.
為了使EJB bean能利用Spring的依賴注入與AOP,需要同時將其定義在spring-ejb-jar.xml(或spring-web.xml)中,從Spring角度來看,這就是一個普通的Spring bean,只不過它的id或name必須與對應的EJB name相同。
將上述目錄文件、相關的Java類、部署描述符等打包成標準的jar文件、war文件或ear文件。
需要注意的是,在Spring配置文件中定義的bean,如果它也是一個EJB bean,則該Spring bean的class屬性會被忽略以避免潛在的沖突。
6.2 示例
示例一:利用Spring的依賴注入。本例將演示兩點,(1) 注入一個集合類型,(2) 注入一個Spring POJO對象
(1) 開發一個bean類
@Stateless(name="AccountManagementBean")
@Remote({examples.AccountManagement.class})
public class AccountManagementBean
{
private List accountList;
private BusinessObject businessObject;
public boolean doBusiness(String account) {
if (accountList.contains(account)) {
businessObject.doBusiness();
return true;
}
return false;
}
public BusinessObject getBusinessObject() {
return businessObject;
}
public void setBusinessObject(BusinessObject businessObject) {
this.businessObject = businessObject;
}
public List getAccountList() {
return accountList;
}
public void setAccountList(List accountList) {
this.accountList = accountList;
}
}
(2) 一個POJO
public class BusinessObjectImpl implements BusinessObject
{
private String result;
public void doBusiness() {
System.out.println("I am doing business, the result is: " + result);
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
}
(3) Spring-ejb-jar.xml
…
<beans>
<bean name="BusinessObject" class="examples.BusinessObjectImpl">
<property name="result" value="success" />
</bean>
<bean id="AccountManagementBean">
<property name="businessObject" ref="BusinessObject" />
<property name="accountList">
<list>
<value>Safin</value>
<value>Amy</value>
<value>Albert</value>
<value>Yang</value>
</list>
</property>
</bean>
</beans>
示例二:利用Spring 的AOP。本例演示了混合使用JEE interceptor、基于代理的Spring AOP以及AspectJ的Aspect
(1) 開發一個bean類
@Stateless(name="AccountManagementBean")
@Remote({examples.AccountManagement.class})
public class AccountManagementBean
{
public AccountManagementBean() {}
@Interceptors({examples.JEEInterceptor.class})
public boolean doBusiness(String account) {
System.out.println("I am doing business " + account);
return true;
}
}
(2) 開發一個JEE interceptor
public class JEEInterceptor {
@AroundInvoke
private Object intercept(InvocationContext inv) throws Exception{
System.out.println("I am in JEEInterceptor.intercept()");
return inv.proceed();
}
}
(3) 開發一個Advice
public class SimpleAdvice implements MethodInterceptor{
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("I am in Spring AOP interceptor");
return invocation.proceed();
}
}
(4) 開發一個Pointcut
public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method method, Class cls) {
return "doBusiness".equals(method.getName());
}
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class cls) {
return AccountManagementBean.class.isAssignableFrom(cls);
}
};
}
}
(5) 開發一個AspectJ Aspect
@Aspect
public class Aspects {
@Around("execution(* examples.AccountManagementBean.doBusiness(..))")
public Object around(ProceedingJoinPoint pjp) {
System.out.println(“I am in AspectJ aspect);
Object retVal = null;
try {
retVal = pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
return retVal;
}
}
(5) Spring-ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean id="AccountManagementBean" />
<bean id="myAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice">
<bean class=”examples.SimpleAdvice” />
</property>
<property name="pointcut">
<bean class="examples.SimpleStaticPointcut" />
</property>
</bean>
<bean id="MyAspect" class="examples.Aspects" />
</beans>
6.3 優點
提供Spring擴展模型具有如下好處:
可以在同一個發布單元中混合使用EJB bean和Spring POJO,而且一個EJB bean可以同時是一個Spring POJO,因此被EJB容器和Spring容器同時管理
有效地克服了EJB3.0 規范在依賴注入和截獲器方面的一些缺陷
為用戶提供更靈活的選擇:
如果標準的EJB 依賴注入以及截獲器可以滿足應用需求,或者可移植性是優先考慮的因素,則選用標準模型
如果標準的EJB依賴注入以及截獲器不能滿足應用需求,或者不用考慮可移植性,或者需要集成一些Spring應用,則應選用Spring擴展模型
7 Spring AOP在WebLogic Server 10 Spring擴展模型中的使用限制及說明
7.1 代理生成方式
Spring AOP采用的是一種基于代理的框架,目前有兩種生成代理的方式:JDK動態代理和CGLIB。在WebLogic Server 10的Spring擴展模型中,目前不支持CGLIB方式,這主要是因為生成的EJB類都是final的
7.2 代理創建
在Spring中,代理或者是通過諸如ProxyFactoryBean之類的工廠bean明確創建,或者是通過“自動代理”創建器隱含創建。
在WebLogic Server 10中,Pitchfork已經隱含提供了類似的“自動代理”機制,所以原有的Spring代理創建方式是無效的,換句話說,用戶只需要在Spring配置文件中定義目標bean以及對應的Spring Advisor和AspectJ Aspect, 不需要再定義ProxyFactoryBean和“自動代理”創建器,Pitchfork會自動為我們創建AOP代理
7.3 截獲的順序問題
在Spring擴展模型中,可以混合使用JEE截獲器、Spring Advisor以及AspectJ Aspect,此時的調用順序為JEE截獲器、Spring Advisor以及AspectJ Aspect。
7.4 bean的實例化模式
缺省方式下,Spring bean是Singleton的,即Spring容器總是返回同一個bean實例,在Spring擴展模型中,這意味著同一個Spring Advisor或AspectJ Aspect實例將被應用到不同的EJB bean實例上,如果Advisor或Aspect實例是有狀態的,這可能會產生問題,為此我們推薦如下的使用方式:
SLSB & MDB
帶有狀態的Advisor或Aspect
不推薦用帶有狀態的Advisor或Aspect,除非用戶意識到相關的后果并認為這種后果是可以接收的,此時的實例化模式取決于具體應用
沒有狀態的Advisor或Aspect
采用Singleton實例化模式
SFSB
帶有狀態的Advisor或Aspect
正常情況下應該采用的實例化模式是Prototype,除非用戶意識到采用Singletong的后果并認為這種后果是可以接收的
沒有狀態的Advisor或Aspect
采用Singleton實例化模式
8 結束語
依賴注入和截獲器的引入,使得EJB3.0克服了很多EJB2.1的缺點,但與開源社區中流行的輕量級容器相比,EJB3.0無論是在依賴注入的范圍還是在AOP的易用性方面都還存在一定的不足。而最近BEA所推出的WebLogic Server 10不但完全支持EJB3.0,而且對其進行了有效擴展,這使得一方面可以充分利用在開源社區中出于領導地位的Spring在依賴注入和AOP方面的強大能力,另一方面又緊密依托WebLogic Server 10在事務、安全、集群方面領先的技術,從而為EJB3.0用戶開發出易維護、可擴展、高效率的企業應用程序打下堅實的基礎。
作者簡介
李鋼 dev2devid: cc_masterli ,BEA研發中心WebLogic Server 研發團隊成員,西安交通大學計算機專業博士,一直從事java中間件領域的研發,2005年加入BEA,現為EJB3 中國研發小組的負責人。