來源:http://java.chinaitlab.com/EJB/743927.html
在過去的三年中,Java開放源代碼社區(qū),Java社區(qū)進程(JCP)以及主要的Java EE供應商,一直致力于讓Java EE更簡單。舉例來說:新的設計范例,比如POJO服務,服務攔截器和依賴注入,已經(jīng)可以在實際應用中用來簡化Java EE的開發(fā)了。還有,新的工具和框架,比如Hibernate, AOP(aspect-oriented programming,面向方面編程),Struts,Xdoclet和Spring, 也已經(jīng)被廣泛用于同一目的。
簡化不是功能的減少
簡化一個編程模型并沒有減少它的功能。簡化只是把復雜的邏輯隱藏到了框架代碼或可重用的組件中去了。根本上,它是把復雜的東西從需要應用開發(fā)者直接管理的地方轉(zhuǎn)移到了大多數(shù)開發(fā)者看不到的地方。
上述的模板和工具讓初學者更容易上手,同時也提高了有經(jīng)驗的Java開發(fā)者的生產(chǎn)力,現(xiàn)在它們正在被JCP合并到下一代的Java EE標準中(比如:EJB 3.0)。由Java開發(fā)人員Raghu Kodali最近所做的研究顯示:將Java EE的示例程序RosterApp從EJB 2.1轉(zhuǎn)到EJB 3.0可以減少百分之五十以上的代碼。
Java注釋是EJB3.0背后的關鍵,它將POJO服務,POJO持久化和依賴注入一起綁定為一個完整的企業(yè)級中間件解決方案。這篇文章中,我使用了一個示例應用:JBoss EJB 3.0 TrailBlazer,來演示使用注釋開發(fā)輕量級的EJB 3.0 POJO應用。TrailBlazer的應用使用EJB 3.0中不同的工具和API重復實現(xiàn)了一個投資計算器。示例程序完全可以在JBoss 應用服務器4.0.3版本中運行,并且與最新的EJB 3.0標準完全兼容(完成時)。
讓我們來開始體驗一下注釋驅(qū)動編程模型的好處吧。
EJB 3.0的注釋驅(qū)動編程模型
從開發(fā)者的觀點來看,EJB 3.0廣泛地使用了Java 注釋.注釋有兩個關鍵優(yōu)勢:它們?nèi)〈诉^多的XML配置文件并且消除了嚴格組件模型需求。
注釋 vs XML
基于XML的布署描述和注釋一起都可以用來在Java EE應用中配置服務的相關屬性。它們的區(qū)別在于:XML文檔是與代碼分開處理的,特別是在運行時刻,而注釋是與代碼編譯在一起的并被編譯器檢查的。對于開發(fā)者來說這就有了一些重要的含義,正如我下面所列出的:
冗長:XML配置文件是出了名的冗長的。為了配置代碼,XML文件必須復制許多信息:比如代碼中類名字和方法名字。Java注釋則不同,它是代碼的一部分,不需要額外的引用就可以指明配置信息。
強壯性:在XML文件中重復的代碼信息引入了多處齟淼目贍堋1熱?如果你寫錯了方法的名字,那應用直到運行時刻才會出錯垮掉。也就是說,XML配置文件的強壯性就不如注釋,注釋是被編譯器檢查的,并和其它代碼一起被處理的。
靈活性:既然XML文件是在代碼之外被單獨處理的,那也就是說基于XML的配置信息不是“硬編碼”的,是可以以后修改的。部署的靈活性對系統(tǒng)管理員來說是非常非常重要的特性。
注釋是簡單易用的,已證明對大多數(shù)應用來說足夠了。XML文件更復雜,但能被用來處理更高級的問題。EJB 3.0允許你通過注釋來配置大多數(shù)的應用。EJB 3.0也支持用XML文件來覆蓋默認的注釋,及配置像數(shù)據(jù)庫聯(lián)接這樣的外部資源。
除了替換和簡化XML描述符,注釋也允許我們廢除困擾EJB 1.x, EJB 2.x的嚴格組件模型。
POJO vs 嚴格組件
EJB 組件是容器管理(container-managed)的對象。容器在運行時刻操作Bean的狀態(tài)和行為。為了讓行為發(fā)生,EJB 2.1規(guī)范定義了一個Bean必須遵守的嚴格的組件模型。每一個EJB類必須從某一種抽象類中繼承,并為容器提供了回調(diào)的鉤子。既然Java只支持單繼承,嚴格組件模型就限制了開發(fā)者使用EJB組件創(chuàng)建一個復雜對象結(jié)構(gòu)的能力。當把復雜的應用數(shù)據(jù)映射到實體 Bean中的時候,正如我們在第二部分中看到的,這會成為一個很大的問題。
在EJB 3.0中,所有的容器服務都可以通過使用注釋的POJO應用來配置和交付。大多數(shù)情況下,并不需要特殊的組件類。讓我們通過JBoss EJB 3.0 TrailBlazer示例看一下如何在EJB 3.0中使用注釋。
開發(fā)藕合松散的服務對象
像Java EE這樣的企業(yè)級中間件的一個最重要的好處是允許開發(fā)者使用藕合松散的組件來開發(fā)應用。這些組件僅僅通過他們自己發(fā)布的商業(yè)接口來藕合。因此這些組件的實現(xiàn)類可以在不改變應用其余部分的情況下改變自己的實現(xiàn)。這將會使應用更加強壯,更容易測試,更易移植。EJB 3.0使得在POJO中創(chuàng)建藕合松散的商業(yè)組件變得更簡單了。
Session bean
在EJB 3.0應用中,藕合松散的服務組件的典型應用是Session Bean。一個Session Bean至少有一個接口(也就是:商業(yè)接口),其它應用組件通過它獲得服務。下面的代碼為我們的投資計算器服務提供了商業(yè)接口。它只有一個方法,根據(jù)給定的起始年齡,終止年齡,增長率,月存金額,計算出總投資額。
public interface Calculator {
public double calculate (int start, int end,
double growthrate, double saving);
}
Session bean類簡單地實現(xiàn)了商業(yè)接口。你必須通過使用Stateless或Stateful注釋來告訴EJB 3.0容器這個POJO類是一個Session Bean。有狀態(tài)(Stateful)的session bean在不同的服務請求間維護著客戶的狀態(tài)。相反地,對于無狀態(tài)(Stateless)的session bean,每次的請求都是被隨機挑選的session bean實例處理的。這些行為是與EJB 2.1規(guī)范中的有狀態(tài)和無狀態(tài)session bean的定義是一致的。EJB 3.0容器算出何時實例化Bean對象,并通過商業(yè)接口讓其可用。下面是session bean實現(xiàn)類的代碼:
@Stateless public class CalculatorBean implements Calculator { public double calculate (int start, int end, double growthrate, double saving) { double tmp = Math.pow(1. + growthrate / 12., 12. * (end - start) + 1); return saving * 12. * (tmp - 1) / growthrate; } } |
你也可以為一個session bean指明多個接口-一個為本地客戶服務,一個為遠程客戶服務。只要使用@Local和@Remote注釋來區(qū)分。下面的代碼片斷顯示了同時實現(xiàn)了本地和遠程接口的CalculatorBean。如果你沒有@Local和@Remote注釋,session bean接口默認為本地接口。
@Stateless @Local () @Remote () public class CalculatorBean implements Calculator, RemoteCalculator { public double calculate (int start, int end, public String getServerInfo () { |
Session bean用戶通過JNDI得到bean的一個存根(Stub)對象。容器所提供的存根對象實現(xiàn)了session bean的商業(yè)接口。所有針對存根的調(diào)用都被引向了容器,由容器調(diào)用相應的實現(xiàn)類中的接口。對于有狀態(tài)的的session bean,你必須自己在客戶端緩存存根對象,這樣在每次的后續(xù)調(diào)用時,容器才知道要提供相同的的bean實例。下面的片斷顯示如何調(diào)用session bean.在后面,你將會學到獲取存根對象的更簡單的方法。
InitialContext ctx = new InitialContext(); cal = (Calculator) ctx.lookup(Calculator.class.getName()); double res = cal.calculate(start, end, growthrate, saving); Session bean生命周期的管理 |
為達到藕合松散的目的,應用把session bean實例的創(chuàng)建、緩存、銷毀全部交給EJB 3.0容器(也就是,反向控制設計模式)。應用只和bean的商業(yè)接口打交道。
但如果應用需要對session對象更好的控制呢?比如說,應用可能需要在創(chuàng)建session bean的時候初始化數(shù)據(jù)庫聯(lián)接,而在銷毀bean時關閉外部的聯(lián)接。上述這些,你都可能通過在bean類中定義生命周期的回調(diào)方法來實現(xiàn)。這些方法將會被容器在生命周期的不同階段調(diào)用(如:創(chuàng)建或銷毀時)。通過使有下面所列的注釋,EJB 3.0允許你將任何方法指定為回調(diào)方法。這不同于EJB 2.1,EJB 2.1中,所有的回調(diào)方法必須實現(xiàn),即使這是空的。EJB 3.0中,bean可以有任意數(shù)量,任意名字的回調(diào)方法。
@PostConstruct:當bean對象完成實例化后,使用了這個注釋的方法會被立即調(diào)用。這個注釋同時適用于有狀態(tài)和無狀態(tài)的session bean。
@PreDestroy:使用這個注釋的方法會在容器從它的對象池中銷毀一個無用的或者過期的bean實例這前調(diào)用。同時適用于有狀態(tài)和無狀態(tài)的session bean.
@PrePassivate:當一個有狀態(tài)的session bean實例空閑過長的時間,容器將會鈍化它,并把它的狀態(tài)保存下來。使用這個注釋的方法會在容器鈍化bean實例之前調(diào)用。適用于有狀態(tài)session bean。
@PostActivate:當客戶端再次使用已經(jīng)被鈍化的的有狀態(tài)session bean時,新的實例被創(chuàng)建,狀態(tài)被恢復。使用此注釋的session bean會在bean的激活完成時調(diào)用。
@Init:這個注釋指定了有狀態(tài)session bean初始化的方法。它區(qū)別于@PostConstruct注釋在于:多個@Init注釋方法可以同時存在于有狀態(tài)session bean 中,但每個bean實例只會有一個@Init注釋的方法會被調(diào)用。這取決于bean是如何創(chuàng)建的(細節(jié)請看EJB 3.0規(guī)范)。@PostConstruct在@Init之后被調(diào)用。
另一個有用的生命周期方法注釋是@Remove,特別是對于有狀態(tài)session bean。當應用通過存根對象調(diào)用使用了@Remove注釋的方法時,容器就知道在該方法執(zhí)行完畢后,要把bean實例從對象池中移走。
@Stateful public class CalculatorBean implements Calculator, Serializable { // ... ... |
消息驅(qū)動bean
Session bean服務提供了同步調(diào)用的方法。另一個重要的藕合松散服務類型是一種通過進入的消息來觸發(fā)的異步服務(比如:email或Java消息服務產(chǎn)生的消息)。EJB 3.0的消息驅(qū)動bean(MDB)是設計用來專門處理基于消息請求的組件。
一個MDB類必須實現(xiàn)MessageListener接口。當容器檢測到bean守候的隊列一條消息時,就調(diào)用onMessage()方法,將消息作為參數(shù)傳入。MDB在OnMessage()中決定如何處理該消息。你可以用注釋來配置MDB偵聽哪一條隊列。當MDB部署時,容器將會用到其中的注釋信息。在下面的例子中,CalculatorBean MDB會在JMS隊列queue/mdb有消息進入時調(diào)用。MDB解析消息,并根據(jù)消息內(nèi)容計算投資。
@MessageDriven(activateConfig = { @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"), @ActivationConfigProperty(propertyName="destination", propertyValue="queue/mdb") }) public class CalculatorBean implements MessageListener { public void onMessage (Message msg) { int start = Integer.parseInt(st.nextToken()); double result = } catch (Exception e) { // ... ... |
依賴注入
在上一節(jié)中,你學到了如何開發(fā)藕合松散的服務組件。但是,為了存取那些服務對象,你需要通過服務器的JNDI來查找存根對象(session bean)或消息隊列(MDB)。JNDI查找是把客戶端與實際的服務端實現(xiàn)解藕的關鍵步驟。但是,直接使用一個字符串來進行JNDI查找并不優(yōu)雅。有這樣幾個原因:客戶端與服務端必須有一致的基于字符串的名字。它沒有在編譯時得到認證或在布署時得到檢查。
從JNDI返回的服務對象的類型沒有在編譯時進行檢查,有可能在運行時出現(xiàn)轉(zhuǎn)換(casting)錯誤。
冗長的查找代碼,有著自己的try-catch代碼塊,在應用之間是重復的和雜亂的
EJB 3.0,對任何POJO,提供了一個簡單的和優(yōu)雅的方法來解藕服務對象和資源。使用@EJB注釋,你可以將EJB存根對象注入到任何EJB 3.0容器管理的POJO中。如果注釋用在一個屬性變量上,容器將會在它被第一次訪問之前賦值給它正確的值。下面的例了演示了怎樣把CalculatorBean無狀態(tài)session bean的存根注入到CalculatorMDB MDB類中。
public class CalculatorMDB implements MessageListener {
@EJB Calculator cal;
// Use the cal variable
// ... ...
}
注釋如果被用在JavaBean風格的setter方法上時,容器會在屬性第一次使用之前,自動地用正確的參數(shù)調(diào)用bean的setter方法。下面的片斷演示了這是如何做的:
public class CalculatorMDB implements MessageListener {
Calculator cal; |
除@EJB注釋之外,EJB 3.0也支持@Resource注釋來注入來自JNDI的任何資源。下面的例子中,我演示了如何注入服務器端默入的TimerService和SessionContext對象,也演示了如何注入來自JNDI的命名數(shù)據(jù)庫和JMS資源。
@Resource TimerService tms; @Resource @Resource (name="DefaultDS") @Resource (name="ConnectionFactory") @Resource (name="queue/A") |
此外,你也可以把一個容器管理的持久化管理器(也就是,EntityManager-類似于Hibernate session對象)注入到EJB 3.0 POJO中。
把容器服務交給POJO
除了管理生命周期和訪問藕合松散的服務對象外,EJB 3.0通過簡單的注釋也為POJO提供了運行時刻服務。
事務
最有用的容器服務可能就是事務管理服務,當應用出現(xiàn)失敗或異常時,它保證了數(shù)據(jù)庫的完整性。你可以簡單地將為一個POJO方法申明它的事務屬性。這樣容器就可以在合適的上下文中運行這個方法。舉例來說,下面的代碼申明了容器在運行updateExchangeRate()時必須創(chuàng)建一個新的事務。當這個方法退出時提交事務。實際上,所有在updateExchangeRate()中被調(diào)用的方法都在此事務中運行,除非有特別申明。在updateExchangeRate()中的數(shù)據(jù)庫操作要么全部成功,要么全部失敗。
@Stateless public class CalculatorBean implements Calculator { // ... ... @TransactionAttribute(TransactionAttributeType.REQUIRED) |
容器也提供了安全服務來進行用戶認證和根據(jù)用戶規(guī)則來限制對POJO的訪問。對每一個POJO來說,你可以通過使用@SecurityDomain注釋為它指定一個安全域, 安全域告訴容器到哪里去找密碼和用戶角色列表。JBoss中的other域表明文件是classpath中的users.propertes和roles.properties。這樣,對每一個方法來說,我們可以使用一個安全限制注釋來指定誰可以運行這個方法。比如,下面的例子,容器對所有試圖調(diào)用addFund()的用戶進行認證,只允許擁有AdminUser角色的用戶實際運行它。如果你沒有登錄或者沒有以管理員的身份登錄,一個安全意外將會拋出。
@Stateless @SecurityDomain("other") public class CalculatorBean implements Calculator { @RolesAllowed() @RolesAllowed() @PermitAll @RolesAllowed() |
通用攔截器
事務和安全服務都可以被看作是容器管理的運行時刻攔截器。容器攔截了對EJB存根的調(diào)用,并在其上應用事務上下文或進行安全限制。
在EJB 3.0中,你可以自己寫攔截器來擴展容器服務。使用@AroundInvoke注釋,你可以將任意bean方法作為攔截器方法在任意bean方法之前和之后運行。下面的例子中,log()方法是一個攔截器,它計算和記錄了其它bean方法的執(zhí)行時間:
@Stateful public class CalculatorBean implements Calculator { // Bean methods that are to be intercepted by "log()" String className = ctx.getBean().getClass().getName(); long start = System.currentTimeMillis(); cal.setTrace(cal.getTrace() + " |