EJB 3.0+Aspect實(shí)現(xiàn)聲明性編程初步
一、 引言
在我們共同尋求進(jìn)一步提高軟件開發(fā)生產(chǎn)性能的方法的過程中,我們-作為Java社團(tuán)成員-一般都轉(zhuǎn)向J2EE來提供針對(duì)企業(yè)開發(fā)中更具挑戰(zhàn)性的技術(shù)問題如分布式事務(wù)管理、并發(fā)性和對(duì)象分布等的解決方案。其背后的指導(dǎo)思想-這些復(fù)雜的企業(yè)服務(wù)能被應(yīng)用程序服務(wù)器供應(yīng)商所實(shí)現(xiàn)并能為商業(yè)開發(fā)者所平衡-的確是一種很好的思想。J2EE,具體地說是EJB,已成功地提供了一個(gè)平臺(tái)-在其上構(gòu)建企業(yè)Java應(yīng)用程序。
這其中部分的成功是由于能夠進(jìn)行聲明性編程-一種程序開發(fā)方式-用這種方式,你可以聲明基礎(chǔ)結(jié)構(gòu)服務(wù)而不是用商業(yè)邏輯明確地編碼從而使代碼散布于各處。EJB已經(jīng)證明了這種編程方式的價(jià)值-通過允許企業(yè)問題例如事務(wù)和安全被用一種發(fā)布描述符所聲明并為容器所處理。
然而,在過去的歲月中,越來越多的開發(fā)者認(rèn)識(shí)到EJB在團(tuán)隊(duì)的生產(chǎn)效率方面給它自己帶來新的大量的挑戰(zhàn)-每個(gè)EJB必須伴隨多個(gè)接口,以一種發(fā)布描述符描述,經(jīng)由JNDI被存取,等等。而在容器外EJB上進(jìn)行單元測試也帶來另外的困難,如今EJB已不再把重點(diǎn)放在單純的面向?qū)ο箝_發(fā)上。
請(qǐng)注意,為閱讀本文您需具備如下工具:
·Java 2 SDK 1.5
·Maven 2.0 Beta 2
EJB 3.0的目標(biāo)在于從以下幾個(gè)方面使企業(yè)開發(fā)更為容易:
·通過引入元數(shù)據(jù)注解來實(shí)現(xiàn)聲明性請(qǐng)求企業(yè)服務(wù)
·經(jīng)由注解實(shí)現(xiàn)依賴性/資源注入
·實(shí)現(xiàn)企業(yè)beans與EJB特定接口的解耦
·經(jīng)由輕量級(jí)的對(duì)象關(guān)系映射實(shí)現(xiàn)持續(xù)性存儲(chǔ)的簡化
這對(duì)于EJB開發(fā)者來說尤如一股春風(fēng)-一直以來,他們竭力地從事開發(fā)、測試和維護(hù)EJB。利用EJB 3.0寫一個(gè)企業(yè)bean現(xiàn)在變得很容易,就如用特定的注解創(chuàng)建一個(gè)POJO(傳統(tǒng)的Java對(duì)象)以把它標(biāo)明為一個(gè)EJB并請(qǐng)求企業(yè)服務(wù)。下面是一個(gè)來自于EJB 3.0 Public Draft中EJB的例子:
@Stateful
public class CartBean implements ShoppingCart
{
private float total;
private Vector productCodes;
public int someShoppingMethod(){...};
...
}
本文沒有提供關(guān)于聲明性編程、EJBs、方面或注解的深度探索。相反,而只是分析一下這些技術(shù)之間的相互關(guān)系并討論如何把它們用一種新的方式結(jié)合起來以簡化應(yīng)用程序開發(fā)。
在本文中,你將會(huì)學(xué)習(xí)到如何編寫一個(gè)EJB 3.0兼容的bean并且通過創(chuàng)建幾個(gè)簡單的方面使其具有聲明性事務(wù)管理、安全和資源注入等功能。我希望您能從這個(gè)練習(xí)中得到以下的受益:
·學(xué)習(xí)方面的三個(gè)實(shí)際應(yīng)用(依賴性注入、安全和事務(wù))。
·熟悉EJB 3.0及其背后的思想。
·認(rèn)識(shí)到怎樣實(shí)現(xiàn)EJB與特定API的解耦以允許EJB 3.0兼容的服務(wù)能夠以輕量級(jí)實(shí)現(xiàn)而不是僅由EJB來提供。
二、 實(shí)例應(yīng)用程序-航班訂購
在整個(gè)后面的討論中,你將學(xué)習(xí)到一個(gè)航班訂購系統(tǒng)的實(shí)現(xiàn)-它使用方面和注解來實(shí)現(xiàn)依賴性注入、安全和事務(wù)管理。該應(yīng)用程序僅執(zhí)行兩項(xiàng)功能:它允許用戶搜索航班(圖1),然后訂購一次旅行(圖2)。這兩個(gè)操作都將被進(jìn)行安全處理以僅允許能被識(shí)別的用戶來執(zhí)行它們。另外,既然"訂購旅行"操作包含訂購兩個(gè)航班(外出和返回航班),那么需要把該操作創(chuàng)建為事務(wù)性的-如,兩個(gè)訂購將作為一個(gè)工作單元要么都成功要么都失敗。

圖1.航班查詢:首先,用戶查找滿足他們的指定標(biāo)準(zhǔn)的航班。

圖2.航班訂購:接下來,用戶訂購一個(gè)外出航班和一個(gè)返回航班。兩個(gè)訂購要么都成功要么都失敗。
這個(gè)簡單的Web應(yīng)用程序包含幾個(gè)servlet、一個(gè)服務(wù)外觀和一個(gè)DAO層(見圖3)。
資源配置、安全性和事務(wù)管理等橫切關(guān)注點(diǎn)將由方面(用AspectJ 1.5 M3實(shí)現(xiàn))所提供以實(shí)現(xiàn)在Java 5注解中所聲明的注入行為。

圖3.航班訂購系統(tǒng)架構(gòu):這個(gè)航班訂購系統(tǒng)包括三個(gè)主要組成組件-它們聯(lián)合起來共同完成用戶請(qǐng)求。
三、 資源注入
EJB 3.0草案聲明中允許資源經(jīng)由@Resource注解來聲明(這一決定定義在草案普通注解聲明中)并且被容器注入進(jìn)你的EJB。依賴性注入是一項(xiàng)技術(shù)-使用這種技術(shù),一個(gè)對(duì)象外部的實(shí)體而不是顯式地為該對(duì)象所創(chuàng)建的實(shí)體能夠提供(注入)一個(gè)對(duì)象的依賴性。它有時(shí)被描述為好萊塢原則-這開玩笑似地意味著"不要給我們打電話,我們會(huì)給你打電話的"。
以TravelAgencyServiceImpl類為例-這個(gè)類為了持續(xù)性存儲(chǔ)一些數(shù)據(jù)需要找到一個(gè)IFlightDAO接口的實(shí)現(xiàn)。傳統(tǒng)地,這是經(jīng)由一個(gè)工廠、singleton、服務(wù)定位器或一些另外的定制解決方案來實(shí)現(xiàn)的。其中,一個(gè)可能的解決方案看上去如下所示:
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);
}
}
你已看到,這個(gè)實(shí)現(xiàn)包含創(chuàng)建一個(gè)特定的工廠類-它很可能讀取存儲(chǔ)在某處的配置信息以了解要?jiǎng)?chuàng)建IFlightDAO的實(shí)現(xiàn)方式。如果不是讓服務(wù)顯式地創(chuàng)建它的由容器所注入的依賴性,那么配置細(xì)節(jié)和對(duì)象創(chuàng)建將被代理到容器上。這允許一個(gè)應(yīng)用程序中的組件能夠被容易地連接到一起-用不同的配置并且消除大量老式的singleton和工廠代碼。
該類的一個(gè)實(shí)現(xiàn)-它依賴于一個(gè)用JSR 250資源注解所聲明的IFlightDAO的實(shí)現(xiàn)-可能看上去如下所示:
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);
}
}
在這種情況下,容器將把一個(gè)命名為"flightDAO"的資源的正確實(shí)現(xiàn)提供給服務(wù)類。但是,如果你現(xiàn)在就想利用資源注入,而不是等待EJB 3.0發(fā)行版,又該如何呢?好,你可以采用一種輕量級(jí)的容器-它能夠提供例如Spring或Pico Container的依賴性注入。然而,當(dāng)前我還不了解存在一個(gè)輕量級(jí)的容器-它能夠使用JSR 250資源注解以指定注入要求(盡管我非常盼望在這一方面出現(xiàn)一些)。
一種解決方案是使用方面來實(shí)現(xiàn)依賴性注入。如果你為此使用@Resource注解,那么你的實(shí)現(xiàn)將與EJB 3.0方式一致并且向前兼容EJB 3.0實(shí)現(xiàn)-而實(shí)現(xiàn)這并不是很困難的事情。下列列表顯示用AspectJ創(chuàng)建的一個(gè)方面-它注入用@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);
}
}
這個(gè)簡單方面所做的全部是,從一個(gè)屬性文件(這個(gè)邏輯被封裝在DependencyManager對(duì)象中)查詢實(shí)現(xiàn)類并且在存取字段之前把它注入到用@Resource注解所注解的字段中。顯然,這種實(shí)現(xiàn)不是完整的,但是它確實(shí)說明了你可以怎樣以一種JSR 250兼容方式且不需采用EJB來提供資源注入。
四、 安全性
除了資源注入外,JSR 250和EJB 3.0還提供經(jīng)由注解的元數(shù)據(jù)安全表示。javax.annotation.security包定義了五個(gè)注解-RunAs,RolesAllowed,PermitAll,DenyAll和RolesReferenced-所有這些都能應(yīng)用到方法上來定義安全要求。例如,如果你想要聲明上面列出的bookFlight方法僅能為具有"user"角色的調(diào)用者所執(zhí)行,那么你可以用如下的安全約束來注解這個(gè)方法:
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);
}
}
這個(gè)注解將指出由容器來負(fù)責(zé)保證只有指定角色的調(diào)用者才能執(zhí)行這個(gè)方法。因此現(xiàn)在我將展示另一個(gè)簡單的方面-它將進(jìn)一步加強(qiáng)在該應(yīng)用程序上的安全約束:
@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)
{ ... }
}
這個(gè)方面包含了所有方法的執(zhí)行-通過核實(shí)該調(diào)用者是在注解中所指定的角色之一,用@RolesAllowed注解來注解并且保證調(diào)用者被授權(quán)調(diào)用該方法。當(dāng)然你還能代之以任何你喜歡的算法來授權(quán)用戶并且檢索他/她的角色,例如JAAS或一個(gè)定制的解決方案。在本示例程序中,為方便起見,我選擇代理到servlet容器。
五、 事務(wù)
事務(wù)成為企業(yè)開發(fā)的一個(gè)重要部分-因?yàn)樗鼈冇兄谠谝粋€(gè)并發(fā)的環(huán)境中的數(shù)據(jù)集成。從一個(gè)高層次上看,事務(wù)可以通過多種或者是完整的或者是都不完整的操作來保證這一點(diǎn)。
不象針對(duì)資源注入和安全的注解,針對(duì)事務(wù)的注解是特定于EJB 3.0的并且沒有在JSR 250普通注解中定義。EJB 3.0定義了兩個(gè)與事務(wù)相聯(lián)系的注解:TransactionManagement和TransactionAttribute。該TransactionManager注解指定事務(wù)是由容器所管理還是為bean所管理的。在EJB 3中,如果這個(gè)注解沒被指定,那么將使用容器所管理的事務(wù)。TransactionAttribute注解用于指定方法的事務(wù)傳播級(jí)別。有效值-包括強(qiáng)制的、要求的、要求新的、支持的、不支持的和從不支持的-用來定義是否要求一個(gè)已有事務(wù)或啟動(dòng)一個(gè)新的事務(wù),等等。
因?yàn)閎ookFlight操作包含兩步-訂購一個(gè)外出航班和一個(gè)返回航班,所以,通過把它包裝成一個(gè)事務(wù),你能保證這項(xiàng)操作的一致性。通過使用EJB 3.0事務(wù)注解,這將看上去如下所示:
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);
}
}
并且你可以應(yīng)用一個(gè)簡單的方面來自動(dòng)地界定事務(wù)邊界:
@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(); }
}
這個(gè)實(shí)現(xiàn)基于這樣的假設(shè)-Hibernate和無所不在的線程本地模式被用于管理Hibernate會(huì)話和事務(wù)對(duì)象;但是,任何其它適當(dāng)?shù)膶?shí)現(xiàn),例如基于JTA的實(shí)現(xiàn),都能被代替使用。
六、 小結(jié)
通過使用EJB 3.0和JSR 250注解集,本文已經(jīng)展示了例如資源管理、安全和事務(wù)等橫切關(guān)注點(diǎn)是怎樣被實(shí)現(xiàn)為方面的。當(dāng)然,還有許多內(nèi)容我們需進(jìn)一步學(xué)習(xí)。首先要學(xué)的就是通過使用AspectJ的實(shí)現(xiàn)這些示例方面為模塊化橫切關(guān)注點(diǎn)所提供的藍(lán)圖。其次,我們已經(jīng)看到了在如今正浮出水面的EJB 3.0聲明背后的一些新思想和新概念。最后,我們還以戲劇性的方式看到了從EJB API中解耦我們的商業(yè)對(duì)象所必須要提供的自由。在這一點(diǎn)上,所有你想使TravelAgencyServiceImpl成為一個(gè)無狀態(tài)會(huì)話要做的僅是添加一條最后的注解:
@Stateful
public class TravelAgencyServiceImpl implements ITravelAgencyService
{ ... }
最后,我非常希望這種自由地提供企業(yè)服務(wù)的方式會(huì)帶來框架/容器工業(yè)界的競爭和革新。
from: http://www.7dspace.com/doc/21/2005-11-21/2005112107135764791_1.htm
posted on 2005-12-24 21:15 weidagang2046 閱讀(177) 評(píng)論(0) 編輯 收藏 所屬分類: Java