提要本文將與你一同探討怎樣把註解和方面的威力聯合起來,以與ejb 3.0兼容的方式為企業實現提供聲明性服務,而在同時仍然提供容器的獨立性。
一、 引言
在我們共同尋求進一步提高軟件開發生產性能的方法的過程中,我們-作為java社團成員-一般都轉向j2ee來提供針對企業開發中更具挑戰性的技術問題如分佈式事務管理、並發性和對象分佈等的解決方案。其背後的指導思想-這些複雜的企業服務能被應用程序服務器供應商所實現並能為商業開發者所平衡-的確是一種很好的思想。 j2ee,具體地說是ejb,已成功地提供了一個平台-在其上構建企業java應用程序。
這其中部分的成功是由於能夠進行聲明性編程-一種程序開發方式-用這種方式,你可以聲明基礎結構服務而不是用商業邏輯明確地編碼從而使代碼散佈於各處。 ejb已經證明了這種編程方式的價值-通過允許企業問題例如事務和安全被用一種發布描述符所聲明並為容器所處理。
然而,在過去的歲月中,越來越多的開發者認識到ejb在團隊的生產效率方面給它自己帶來新的大量的挑戰-每個ejb必須伴隨多個接口,以一種發布描述符描述,經由jndi被存取,等等。而在容器外ejb上進行單元測試也帶來另外的困難,如今ejb已不再把重點放在單純的面向對像開發上。
請注意,為閱讀本文您需具備如下工具:
·java 2 sdk 1.5
·maven 2.0 beta 2
ejb 3.0的目標在於從以下幾個方面使企業開發更為容易:
·通過引入元數據註解來實現聲明性請求企業服務
·經由註解實現依賴性/資源注入
·實現企業beans與ejb特定接口的解耦
·經由輕量級的對象關係映射實現持續性存儲的簡化
這對於ejb開發者來說尤如一股春風-一直以來,他們竭力地從事開發、測試和維護ejb。利用ejb 3.0寫一個企業bean現在變得很容易,就如用特定的註解創建一個pojo(傳統的java對象)以把它標明為一個ejb並請求企業服務。下面是一個來自於ejb 3.0 public draft中ejb的例子:
@stateful
public class cartbean implements shoppingcart
{
private float total;
private vector productcodes;
public int someshoppingmethod(){...};
...
}
ejb 3.0聲明中實質上指明開發者需要的不是一重量級的、"一次發佈滿足所有"的解決方案,而是一個輕量級的、容易使用的解決方案-為開發者提供一定範圍的企業服務。為此,ejb 3.0所提供的最重要的方法之一就是實現企業beans與ejb api的解耦。並且,此解決方案還帶來令人感興趣的衍生-ejb現在不僅能夠運行在不同的ejb容器上,而且還能運行於任何應用程序框架內部-這些框架必須能夠識別ejb 3.0(jsr 220)和用於聲明企業服務的普通註解(jsr 250)。
本文沒有提供關於聲明性編程、ejbs、方面或註解的深度探索。相反,而只是分析一下這些技術之間的相互關係並討論如何把它們用一種新的方式結合起來以簡化應用程序開發。
在本文中,你將會學習到如何編寫一個ejb 3.0兼容的bean並且通過創建幾個簡單的方面使其具有聲明性事務管理、安全和資源注入等功能。我希望您能從這個練習中得到以下的受益:
·學習方面的三個實際應用(依賴性注入、安全和事務)。
·熟悉ejb 3.0及其背後的思想。
·認識到怎樣實現ejb與特定api的解耦以允許ejb 3.0兼容的服務能夠以輕量級實現而不是僅由ejb來提供。
二、 實例應用程序-航班訂購
在整個後面的討論中,你將學習到一個航班訂購系統的實現-它使用方面和註解來實現依賴性注入、安全和事務管理。該應用程序僅執行兩項功能:它允許用戶搜索航班(圖1),然後訂購一次旅行(圖2)。這兩個操作都將被進行安全處理以僅允許能被識別的用戶來執行它們。另外,既然"訂購旅行"操作包含訂購兩個航班(外出和返回航班),那麼需要把該操作創建為事務性的-如,兩個訂購將作為一個工作單元要么都成功要么都失敗。
圖1.航班查詢:首先,用戶查找滿足他們的指定標準的航班。
圖2.航班訂購:接下來,用戶訂購一個外出航班和一個返回航班。兩個訂購要么都成功要么都失敗。
這個簡單的web應用程序包含幾個servlet、一個服務外觀和一個dao層(見圖3)。
資源配置、安全性和事務管理等橫切關注點將由方面(用aspectj 1.5 m3實現)所提供以實現在java 5註解中所聲明的注入行為。
圖3.航班訂購系統架構:這個航班訂購系統包括三個主要組成組件-它們聯合起來共同完成用戶請求。三、 資源注入
ejb 3.0草案聲明中允許資源經由@resource註解來聲明(這一決定定義在草案普通註解聲明中)並且被容器注入進你的ejb。依賴性注入是一項技術-使用這種技術,一個對像外部的實體而不是顯式地為該對象所創建的實體能夠提供(注入)一個對象的依賴性。它有時被描述為好萊塢原則-這開玩笑似地意味著"不要給我們打電話,我們會給你打電話的"。
以travelagencyserviceimpl類為例-這個類為了持續性存儲一些數據需要找到一個iflightdao接口的實現。傳統地,這是經由一個工廠、singleton、服務定位器或一些另外的定制解決方案來實現的。其中,一個可能的解決方案看上去如下所示:
public class travelagencyserviceimpl implements itravelagencyservice
{
public iflightdao flightdao;
public travelagencyserviceimpl()
{ flightdao = flightdaofactory.getinstance().getflightdao(); }
public void booktrip(long outboundflightid, long returnflightid, int seats)
throws insufficientseatsexception
{
reserveseats(outboundflightid, seats);
reserveseats(returnflightid, seats);
}
}
你已看到,這個實現包含創建一個特定的工廠類-它很可能讀取存儲在某處的配置信息以了解要創建iflightdao的實現方式。如果不是讓服務顯式地創建它的由容器所注入的依賴性,那麼配置細節和對象創建將被代理到容器上。這允許一個應用程序中的組件能夠被容易地連接到一起-用不同的配置並且消除大量老式的singleton和工廠代碼。
該類的一個實現-它依賴於一個用jsr 250資源註解所聲明的iflightdao的實現-可能看上去如下所示:
public class travelagencyserviceimpl implements itravelagencyservice
{
@resource(name = "flightdao")
public iflightdao flightdao;
public void booktrip(long outboundflightid, long returnflightid, int seats)
throws insufficientseatsexception
{
reserveseats(outboundflightid, seats);
reserveseats(returnflightid, seats);
}
}
在這種情況下,容器將把一個命名為"flightdao"的資源的正確實現提供給服務類。但是,如果你現在就想利用資源注入,而不是等待ejb 3.0發行版,又該如何呢?好,你可以採用一種輕量級的容器-它能夠提供例如spring或pico container的依賴性注入。然而,當前我還不了解存在一個輕量級的容器-它能夠使用jsr 250資源註解以指定注入要求(儘管我非常盼望在這一方面出現一些)。
一種解決方案是使用方面來實現依賴性注入。如果你為此使用@resource註解,那麼你的實現將與ejb 3.0方式一致並且向前兼容ejb 3.0實現-而實現這並不是很困難的事情。下列列表顯示用aspectj創建的一個方面-它注入用@resource註解所註解的字段:
@aspect
public class injectionaspect
{
private dependencymanager manager = new dependencymanager();
@before("get(@resource * *.*)")
public void beforefieldaccesses(joinpoint thisjoinpoint)
throws illegalargumentexception, illegalaccessexception
{
fieldsignature signature = (fieldsignature) thisjoinpoint.getsignature();
resource injectannotation = signature.getfield().getannotation(resource.class);
object dependency = manager.resolvedependency(signature.getfieldtype(),injectannotation.name());
signature.getfield().set(thisjoinpoint.getthis(), dependency);
}
}
這個簡單方面所做的全部是,從一個屬性文件(這個邏輯被封裝在dependencymanager對像中)查詢實現類並且在存取字段之前把它注入到用@resource註解所註解的字段中。顯然,這種實現不是完整的,但是它確實說明了你可以怎樣以一種jsr 250兼容方式且不需採用ejb來提供資源注入。
四、 安全性
除了資源注入外,jsr 250和ejb 3.0還提供經由註解的元數據安全表示。 javax.annotation.security包定義了五個註解-runas,rolesallowed,permitall,denyall和rolesreferenced-所有這些都能應用到方法上來定義安全要求。例如,如果你想要聲明上面列出的bookflight方法僅能為具有"user"角色的調用者所執行,那麼你可以用如下的安全約束來註解這個方法:
public class travelagencyserviceimpl implements itravelagencyservice
{
@resource(name = "flightdao")
public iflightdao flightdao;
@rolesallowed("user")
public void booktrip(long outboundflightid, long returnflightid, int seats)
throws insufficientseatsexception
{
reserveseats(outboundflightid, seats);
reserveseats(returnflightid, seats);
}
}
這個註解將指出由容器來負責保證只有指定角色的調用者才能執行這個方法。因此現在我將展示另一個簡單的方面-它將進一步加強在該應用程序上的安全約束:
@aspect
public class securityaspect
{
@around("execution(@javax.annotation.security.rolesallowed * *.*(..))")
public object aroundsecuredmethods(proceedingjoinpoint thisjoinpoint)
throws throwable
{
boolean callerauthorized = false;
rolesallowed rolesallowed = rolesallowedforjoinpoint(thisjoinpoint);
for (string role : rolesallowed.value())
{
if (callerinrole(role))
{ callerauthorized = true; }
}
if (callerauthorized)
{ return thisjoinpoint.proceed(); }
else
{
throw new runtimeexception("caller not authorized to perform specified function");
}
}
private rolesallowed rolesallowedforjoinpoint(proceedingjoinpoint thisjoinpoint)
{
methodsignature methodsignature = (methodsignature) thisjoinpoint.getsignature();
method targetmethod = methodsignature.getmethod();
return targetmethod.getannotation(rolesallowed.class);
}
private boolean callerinrole(string role)
{ ... }
}
這個方麵包含了所有方法的執行-通過核實該調用者是在註解中所指定的角色之一,用@rolesallowed註解來註解並且保證調用者被授權調用該方法。當然你還能代之以任何你喜歡的算法來授權用戶並且檢索他/她的角色,例如jaas或一個定制的解決方案。在本示例程序中,為方便起見,我選擇代理到servlet容器。
五、 事務
事務成為企業開發的一個重要部分-因為它們有助於在一個並發的環境中的數據集成。從一個高層次上看,事務可以通過多種或者是完整的或者是都不完整的操作來保證這一點。
不像針對資源注入和安全的註解,針對事務的註解是特定於ejb 3.0的並且沒有在jsr 250普通註解中定義。 ejb 3.0定義了兩個與事務相聯繫的註解:transactionmanagement和transactionattribute。該transactionmanager註解指定事務是由容器所管理還是為bean所管理的。在ejb 3中,如果這個註解沒被指定,那麼將使用容器所管理的事務。 transactionattribute註解用於指定方法的事務傳播級別。有效值-包括強制的、要求的、要求新的、支持的、不支持的和從不支持的-用來定義是否要求一個已有事務或啟動一個新的事務,等等。
因為bookflight操作包含兩步-訂購一個外出航班和一個返回航班,所以,通過把它包裝成一個事務,你能保證這項操作的一致性。通過使用ejb 3.0事務註解,這將看上去如下所示:
public class travelagencyserviceimpl implements itravelagencyservice
{
@resource(name = "flightdao")
public iflightdao flightdao;
@rolesallowed("user")
@transactionattribute(transactionattributetype.required)
public void booktrip(long outboundflightid, long returnflightid, int seats)
throws insufficientseatsexception
{
reserveseats(outboundflightid, seats);
reserveseats(returnflightid, seats);
}
}
並且你可以應用一個簡單的方面來自動地界定事務邊界:
@aspect
public class transactionaspect
{
@pointcut("execution(@javax.ejb.transactionattribute * *.*(..))")
public void transactionalmethods(){}
@before("transactionalmethods()")
public void beforetransactionalmethods()
{ hibernateutil.begintransaction(); }
@afterreturning("transactionalmethods()")
public void afterreturningtransactionalmethods()
{ hibernateutil.committransaction(); }
@afterthrowing("transactionalmethods()")
public void afterthrowingtransactionalmethods()
{ hibernateutil.rollbacktransaction(); }
}
這個實現基於這樣的假設-hibernate和無所不在的線程本地模式被用於管理hibernate會話和事務對象;但是,任何其它適當的實現,例如基於jta的實現,都能被代替使用。
六、 小結
通過使用ejb 3.0和jsr 250註解集,本文已經展示了例如資源管理、安全和事務等橫切關注點是怎樣被實現為方面的。當然,還有許多內容我們需進一步學習。首先要學的就是通過使用aspectj的實現這些示例方面為模塊化橫切關注點所提供的藍圖。其次,我們已經看到了在如今正浮出水面的ejb 3.0聲明背後的一些新思想和新概念。最後,我們還以戲劇性的方式看到了從ejb api中解耦我們的商業對象所必須要提供的自由。在這一點上,所有你想使travelagencyserviceimpl成為一個無狀態會話要做的僅是添加一條最後的註解:
@stateful
public class travelagencyserviceimpl implements itravelagencyservice
{ ... }
最後,我非常希望這種自由地提供企業服務的方式會帶來框架/容器工業界的競爭和革新。