程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 用EJB 3.0簡化企業Java開發(上)

用EJB 3.0簡化企業Java開發(上)

編輯:關於JAVA

對開發服務器端應用程序而言,Java企業版本即Java EE(以前叫J2EE)是一個功能強大、但又過於復雜的平台。很早以來,過於復雜歷來被認為是阻礙人們采用Java EE的一個重要因素。

但在過去的三年,Java開放源代碼社區、Java社區組織(JCP)以及主要的Java EE開發商都在致力於簡化Java EE。譬如說,實際的應用程序使用新的設計范例來簡化Java EE的開發,譬如普通Java對象(POJO)服務、服務攔截器和依賴注入。而諸多新的工具和框架也得到了廣泛采用,用於同樣的目的,譬如Hibernate、面向方面編程(AOP)、Struts、XDoclet和Spring。

這些模式和工具讓剛入門的開發人員更容易上手,同時提高了經驗豐富的Java開發人員的生產力,目前它們正在被JCP集成到下一代Java EE標准(即EJB 3.0)當中。Java開發人員Raghu Kodali最近開展的一項調查表明,把Sun的Java EE示例應用程序RosterApp從EJB 2.1移植到EJB 3.0可以減少50%以上的代碼。

Java注釋是EJB3.0的重要特性,它把POJO服務、POJO持久性和依賴注入聯系起來,成為完整的企業中間件解決方案。本文使用了一個示例應用程序:JBoss EJB 3.0 TrailBlazer,以演示開發添加注釋的輕便型EJB 3.0 POJO應用程序。TrailBlazer應用程序多次使用EJB 3.0中的不同工具和API,實現了一個投資計算器。示例應用程序在JBoss 應用服務器4.0.3裡面以非傳統方式運行,完全符合最新的EJB 3.0規范(公眾預覽版)。

EJB 3.0的注釋驅動編程模型

從開發人員的角度來看,EJB 3.0廣泛使用Java注釋。注釋有兩個重要優點:它們取代了過多的XML配置文件,而且不需要嚴格的組件模型。

注釋與XML

基於XML的部署描述符和注釋都可以用來配置Java EE應用程序中的服務相關屬性。兩者的區別在於:XML文件與代碼分開處理(往往在運行時);而注釋與代碼一起編譯,而且由編譯器進行檢查。這對開發人員產生了以下這些重要影響:

● 冗長性:XML配置文件以冗長出名。為了配置代碼,XML文件必須從代碼地方復制許多信息,譬如類名稱和方法名稱。另一方面,Java注釋卻是代碼的一部分,不需要另外引用代碼,就可以指定配置信息。

● 健壯性:XML配置文件中的復制代碼信息帶來了多個潛在故障點。譬如說,如果拼錯了XML文件中的方法名稱,應用程序會在運行時出錯。換句話說,XML配置文件不如注釋來得健壯。注釋可以由編譯器來檢查,同代碼的其余部分一起處理。

● 靈活性:因為XML文件與代碼分開處理,所以基於XML的配置信息不是“硬編碼”的,以後可以改動。部署時間的靈活性對系統管理員來說是一項很好的特性。

注釋使用簡單,足以滿足大多數應用程序的要求。XML文件比較復雜,可用來處理更高級的問題。EJB 3.0允許通過注釋來配置大多數應用程序的設置。EJB 3.0還支持XML文件用於取消默認的注釋值、配置外部資源(如數據庫連接)。

POJO與嚴格組件

除了取代及簡化XML描述符外,注釋還可以讓我們棄用曾困擾EJB 1.x和EJB 2.x的嚴格的組件模型。

EJB 組件是容器管理的對象。容器在運行時操縱bean實例的行為和內部狀態。為了讓這種行為出現,EJB 2.1規范定義了bean必須遵守的嚴格的組件模型。每個EJB類必須從為容器提供回調鉤子(callback hook)的某個抽象類繼承而來。因為Java只支持單一繼承,嚴格的組件模型就限制了開發人員使用EJB組件創建復雜對象結構的能力。讀者會在本文下篇分看到,如果映射實體bean中復雜的應用程序數據,這更是個問題。

在EJB 3.0中,所有容器服務都可以通過注釋進行配置,並提供給應用程序裡面的任何POJO。大多數情況下,不需要特殊的組件類。

開發松散耦合的服務對象

Java EE等企業中間件的最重要的好處之一就是,讓開發人員可以使用松散耦合的組件來開發應用程序。這些組件僅僅通過已發布的業務接口來進行耦合。因此,可在不改變應用程序其余部分的情況下,改變組件實現類。這樣使應用程序更健壯、更容易測試,而且更容易移植。EJB 3.0簡化了在POJO中構建松散耦合的業務組件。

會話bean

在EJB 3.0應用程序中,松散耦合的服務組件通常作為會話bean來實現。會話bean要有一個接口(即業務接口),那樣其他應用程序的組件就可以通過它使用其服務。下面的代碼為我們的示例投資計算器服務提供了業務接口。根據投資者開始投資時及終止投資時的年齡、基金增長率及每月儲蓄額,它只有一個方法來計算總的投資回報。

public interface Calculator {

public double calculate (int start, int end, double growthrate, double saving); }

會話bean類僅僅實現了業務接口。必須通過為其添加無狀態或者有狀態的注釋,告訴EJB 3.0容器這個POJO類是會話bean。有狀態的會話bean可以在幾個不同的服務請求期間保持客戶端狀態。與之相反,無狀態的會話bean的請求每次都是由隨機的會話bean實例來處理。其行為與原來EJB 2.1中的有狀態和無狀態的會話bean的行為相一致。EJB 3.0容器計算出什麼時候為bean對象創建實例,然後通過業務接口來提供。下面是會話bean實現類的代碼:

@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; }

}

還可以為一個會話bean指定多個接口-一個用於本地客戶端,一個用於遠程客戶端。只要使用@Local和@Remote注釋,就可以區別接口。下面的代碼片斷顯示了CalculatorBean會話bean同時實現了本地接口和遠程接口。如果你沒有@Local和@Remote注釋,會話bean接口就是默認的本地接口。

@Stateless

@Local ({Calculator.class})

@Remote ({RemoteCalculator.class})

public class CalculatorBean implements Calculator, RemoteCalculator {

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; }

public String getServerInfo () {

return "This is the JBoss EJB 3.0 TrailBlazer"; }

}

會話bean用戶通過Java命令和目錄接口(JNDI)得到bean的存根對象。由容器提供的存根對象實現了會話bean的業務接口。針對存根對象的所有調用都被轉向容器,並針對可管理的bean實例進行調用。至於無狀態的會話bean,每次進行調用時,都能獲得新的存根對象。至於有狀態的會話bean,必須把存根對象緩存在客戶端上,那樣容器就知道以後每次調用時為你提供相同的的bean實例。下面的代碼片斷顯示如何調用會話bean。這裡介紹獲得bean存根對象的一種更簡單的方法。

InitialContext ctx = new InitialContext();

cal = (Calculator) ctx.lookup(Calculator.class.getName());

double res = cal.calculate(start, end, growthrate, saving);

會話bean的生命周期管理

為了實現松散耦合,應用程序把會話bean實例的創建、緩存、銷毀全部交給EJB 3.0容器(即反向控制設計模式)。而應用程序只處理業務接口。

但如果應用程序需要對會話對象實行粒度更細的控制,該如何呢?譬如說,應用程序可能需要在容器創建會話bean時執行數據庫初始化,或者在銷毀bean時需要關閉外部連接。只要在bean類中實現生命周期回調方法,就能實現這些操作。這些方法由容器在bean生命周期的不同階段(如bean創建和銷毀)進行調用。在EJB 3.0中,可以指定任何bean方法作為回調,只要為其添加下列注釋。不像EJB 2.1裡面,所有的回調方法都必須加以實現,即便回調方法是空的;EJB 3.0 bean可以有好多回調方法,可以是任何方法名稱。

● @PostConstruct:bean實例創建後,容器立即調用添加了注釋的方法。這個注釋同時適用於有狀態和無狀態的會話bean。

● @PreDestroy:容器從對象池當中銷毀閒置或者過期的bean實例之前,調用添加了注釋的方法。這個注釋同時適用於有狀態和無狀態的會話bean。

● @PrePassivate:如果某個有狀態的會話bean實例閒置時間過長,容器就會將它掛起(passivate),並把其狀態保存在緩存當中。容器將bean實例掛起之前,調用由這個注釋作以標記的方法。這個注釋適用於有狀態的會話bean。

● @PostActivate:如果客戶端再次使用已被掛起的的有狀態的會話bean時,新的實例被創建,bean狀態被恢復。如果被激活的bean實例准備就緒,就調用由該注釋作以標記的方法。這個注釋只適用於有狀態的會話bean。

● @Init:這個注釋為有狀態的會話bean指定了初始化方法。它有別於@PostConstruct注釋之處在於:在有狀態的會話bean中,可以用@Init對多個方法作以標記。不過,每個bean實例只能有一個@Init方法被調用。EJB 3.0容器決定調用哪個@Init方法,具體取決於bean是如何創建的。@PostConstruct方法在@Init方法之後被調用。

生命周期方法的另一個有用注釋是@Remove,對有狀態的會話bean來說更是如此。應用程序通過存根對象調用使用@Remove標注的方法時,容器就知道在該方法執行完畢後,把bean實例從對象池當中移走。下面是這些生命周期方法注釋在CalculatorBean中的一個示例:

@Stateful

public class CalculatorBean implements Calculator, Serializable {

@PostConstruct

public void initialize () {

//初始化歷史記錄,並從數據庫中裝入必要數據。 }

@PreDestroy

public void exit () {

// 若有必要,把歷史記錄保存至數據庫中 }

@Remove

public void stopSession () {

// 調用該方法以通知容器,移除該bean實例、終止會話。方法體可以是空的。}

}

消息驅動的bean

會話bean服務通過同步方法調用來提供。另一種重要的松散耦合的服務就是,由入站消息觸發的異步服務,入站消息包括電子郵件或者Java消息服務(JMS)消息。EJB 3.0消息驅動的bean(MDB)是為了處理基於消息的服務請求而設計的組件。

MDB類必須實現消息監聽器(MessageListener)接口。當容器檢測到該bean的消息後,就調用onMessage()方法,並把入站消息作為調用參數傳遞。MDB會決定在OnMessage()方法中如何處理消息。可以用注釋來配置這個MDB監控哪些消息隊列。MDB部署後,容器使用注釋裡面指定的配置信息。在下面的示例中,當容器檢測到queue/mdb JMS隊列中的入站消息後,就會調用CalculatorBean MDB。MDB會解析消息,並根據消息內容執行投資計算。

@MessageDriven(activateConfig =

{

@ActivationConfigProperty(propertyName="destinationType",

ropertyValue="Javax.jms.Queue"),

@ActivationConfigProperty(propertyName="destination", propertyValue="queue/mdb")

})

public class CalculatorBean implements MessageListener {

public void onMessage (Message msg) {

try {

TextMessage tmsg = (TextMessage) msg;

Timestamp sent = new Timestamp(tmsg.getLongProperty("sent"));

StringTokenizer st = new StringTokenizer(tmsg.getText(), ",");

int start = Integer.parseInt(st.nextToken());

int end = Integer.parseInt(st.nextToken());

double growthrate = Double.parseDouble(st.nextToken());

double saving = Double.parseDouble(st.nextToken());

double result = calculate (start, end, growthrate, saving);

RecordManager.addRecord (sent, result);

} catch (Exception e) {

e.printStackTrace (); }

}

}

依賴注入

在前面一節中,介紹了如何開發松散耦合的服務組件。然而,為了使用這些服務對象,你需要通過服務器的JNDI來查詢存根對象(用於會話bean)或者消息隊列(用於MDB)。JNDI查詢是把客戶端從實際實現的服務對象解除耦合的一個關鍵步驟。不過,基於字符串名的普通JNDI查詢並不方便。以下是幾個原因:

● 客戶端與服務端必須就基於字符串的名字達成一致。這不是由編譯器或者任何部署時間檢查所執行的契約。

● 已獲取的服務對象在編譯時不進行檢查,可能會導致運行時出現數據類型轉換錯誤(casting error)。

● 應用程序裡面一再出現冗長的查詢代碼,該代碼有自己的try-catch代碼塊。

EJB 3.0采用了一種簡單、便利的方法,把解除耦合的服務對象和資源提供給任何POJO使用。你使用@EJB注釋,就可以把EJB存根對象注入到EJB 3.0容器管理的任何POJO中。如果對某字段變量標以注釋,容器會在第一次訪問之前,為該變量賦予正確的值。下面的示例顯示了如何把CalculatorBean無狀態會話bean的存根對象注入到CalculatorMDB MDB類中。

public class CalculatorMDB implements MessageListener {

@EJB Calculator cal;

// 使用cal變量

// ... ... }

如果對某個屬性的JavaBean風格的設置方法標以注釋,屬性第一次使用之前,容器會自動用正確的參數調用屬性設置方法。下面的代碼片斷演示了工作過程:

public class CalculatorMDB implements MessageListener {

Calculator cal;

 @EJB

 public void setCal (Calculator cal) {

this.cal = cal; }

// 使用cal變量

 // ... ... }

除@EJB注釋外,EJB 3.0還支持@Resource注釋注入來自JNDI的任何資源。下面的例子演示了如何注入服務器的默認的TimerService和SessionContext對象,並且演示了如何注入來自JNDI的命名數據庫和JMS資源。

@Resource

TimerService tms;

@Resource

SessionContext ctx;

@Resource (name="DefaultDS")

DataSource myDb;

@Resource (name="ConnectionFactory")

QueueConnectionFactory factory;

@Resource (name="queue/A")

Queue queue;

此外,你還可以把容器管理的持久性管理器(即實體管理器――類似Hibernate會話對象)注入到EJB 3.0 POJO中。

為POJO提供容器服務

除了管理松散耦合的服務對象的生命周期和訪問外,EJB 3.0容器還通過簡單的注釋為可管理的POJO提供運行時服務。

事務

最有用的容器服務可能就是事務服務:萬一應用程序出現錯誤或者異常,它可以保證數據庫的完整性。你只要為POJO方法添加注釋,即可聲明事務屬性。容器可以在適當的事務上下文中運行方法。譬如說,下面的代碼聲明:容器應當創建新的事務來運行updateExchangeRate()方法。如果該方法存在,事務就提交。實際上,從updateExchangeRate()裡面被調用的所有方法也都在同樣的事務上下文中執行,除非以其他方式進行顯示聲明。updateExchangeRate()方法中執行的數據庫操作要麼全部成功,要麼全部失敗。

@Stateless

public class CalculatorBean implements Calculator {

// ... ...

@TransactionAttribute(TransactionAttributeType.REQUIRED)

public void updateExchangeRate (double newrate) throws Exception {

// 在循環中更新數據庫。

// ... ...

//循環中的操作必須全部成功,否則數據庫根本不更新。 }

}

安全

容器還能提供驗證用戶身份的安全服務,並且可以根據用戶角色,限制對可管理的POJO的訪問。對每個POJO類而言,你可以使用@SecurityDomain注釋指定安全域,它能告訴容器到哪裡去找密碼和用戶角色列表。JBoss裡面的other域表明文件是類路徑中的users.propertes和roles.propertIEs文件。然後,對於每個方法,你可以使用安全限制注釋來指定誰可以運行這個方法。譬如在下面例子中,容器對所有試圖執行addFund()方法的用戶進行驗證,只允許角色是AdminUser的用戶才能實際運行。如果你沒有登錄,或者不是以管理員的身份登錄,就會引發安全異常。

@Stateless

@SecurityDomain("other")

public class CalculatorBean implements Calculator {

@RolesAllowed({"AdminUser"})

public void addFund (String name, double growthrate) {

// ... ... }

@RolesAllowed({"AdminUser"})

public void addInvestor (String name, int start, int end) {

// ... ... }

@PermitAll

  public Collection getFunds () {

// ... ... }

// ... ...

@RolesAllowed({"RegularUser"})

public double calculate (int fundId, int investorId, double saving) {

// ... ... }

}

通用攔截器

事務服務和安全服務都可以被看成是由容器管理的運行時攔截器。容器攔截來自EJB存根對象的方法調用後,為調用添加事務上下文或者安全限制。

在EJB 3.0中,你可以自己編寫攔截器來擴展容器服務。使用@AroundInvoke注釋,就可以把任何bean方法指定為在其他任何bean方法運行前後執行的攔截器方法。在下面例子中,log()方法是分析及記錄其他bean方法的執行時間的攔截器:

@Stateful

public class CalculatorBean implements Calculator {

//被“log()”攔截的bean方法

// ... ...

@AroundInvoke

public Object log (InvocationContext ctx) throws Exception {

String className = ctx.getBean().getClass().getName();

String methodName = ctx.getMethod().getName();

String target = className + "." + methodName + "()";

long start = System.currentTimeMillis();

System.out.println ("Invoking " + target);

try {

return ctx.proceed();

} catch(Exception e) {

throw e;

} finally {

System.out.println("Exiting " + target);

cal.setTrace(cal.getTrace() + "" +"Exiting " + target);

long time = System.currentTimeMillis() - start;

System.out.println("This method takes " + time + "ms to execute");

}

}

}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved