2007年9月29日
#
request.getRealPath("url");//虛擬目錄映射為實際目錄
request.getRealPath("./");//網頁所在的目錄
request.getRealPath("../");//網頁所在目錄的上一層目錄
request.getContextPath();//應用的web目錄的名稱
如
http://localhost:7001/bookStore/
/bookStore/ => [contextPath] (request.getContextPath())
獲取Web項目的全路徑
String strDirPath = request.getSession().getServletContext().getRealPath("/");
XDoclet起步
XDoclet是一個代碼生成工具,它可以把你從Java開發過程中繁重的重復勞動中解脫出來。XDoclet可以讓你的應用系統開發的更加快速,而你只要付比原先更少的努力。你可以把你手頭上的冗長而又必需的代碼交給它幫你完成,你可以逃脫“deployment descriptor地獄”,你還可以使你的應用系統更加易于管理。而你所要做的,只不過是在你的注釋里,多加一些類javadoc屬性。然后,你會驚訝于XDoclet為了做到的一切。
討論XDoclet,有一點比較容易產生混淆,那就是XDoclet不但是一系統的代碼生成應用程序,而且它本身還是一個代碼生成框架。雖然每個應用系統的細節千變萬化(比如EJB代碼生成和Struts代碼生成是不一樣的,而JMX代碼生成又是另一番景象),但這些代碼生成的核心概念和用法卻是類似的。
在這一章里,我們將會看到滲透到所有XDoclet代碼生成程序當中的XDoclet框架基礎概念。但在之前,我們先從一個例子入手。
2.1 XDoclet in action
每一個程序員都會認識到,他們的程序永遠也不會完成。總會有另一些的功能需要添加,另一些的BUG需要修正,或者需要不斷的進行重構。所以,在代碼里添加注釋,提醒自己(或者其他的程序員)有哪些任務需要完成已成為一個共識。
如何來跟蹤這些任務是否完成了呢?理想情況下,你會收集整理出來一個TODO任務列表。在這方面,XDoclet提供了一個強大的TODO生成器,來幫助你完成這個任務。這是一個把XDoclet引入項目的好機會。
2.1.1 一個公共的任務
假設你正在開發一個使用了勺子的類。
public class Matrix {
// TODO ? 需要處理當沒有勺子的情況
public void reload() {
// ...
Spoon spoon = getSpoon();
// ...
}
}
理想情況下,你在下一次閱讀這段代碼的時候,你會處理這個“空勺子”(null spoon)的問題。但如果你過了很久才回來看這段代碼,你還會記得在這個類里還有一些工作要做嗎?當然,你可以在你的源碼里全局搜索TODO,甚至你的集成開發環境有一個內建的TODO列表支持。但如果你想把任務所在的類和方法也標注出來的話,XDoclet可以是另一種選擇。XDoclet可以為你的項目生成一個TODO報表。
2.1.2 添加XDoclet標簽
為了把你的TODO項目轉換成另一種更加正式的格式,你需要對代碼進行一些細微的改動。如下所示:
public class Matrix {
/** @todo 需要處理當沒有勺子的情況 */
public void reload() {
// ...
}
}
這里加入了一個XDoclet需要的類javadoc標簽。XDoclet會使用這些標簽標記的信息,以及在這種情況下標簽所處的類和方法,來生成TODO報表。
2.1.3 與Ant集成
要生成TODO報表,你需要確保在你的機器上正確安裝了XDoclet。
在Ant任務里,最少要包含一個目標(例如init目標)定義<documentdoclet>任務,這是一個Ant自定義任務,例如:
<taskdef name=”documentdoclet”
classname=”xdoclet.modules.doc.DocumentDocletTask”
classname=”xdoclet.lib.path” />
這個<documentdoclet>任務是XDoclet核心代碼生成應用程序中的一個。
現在,你可以在Ant構建文件中加入一個todo目標調用這個任務來生成TODO報表,如:
<target name=”todo” depends=”init”>
<documentdoclet destdir=”todo”>
<fileset dir=”${dir.src}”>
<include name=”**/*.java” />
</fileset>
<info/>
</documentdoclet>
</target>
<info>子任務會遍歷你的源文件,查找todo標簽,并在todo子目錄下生成HTML格式的TODO報表。
2.1.4 創建一個更加職業化的TODO報表
XDoclet生成的TODO報表可以有一個更加職業化的外表。報表會列出一個概覽,顯示在哪個包哪個類里有todo項(以及todo項的個數)。Todo項可以跟在方法、類和域上,從報表上可以清楚的區別它們。類級別的todo項會標注class,方法級別的todo項會在方法簽名上標注M。構造函數和域相關的todo項也會進行相似的標注。
這個任務看起來很簡單,但考慮到你所需要做的只是在注釋上添加一些格式化的@todo標簽,相對于那種只有人才可以理解的無格式的松散的注釋,這種標簽是機器可讀的,也更容易編程處理。生成的輸出也更容易閱讀并且更加的商業化。
2.2 任務和子任務
生成todo報表,只是XDoclet可以完成的事情當中的冰山一角。當初,XDoclet因為可以自動生成EJB繁雜的接口和布署描述文件而聲名鵲起。然而,現在的XDoclet已經發展成了一個全功能的、面向屬性的代碼生成框架。J2EE代碼生成只是XDoclet的一個應用方面,它可以完成的任務已經遠遠超越了J2EE和項目文檔的生成。
2.2.1 XDoclet 任務
到現在為止,我們一直在討論使用XDoclet生成代碼,但事實上,更確切的說法應該是,我們使用XDoclet的一個特定的任務來生成代碼,比如<ejbdoclet>。每一個XDoclet任務關注于一個特定的領域,并提供這個領域的豐富的代碼生成工具。
[定義:任務(Tasks)是XDoclet里可用的代碼生成應用程序的高層概念。]
在XDoclet里,目前已有如下所示的七個核心任務。
<ejbdoclet>:面向EJB領域,生成EJB、工具類和布署描述符。
<webdoclet>:面向Web開發,生成serlvet、自定義標簽庫和web框架文件。
<hibernatedoclet>:Hibernate持續,配置文件、Mbeans
<jdodoclet>:JDO,元數據,vender configuration
<jmxdoclet>:JMX,MBean接口,mlets,配置文件。
<doclet>:使用用戶自定義模板來生成代碼。
<documentdoclet>:生成項目文件(例如todo列報表)
這其中,<ejbdoclet>最常用,并且很多項目也僅僅使用XDoclet來進行EJB代碼生成。<webdoclet>是其次一個常用的代碼生成任務。當然,在一個項目中同時使用幾個XDoclet任務是可能的(并且也是推薦的),但在這些任務之間是完全獨立的,它們彼此之間并不能進行直接的交流。
2.2.2 XDoclet子任務
XDoclet的任務是領域相關的,而在某個特定領域的XDoclet任務,又由許許多多緊密耦合在一起的子任務組成的,這些子任務每個都僅僅執行一個非常特定和簡單的代碼生成任務。
[定義:子任務(subtasks)是由任務提供的單目標的代碼生成過程]
任務提供子任務執行時的上下文,并且把這些相關的子任務組織管理了起來。任務會依賴這些子任務來生成代碼。在一個任務當中調用多個子任務來協同完成各種各樣比較大型的代碼生成任務是非常常見的。比如,在開發EJB時,你可能想要為每一個bean生成一個home接口,一個remote接口以及ejb-jar.xml布署描述符文件。這就是在<ejbdoclet>任務的上下文環境中的三個獨立的代碼生成子任務。
子任務可以隨意的組合排列,以滿足項目代碼生成的需要。某個XDoclet任務包含的子任務經常會共享功能和在源文件中使用相同的XDoclet標簽。這意味著當你開始一個任務的時候,你可以很容易的集成進一個相關的子任務,而不需要很大的改動。
子任務交互
讓我們以<ejbdoclet>任務為例,看一下相關的子任務之間是如何進行關聯的。假設你正在開發一個CMP(容器管理持久化)實體Bean。你想要使用一些<ejbdoclet>的子任務:
•<deploymentdescriptor>:生成ejb-jar.xml布署描述符文件。
•<localhomeinterface>:生成local home接口。
•<localinterface>:生成local接口。
在執行如上子任務的時候,你需要標記出你的實體Bean的CMP域。當你發布你的bean的時候,你還需要在開發商相關的布署描述符中提供某個特定的關系數據庫中的特定表和列與你的CMP實體Bean的映射關系。XDoclet可以讓你在原先已存在的CMP XDoclet屬性基礎上再加上一些關系映射屬性,然后,你就可以在任務中加入一個開發商相關的子任務(例如<jboss>或者<weblogic>)來生成布署描述符文件。XDoclet提供了幾乎所有的應用服務器的支持,你只需要一些初始化的小改動,就可以進行這些應用服務器相關的代碼生成了。
但那只是冰山一角。你還可以使用<entitycmp>子任務為為你的bean生成一個實體bean接口的實現子類。如果你使用<valueobject>子任務來為了你的bean生成值對象,<entityemp>子任務還會為你的值對象生成方法的實現代碼。
覺得不可思議了吧。可惜XDoclet沒有提供<cupofcoffee>子任務,要不然我們可以喝杯咖啡,休息一下啦。
這里不是想向你介紹<ejbdoclet>所有的子任務或者<ejbdoclet>可以完成的所有代碼生成功能,而僅僅是想向你展示一下任務的子任務之間是如何工作在一起的。一旦你開始并熟悉了一個XDoclet 子任務,熟悉另一個子任務會變得非常簡單- 那種每個子任務都是孤立的相比,使用這種可以相互協作的子任務,開發成本會顯著的降低,效果也更加的立竿見影。
2.3 使用Ant執行任務
XDoclet“嫁”給了Ant。XDoclet任務就是Ant的自定義任務,除此以外,沒有其他運行XDoclet任務的方法。所幸的是,Ant已經成為了Java構建工具事實上的標準,所以這不算什么限制。事實上,反過來,XDoclet與Ant的這種“親密”關系使得XDoclet可以參與到任何Ant構建過程當中去。
2.3.1 聲明任務
XDoclet并沒有和Ant一起發布,所以如果你想要使用XDoclet的話,就需要單獨的下載和安裝。在使用任何一個XDoclet的任務之前,你首先需要在使用Ant的<taskdef>任務來聲明它。例如:
<taskdef name=”ejbdoclet”
classname=”xdoclet.modules.ejb.EjbDocletTask”
classpathref=”xdoclet.lib.path”/>
如果你熟悉Ant的話,你就會知道這段代碼是告訴Ant加載<ejbdoclet>的任務定義。當然,你也可以以你喜歡的任何方式來命名這個自定義任務,但最好還是遵守標準的命名規律以免發生混淆。classname和classpathref屬性告訴Ant到哪里去找實現這個自定義任務的XDoclet類。如果你想使用其他的XDoclet任務,就必須要類似這樣首先聲明這個任務。
一般共通的做法是,把所有需要使用的XDoclet任務都放在Ant的一個目標里聲明,這樣在其他的目標里如果需要使用這些任務,只要depends這個任務就可以了。你可能已經在Ant的構建文件里包含了init目標,這就是放置XDoclet任務聲明的好地方(當然如果你沒有,你也可以建一個)。下面的例子就是在一個init目標里加入了<ejbdoclet>和<webdoclet>的聲明:
<target name=”init”>
<taskdef name=”documentdoclet”
classname=”xdoclet.modules.doc.DocumentDocletTask”
classpathref=”xdoclet.lib.path” />
<taskdef name=”ejbdoclet”
classname=”xdoclet.modules.ejb.EjbDocletTask”
classpathref=”xdoclet.lib.path” />
<taskdef name=”webdoclet”
classname=”xdoclet.modules.web.WebDocletTask”
classpathref=”xdoclet.lib.path” />
</target>
現在,任務聲明好了,XDoclet“整裝待發”。
2.3.2 使用任務
你可以在任何目標里使用聲明好的任務。在任務的上下文環境里,可以調動相關的子任務。讓我們看一個例子,這個例子調用了<ejbdoclet>任務。不要擔心看不懂語法的細節,現在你只需要關心一些基礎概念就可以了。
<target name=”generateEjb” depends=”init”>
<ejbdoclet destdir=”${gen.src.dir}”>
<fileset dir=”${src.dir}”>
<include name=”**/*Bean.java”/>
</fileset>
<deploymentdescriptor destdir=”${ejb.deployment.dir}”/>
<homeinterface/>
<remoteinterface/>
<localinterface/>
<localhomeinterface/>
</ejbdoclet>
</target>
把任務想像成一個子程序運行時需要的一個配置環境(記住,子任務才是真正進行代碼生成工作的)。當調用一個子任務時,子任務從任務繼承上下文環境,當然,你也可以根據需要隨意的覆蓋這些值。在上面的例子里,因為<deploymentdescriptor>子任務生成的布署描述符文件和其他生成各種接口的子任務生成的Java源文件需要放在不同的位置,所以覆蓋了destdir的屬性值。布署描述符文件需要放在一個在打包EJB JAR文件的時候可以容易包含進來的地方,而生成的Java代碼則需要放置在一個可以調用Java編譯器進行編譯的地方。需要這些子任務之間是緊密關聯的,但只要你需要,你可以有足夠的自主權控制任務的生成環境。
<fileset>屬性同樣被應用到所有的子任務。這是一個Ant的復雜類型(相對于文本和數值的簡單類型),所以以子元素的方式在任務中聲明。不要把它和子任務混為一談。當然,如果你想在某個子任務中另外指定一個不同的輸入文件集,你也可以在這個子任務中放置一個<fileset>子元素來覆蓋它。
子任務的可配置選項遠遠不止這些。我們會在下一章繼續介紹所有的任務和子任務,以及常用的配置選項。
2.4 用屬性標注你的代碼
可重用的代碼生成系統需要輸入來生成感興趣的輸出。一個解析器生成器也需要一個語言描述來解析生成解析器。一個商務對象代碼生成器需要領域模型來知道要生成哪些商務對象。XDoclet則需要Java源文件做為輸出來生成相關的類或者布署/配置文件。
然而,源文件可能并沒有提供代碼生成所需要的所有信息。考慮一個基于servlet的應用,當你想生成web.xml文件的時候,servlet源文件僅可以提供類名和適當的servlet接口方法。其他的信息比如URI pattern映射、servlet需要的初始化參數等信息并沒有涵蓋。顯而易見,如果class并沒有提供這些信息給你,你就需要自己手動在web.xml文件時填寫這些信息。
XDoclet當然也不會知道這些信息。幸運的是,解決方法很簡單。如果所需信息在源文件時沒有提供,那就提供它,做法就是在源文件里加入一些XDoclet屬性。XDoclet解析源文件,提取這些屬性,并把它們傳遞給模板,模板使用這些信息生成代碼。
2.4.1 剖析屬性
XDoclet屬性其實就是javadoc的擴展。它們在外表上和使用上都有javadoc屬性一樣,可以放置在javadoc文檔注釋里。文檔注釋以/**開始,*/結尾。下面是一個簡單的例子:
/**
* 這是一段javadoc注釋。
* 注釋可以被分解成多行,每一行都以星號開始。
*/
在注釋里的所有文本都被視為javadoc注釋,并且都能夠被XDoclet訪問到。注釋塊一般都與Java源文件中的某個實體有關,并緊跟在這個實體的前面。沒有緊跟實體的注釋塊將不會被處理。類(或者接口)可以有注釋塊,方法和域也可以有自己的注釋塊,比如:
/**
* 類注釋塊
*/
public class SomeClass {
/** 域注釋塊 */
private int id;
/**
* 構造函數注釋塊
*/
public SomeClass() {
// ...
}
/**
* 方法注釋塊
*/
public int getId() {
return id;
}
}
注釋塊分成兩部分:描述部分和標簽部分。當遇到第一個javadoc標簽時,標簽部分開始。Javadoc標簽也分成兩部分:標簽名和標簽描述。標簽描述是可選的,并且可以多行。例如:
/**
* 這是描述部分
* @tag1 標簽部分從這里開始
* @tag2
* @tag3 前面一個標簽沒有標簽描述。
* 這個標簽有多行標簽描述。
*/
XDoclet使用參數化標簽擴展了javadoc標簽。在XDoclet里,你可以在javadoc標簽的標簽描述部分加入name=”value”參數。這個微小的改動大大增強了javadoc標簽的表達能力,使得javadoc標簽可以用來描述復雜的元數據。下面的代碼顯示了使用XDoclet屬性描述實體Bean方法:
/**
* @ejb.interface-method
* @ejb.relation
* name=”blog-entries”
* role-name=”blog-has-entries”
* @ejb.value-object
* compose=”com.xdocletbook.blog.value.EntryValue”
* compose-name=”Entry”
* members=”com.xdocletbook.blog.interfaces.EntryLocal”
* members-name=”Entries”
* relation=”external”
* type=”java.util.Set”
*/
public abstract Set getEntries();
參數化標簽允許組合邏輯上相關聯的屬性。你可以加入描述這個類的元信息,使得這個類的信息足夠生成代碼。另外,程序員借由閱讀這樣的元信息,可以很快的理解這個類是如何使用的。(如果這個例子上的元信息你看不懂,不要擔心,在第4章里,我們會學到EJB相關的標簽以及它們的涵意。)
另外,請注意上面的例子中,所有的標簽名都以ejb開頭。XDoclet使用namespace.tagname的方式給標簽提供了一個命名空間。這樣做除了可以跟javadoc區別開來以外,還可以把任務相關的標簽組織起來,以免任務之間的標簽產生混淆。
2.5 代碼生成模式
XDoclet是一種基于模板的代碼生成引擎。從高層視圖上來看,輸出文件其實就是由解析執行各式各樣的模板生成出來的。如果你理解了模板以及它所執行的上下文環境,就可以確切的認識到,XDoclet可以生成什么,不可以生成什么。如果你正在評估XDoclet平臺,理解這些概念是非常重要的。要不然,你可能會錯過XDoclet的許多強大的功能,也可能會被XDoclet的一些限制感到迷惑。
XDoclet運行在在Ant構建文件環境中,它提供了Ant自定義任務和子任務來與XDoclet引擎交互。任務是子任務的容器,子任務負責執行代碼生成。子任務調用模板。模板提供了你將生成代碼的餅干模子。XDoclet解析輸入的源文件,提取出源文件中的XDoclet屬性元數據,再把這些數據提供給模板,驅動模板執行。除此之外,模板還可以提供合并點(merge points),允許用戶插入一些模板片斷(合并文件merge files)來根據需要定制代碼生成。
2.5.1 模板基礎
XDoclet使用代碼模板來生成代碼。模板(template)是你想生成文件的原型。模板里使用一些XML標簽來指導模板引擎如何根據輸入類以及它們的元數據來調整代碼的生成。
[定義:模板(template)是生成代碼或描述文件的抽象模視圖。當模板被解析的時候,指定的細節信息會被填入。]
模板一般情況下會有一個執行環境。模板可能應用在一個類環境(轉換生成transform generation),也有可能應用在一個全局環境(聚集生成aggregate generation)。轉換生成和聚集生成是XDoclet的兩種類型的任務模式,理解它們之間的區別對于理解XDoclet是非常重要的。
當你使用XDoclet生成布置描述符文件時,你使用的是聚集生成。布置描述符文件并不僅僅只與一個類相關,相反,它需要從多個類里聚集信息到一個輸入文件。在這種生成模式里,解析一次模板只會生成一個輸出文件,不管有多少個輸入文件。
在轉換生成模式里,模板遇到每一個源文件就會解析一次,根據該文件類的上下文環境生成輸出。這種生成模式會為每一個輸入文件生成一個輸出文件。
轉換生成模式的一個很好的例子是生成EJB的local和remote接口。顯然,接口是和Bean類一一相關的。從每一個類里提取信息(類以及它的方法、域、接口以及XDoclet屬性等信息)轉換出接口。除此以外,不需要其他的信息。
從實現里提取出接口似乎有點反向。如果你手寫程序的話,一般來說會先定義一個接口,然后再寫一個類來關現它。但XDoclet做不到,XDoclet不可能幫你實現一個已有接口,因為它不可能幫你生成你的業務邏輯。當然,如果業務邏輯可以從接口本身得到(比如JavaBean的get/set訪問器)或者使用XDoclet屬性聲明好,那么生成業務邏輯代碼來實現一個接口也不是不可能。但一般情況下,這樣做不太現實。相比而言,提供一個實現,并描述接口與這個實現之間的關聯就容易多了。
聚集生成和轉換生成主要區別在它們的環境信息上。即使一個代碼生成任務中生成一個Java文件,一般也不常用聚集生成,因為生成一個Java類還需要一些重要信息如類所處的包以及你想生成的類名,在這種環境下是無法提供的。如果一定要使用聚集生成的話,那就需要在另一個單獨的地方提供好配置信息了。
2.5.2 模板標簽
在還沒見到模板長啥樣子之前,我們已經比較深入的認識它了。那模板文件究竟長啥樣子呢?它有點像JSP文件。它們都包含文件和XML標簽,生成輸出文件時XML標簽會被解析,然后生成文本并顯示在XML標簽所處的位置上。除了以XDt為命名空間打頭的XML標簽會被XDoclet引擎解析以外,其余的XML標簽XDoclet會忽略不管。下面的代碼片斷顯示了XDoclet模板的“經典造型”:
public class
<XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
Extends Observabe {
static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
_instance = null;
public static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClassOf>
getInstance() {
if (_instance == null) {
_instance =
new <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/>
</XDtClass:classOf>();
}
return _instance;
}
}
研究一下這個模板,你會發現,它生成的是一個類定義。這個類里定義了一個靜態變量instance,并且使用一個靜態方法來控制這個靜態文件的訪問。借助Java語法,你可以很容易的推斷出那些XDoclet模板標簽的目錄是生成類名,雖然對于這個標簽如何工作你還并不是很了解。
即使你從沒打算過要自己寫模板,但理解模板是如何被解析運行的還是很有必要的。遲早你會調用到一個運行失敗的XDoclet任務,沒有產生你所期望的輸出,那么最快捷的找出原因的方法就是直接檢查模板文件,看看是哪里出了問題。
讓我們看一下生成靜態域定義的片斷:
static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
_instance = null;
在XDoclet的眼里,這段模板代碼很簡單,那就是:
static <tag/> _instance = null;
XDoclet解析執行標簽,如果有輸出的話,把輸入置回到文本里去。有些標簽會執行一些運算,把輸出放回到一個流里。這樣的標簽稱之為內容標簽(content tags),因為它們產生內容。
另一種類型的標簽稱之為BODY標簽。BODY標簽在開始和結束標簽之間存在文本。而BODY標簽強大就強大在這些文本自己也可以是一斷可以由外圍標簽解析的模板片斷。比如在上面的例子里,XDtClass:classOf標簽,它們的內容就是模板片斷:
<XDtEjbFacade:remoteFacadeClass/>
classOf標簽解析這段模板,提取出全限制的內容,然后剃除前面的包面,只輸出類名。BODY標簽并不總是會解析它的內容,在做這件事之前,它們會事先檢查一些外部判斷條件(比如檢查檢查你正在生成的是一個接口還是一個類)。這里標標簽稱之為條件標簽(conditional tags)。還有一些BODY標簽提供類似迭代的功能,它的內容會被解析多次。比如一個標簽針對類里的每一個方法解析一次內容。
XDoclet標簽提供了許多高層次的代碼生成功能,但是有時候,它們可能顯得不夠靈活,或者表達能力滿足不了你的需要。這時候,相對于另外開發一套通用功能的模板引擎相比,你可以選擇擴展XDoclet模板引擎。你可以使用更具表述能力、功能更加強大的Java平臺開發你自己的一套標簽。
2.6 使用合并定制
代碼生成系統之所以使用的不多,主要原因就在于它們往往只能生成一些死板的、不夠靈活的代碼。大多數代碼生成系統不允許你改動它們生成的代碼;如果,如果這個系統不夠靈活,你所能做到的最好的擴展就是應用繼承擴展生成的代碼,或者使用一些共通的設計模式(比如Proxy和Adaptor)來滿足你的需要。無論如此,這都不是產生你想生成的代碼的好辦法。代碼生成器最好能做到所生成即所得WYGIWYG(what you generate is what you get),來取代你需要花費大量的時間來粉飾生成出來的并不滿足要求的代碼。所以,對于代碼生成器來說,支持靈活的定制,是生成能夠完全滿足要求的代碼的前提條件。
XDoclet通過合并點(merge points)支持定制??合并點是在模板文件定義里允許運行時插入定制代碼的地方。有時候,合并點甚至可以影響到全局代碼的生成,不但允許你添加一些定制內容,還可以從根本上改變將要生成出來的東西。
[定義:合并點(Merge points)是模板預先定義的允許你在代碼生成的運行時加入定制內容的擴展點]
讓我們研究一段從XDoclet源代碼里摘取出來的模板代碼。在為實體Bean生成主鍵的模板末尾,定義了這樣一個合并點:
<XDtMerge:merge file=”entitypk-custom.xdt”></XDtMerge:merge>
如果你在你的merge目錄下創建了一個名為entitypk-custom.xdt文件,那么這個模板文件的內容將會在這個合并點被包含進來。你的定制可以執行高層模板可以執行的所有強大功能,可以進行所有模板可以進行的運算(包括定義自定義標簽,定義它們自己的合并點)。
上面的這種合并點,為所有的類環境使用了同一個文件。當然,也可以為每一個類環境使用不同的合并文件。如果你不想定制全部的類文件,或者你不想為了某些改動而重寫模板的時候,這會很有用。不管動機是什么,逐類的合并點很容易識別出來:他們會在名字里包含一個XDoclet的逐類標記{0}。這里有一個生成ejb-jar.xml文件里的安全角色引用的例子:
<XDtMerge:merge file=”ejb-sec-rolerefs-{0}.xml”>
<XDtClass:forAllClassTags tagName=”ejb:security-role-ref”>
<security-role-ref>
<role-name>
<XDtClass:classTagValue
tagName=”ejb:security-roleref”
paramName=”role-name”/>
</role-name>
<role-link>
<XDtClass:classTagValue
tagName=”ejb:security-roleref”
paramName=”role-link”/>
</role-link>
</security-role-ref>
</XDtClass:forAllClassTags>
</XDtMerge:merge>
這段模板會遍歷工程里的所有Bean。對于每一個Bean,XDoclet先從Bean的文件名里提取出Bean名,然后替換{0},再根據替換后的文件名去尋找合并文件。例如,如果你有一個名為BlogFacadeBean的Bean,XDoclet會嘗試尋找一個名為ejb-src-rolerefs-BlogFacade.xml的合并文件。
如果找不到這個合并文件,則這個<merge>標簽的內容模板會被解析。這意味著合并點不僅可以提供定制內容,還可以在一個模板文件里定義一個替換點,當定制內容不存在的時候使用替換點里的內容。不是所有的XDoclet任務都提供了有替換內容的合并點,一般來說,它們更傾向于只提供一個簡單的合并點,僅僅當合并文件存在的時候解析并導入合并文件的內容。這取決于任務的開發者覺得哪種合并點更符合他的要求。
還有一點沒有介紹到的是XDoclet如何定位合并文件,每一個XDoclet任務或者子任務都會提供一個mergeDir屬性,這個屬性用于設置你存放合并文件的目錄。
一、EJB技術簡介
EJB的全稱是Enterprise java bean。是JAVA中的商業應用組件技術。EJB結構中的角色 EJB 組件結構是基于組件的分布式計算結構,是分布式應用系統中的組件。
一個完整的基于EJB的分布式計算結構由六個角色組成,這六個角色可以由不同的開發商提供,每個角色所作的工作必須遵循Sun公司提供的EJB規范,以保證彼此之間的兼容性。這六個角色分別是EJB組件開發者(Enterprise Bean Provider) 、應用組合者(Application Assembler)、部署者(Deployer)、EJB 服務器提供者(EJB Server Provider)、EJB 容器提供者(EJB Container Provider)、系統管理員(System Administrator):
二、EJB中各角色的分析
1、EJB組件開發者(Enterprise Bean Provider)
EJB組件開發者負責開發執行商業邏輯規則的EJB組件,開發出的EJB組件打包成ejb-jar文件。EJB組件開發者負責定義EJB的remote和home接口,編寫執行商業邏輯的EJB class,提供部署EJB的部署文件(deployment descriptor)。部署文件包含EJB的名字,EJB用到的資源配置,如JDBC等。EJB組件開發者是典型的商業應用開發領域專家。
EJB組件開發者不需要精通系統級的編程,因此,不需要知道一些系統級的處理細節,如事務、同步、安全、分布式計算等。
2、應用組合者(Application Assembler)
應用組合者負責利用各種EJB組合一個完整的應用系統。應用組合者有時需要提供一些相關的程序,如在一個電子商務系統里,應用組合者需要提供JSP(Java Server Page)程序。
應用組合者必須掌握所用的EJB的home和remote接口,但不需要知道這些接口的實現。
3、部署者(Deployer)
部署者負責將ejb-jar文件部署到用戶的系統環境中。系統環境包含某種EJB Server和EJB Container。部署者必須保證所有由EJB組件開發者在部署文件中聲明的資源可用,例如,部署者必須配置好EJB所需的數據庫資源。
部署過程分兩步:部署者首先利用EJB Container提供的工具生成一些類和接口,使EJB Container能夠利用這些類和接口在運行狀態管理EJB。 部署者安裝EJB組件和其他在上一步生成的類到EJB Container中。 部署者是某個EJB運行環境的專家。
某些情況下,部署者在部署時還需要了解EJB包含的業務方法,以便在部署完成后,寫一些簡單的程序測試。
4、EJB 服務器提供者(EJB Server Provider)
EJB 服務器提供者是系統領域的專家,精通分布式交易管理,分布式對象管理及其它系統級的服務。EJB 服務器提供者一般由操作系統開發商、中間件開發商或數據庫開發商提供。
在目前的EJB規范中,假定EJB 服務器提供者和EJB 容器提供者來自同一個開發商,所以,沒有定義EJB 服務器提供者和EJB容器提供者之間的接口標準。
5、EJB 容器提供者(EJB Container Provider)
EJB 容器提供者提供以下功能:
提供EJB部署工具為部署好的EJB組件提供運行環境 。EJB容器負責為EJB提供交易管理,安全管理等服務。
EJB 容器提供者必須是系統級的編程專家,還要具備一些應用領域的經驗。EJB 容器提供者的工作主要集中在開發一個可伸縮的,具有交易管理功能的集成在EJB 服務器中的容器。EJB 容器提供者為EJB組件開發者提供了一組標準的、易用的API訪問EJB 容器,使EJB組件開發者不需要了解EJB服務器中的各種技術細節。
EJB容器提供者負責提供系統監測工具用來實時監測EJB容器和運行在容器中的EJB組件狀態。
6、系統管理員(System Administrator)
系統管理員負責為EJB服務器和容器提供一個企業級的計算和網絡環境。
系統管理員負責利用EJB 服務器和容器提供的監測管理工具監測EJB組件的運行情況。
三、EJB的體系結構:
EJB分布式應用程序是基于對象組件模型的,低層的事務服務用了API技術。EJB技術簡化了用JAVA語言編寫的企業應用系統的開發,配置。EJB技術定義了一組可重用的組件:Enterprise Beans。你可以利用這些組件,象搭積木一樣的建立你的分布式應用程序。當你把代碼寫好之后,這些組件就被組合到特定的文件中去。每個文件有一個或多個Enterprise Beans,在加上一些配置參數。最后,這些Enterprise Beans被配置到一個裝了EJB容器的平臺上。客戶能夠通過這些Beans的home接口,定位到某個beans,并產生這個beans的一個實例。這樣,客戶就能夠調用Beans的應用方法和遠程接口。
EJB服務器作為容器和低層平臺的橋梁管理著EJB容器和函數。它向EJB容器提供了訪問系統服務的能力。例如:數據庫的管理和事務的管理,或者對于其它的Enterprise的應用服務器。所有的EJB 實例都運行在EJB容器中。容器提供了系統級的服務,控制了EJB的生命周期。EJB中的有一些易于使用的管理工具如:Security--配置描述器(The Deployment descriptor)定義了客戶能夠訪問的不同的應用函數。容器通過只允許授權的客戶訪問這些函數來達到這個效果。Remote Connectivity--容器為遠程鏈接管理著低層的通信issues,而且對Enterprise Beas的開發者和客戶都隱藏了通信細節。EJB的開發者在編寫應用方法的時候,就象是在條用本地的平臺一樣的。客戶也不清楚他們調用的方法可能是在遠程被處理的。Life Cycle managment--客戶簡單的創建一個Enterprise beans的實例,并通常取消一個實例。而容器管理著Enterprise Beans的實例,使Enterprise Beans實現最大的效能和內存利用率。容器能夠這樣來激活和使Enterprise Beans失效,保持眾多客戶共享的實例池。等等。 Trasction management-配置描述器定義了Enterprise beans 的事務處理的需求。容器管理著那些管理分布式事務處理的復雜的issues。這些事務可能要在不同的平臺之間更新數據庫。容器使這些事務之間互相獨立,互不干擾。保證所有的更新數據庫都是成功發生的,否者,就回滾到事務處理之前的狀態。
EJB 組件是基于分布式事務處理的企業級應用程序的組件。所有的EJB都有如下的特點:EJB包含了處理企業數據的應用邏輯。定義了EJB的客戶界面。這樣的界面不受容器和服務器的影響。于是,當一個EJB被集合到一個應用程序中去時,不用更改代碼和重新編譯。EJB能夠被定制 各種系統級的服務,例如安全和事務處理的特性,都不是屬于EJB類的。而是由配置和組裝應用程序的工具來實現。 有兩種類型的EJB: Session beans 和 entity beans.Session beans是一種作為單用戶執行的對象。作為對遠程的任務請求的相應,容器產生一個Session beans 的實例。一個Session beans有一個用戶.從某種程度上來說,一個Session bean 對于服務器來說就代表了它的那個用戶.Session beans 也能用于事務,它能夠更新共享的數據,但它不直接描繪這些共享的數據。Session beans 的生命周期是相對較短的。典型的是,只有當用戶保持會話的時候,Session beans 才是活著的。一旦用戶退出了,Session beans 就不再與用戶相聯系了。Session beans被看成是瞬時的,因為如果容器崩潰了,那么用戶必須重新建立一個新的Session對象來繼續會話。
Session bean典型的聲明了與用戶的互操作或者會話。也就是說,Session bean了在客戶會話期間,通過方法的調用,掌握用戶的信息。一個具有狀態的Session bean稱為有狀態的Session bean.當用戶終止與Session beans互操作的時候.會話終止了,而且,bean 也不再擁有狀態值。Session bean也可能是一個無狀態的 session bean.無狀態的Session beans并不掌握它的客戶的信息或者狀態。用戶能夠調用beans的方法來完成一些操作。但是,beans只是在方法調用的時候才知道用戶的參數變量。當方法調用完成以后,beans并不繼續保持這些參數變量。這樣,所有的無狀態的session beans的實例都是相同的,除非它正在方法調用期間。這樣,無狀態的Session beans就能夠支持多個用戶.容器能夠聲明一個無狀態的Session beans.能夠將任何Session beans指定給任何用戶.
Entity Beans對數據庫中的數據提供了一種對象的視圖。例如:一個Entity bean能夠模擬數據庫表中一行相關的數據。多個client能夠共享訪問同一個Entity bean.多個client也能夠同時的訪問同一個Entity bean.Entity beans通過事務的上下文來訪問或更新下層的數據。這樣,數據的完整性就能夠被保證。Entity Beans能存活相對教長的時間,并且狀態是持續的。只要數據庫中的數據存在,Entity beans就一直存活。而不是按照應用程序或者服務進程來說的。即使EJB容器崩潰了,Entity beans也是存活的。Entity Beans生命周期能夠被容器或者 Beans自己管理。如果由容器控制著保證 Entity beans持續的issus。如果由Beans自己管理,就必須寫Entity beans的代碼,包括訪問數據庫的調用。
Entity Beans是由主鍵(primary key 一種唯一的對象標識符)標識的。通常,主鍵與標識數據庫中的一塊數據,例如一個表中的一行,的主鍵是相同的。主鍵是client能夠定位特定的數據塊。
四、開發EJB
1、類介紹:
開發EJB的主要步驟一般來說,整個的開發步驟(開發,配置,組裝)包括如下幾個方面。開發:首先要定義三個類:Bean類本身,Bean的本地和遠程接口類。 配置:配置包括產生配置描述器--這是一個XML文件、聲明了Enterprise Bean的屬性、綁定了bean的class文件(包括stub文件和skeleton文件)。最后將這些配置都放到一個jar文件中。還需要在配置器中定義環境屬性。組裝應用程序:包括將Enterprise beans安裝到Server服務器中,測試各層的連接情況。程序組裝器將若干個Enterprise Beans與其它的組件結合起來。組合成一個完整的應用程序。或者將若干個Enterprise beans組合成一個復雜的Enterprise Bean。管理Enterprise Bean。
我們必須定義和編寫一些EJB中的基本類。如Enterprise bean類:這是Enterprise bean內部應用邏輯的實現。編寫Enterprise bean的遠程接口類。編寫Enterprise bean的本地接口類。說明主鍵類,主鍵類只是對于Entity bean才需要的。在Enterprise bean的配置描述器中指定主鍵的名字。Enterprise beans提供者定義了遠程接口和本地接口,實現了EJB類本身。Remote接口中提供了客戶調用EJB實現的應用邏輯函數的接口。而home接口提供了產生和定位remote接口實例的方法。
在Enterprise bean本身類的實現,本地home接口,遠程remote接口之間并沒有正式的聯系(例如繼承關系)。但是,在三個類里聲明的方法卻必須遵守EJB里面定義的規范。例如: 你在Enterprise bean里面聲明了一個應用程序的方法或者說應用邏輯。也在beans的remote接口中聲明了這個方法,那么,這兩個地方必須要同樣的名字。Bean的實現里面必須至少有一個Create()方法:ejbCreate()。但是可以有多個帶有不同參數的create()方法。 在home接口中,也必須有相同的方法定義(參數的個數相同)。EjbCreate()方法返回的一個容器管理的持久對象。它們都返回一個容器管理持久性的主鍵值。但是,在home的相應的Create()方法中返回值的類型是remote接口。
注意:實體bean的實現的ejbCreate方法有點不同。實體bean可以不定義ejbCreate方法。如果實體只是通過應用程序或通過數據庫管理程序的途徑被加到數據庫中,實體bean就省略了ejbCreate方法。EjbCreate返回的值是主鍵類型。如果ejbCreate方法是容器管理持久性的實體bean的方法,它的返回值就是NULL類型。如果實體bean實現了Bean管理的持久性,ejbCreate方法就返回值類型就是主鍵類型。容器的任務是把各接口和Enterprise bean的實現類結合起來。保證在編譯時和運行時,各接口和實現類是相對應的。
EJB的實現類,各接口要從不同的基類中繼承下來。一個會話bean必須實現基類javax.ejb.SessionBean。而實體bean必須實現基類javax.ejb.EntiyBean。這些EJB的基類都是從javax.ejb.EnterpriseBean繼承而來。而javax.ejb.EnterpriseBean又是從java.io.Serializable繼承而來。每一個Enterprise Bean都必須有一個remote接口。Remote接口定義了應用程序規定客戶可以調用的邏輯操作。這些是一些可以由客戶調用的公共的方法,通常由Enterprise beans類來實現。注意,Enterprise bean的客戶并不直接訪問Bean。而是通過remote接口來訪問。Enterprise bean類的remote接口擴展了javax.ejb.EJBObject類的公共java接口。而Javax.ejb.EJBObject是所有remote接口的基類。其代碼如下:
package javax.ejb;
public interface EJBObject extends java.rmi.Remote{
public EJBHome getEJBHome() throws java.rmi.RemoteException;
public Object getPrimaryKey() throws java.rmi.RemoteException;
public void Remove() throws java.rmi.RemtoeException, java.rmi.RemoveException
public Handle getHandle() throws java.rmi.RemoteException;
boolean isIdentical (EJBObject p0) throws java.rmi.RemoteException;
}
getEJBHome()方法允許你取得一個相關的Home接口。對于 實體Bean,用getPrimaryKey()方法獲得實體Bean的主鍵值。Remove()可以刪除一個Enterprise bean。具體的語義在各種不同類型的enterprise beans的生命周期中,由上下文中解釋的。方法getHandle()返回了一個Enterprise bean實例的持久的句柄。IsIndentical()方法允許你去比較Enterprise beans是否相同。
2、方法:
所有的remote接口中的方法必須聲明為公共(public)的,并必須拋出java.rmi.RemotException異常。另外,所有的remote接口中的方法定義的參數和都必須是在RMI-IIOP中有效的。對每一個在remote接口中定義的方法,在Enterprise bean 類里面都要有相應的方法。相應的方法必須要有同樣的名字,同樣類型和數量的參數,同樣的返回值,而且還要拋出同樣的例外。 如下代碼顯示了一個ATM例子的會話bean的remote接口Atm,。里面聲明了一個應用方法transfer()。黑體部分表示EJB規范中必須要有的內容。Remote接口必須擴展javax.ejb.EJBObject類。從客戶端調用的Enterprise bean的每一個方法都必須在remote接口中聲明。Transfer()方法拋出了兩個意外。其中InSufficientFundsException例外是應用程序定義的意外。
Public interface Atm extends javax.ejb.EJBObject{
Public void transfer(String Source, String Target, float amount)
Throws java.rmi.RemoteException, InSufficientFundsException;
}
Home接口必須定義一個或多個的Create()方法。每一個這樣的Create()方法都必須命名為Create。并且,它的參數,不管是類型還是數量都必須與bean類里面的ejbCreate()方法對應。注意,home接口中的Create()方法和bean類中ejbCreate()方法的返回值類型是不同的。實體bean的home接口還包含find()方法。 每一個Home接口都擴展了javax.ejb.EJBHome接口。如下代碼顯示了javax.ejb.EJBHome接口的定義:
package javax.ejb;
public interface EJBHome extends java.rmi.Remote() {
void remove(Handle handle) throws java.rmi.RemoteException,RemoveException;
void remove(Object primarykey) throws java.rmi.RemoteException,RemoveException;
EJBMetaData getEJBMetaData() throws RemoteException;
Homehandle getHomeHandle() throws RemoteException;
}
這里提供了兩個remove()方法來刪除Enterprise bean的實例。第一個remove方法是通過句柄來刪除一個Enterprise bean的實例。第二個remove方法通過主鍵來刪除一個Enterprise bean的實例。 在眾多的Enterprise bean實例中,句柄唯一的標識一個實例。一個句柄與它引用的Enterprise bean有相同的生命期。考慮一個實體對象,客戶可以通過一個句柄來重新獲得相應的Enterprise bean的實例。一個句柄能夠對應一個Enterprise bean對象的多個實例。例如,即使當Enterprise bean對象所在的主機崩潰了,或者Enterprise bean對象在不同的機器之間移動,句柄仍是有效的。這里的句柄是Serialized句柄,與CORBA中的字符串化的CORBA對象的引用是相似的概念。在EJBHome接口中的第二個remove操作通過其主鍵來決定要刪除的Enterprise bean。主鍵可以是擴展了Java Object類的任何類型,但是,必須要實現Java的Serializable接口。主鍵是標識實體bean的主要的方法。通常,主鍵是數據庫中的一個關鍵字,唯一的定義了由實體bean代表的數據。
方法getEJBMetaData()返回了Enterprise bean對象的metadata接口。這個接口允許客戶獲得Enterprise bean的metadata信息。當開發工具來編譯鏈接應用程序的時候,或者配置工具來配置的時候,可能會用到metadata信息。Javax.ejb.EJBMetadata接口提供了獲得javax.ejb.EJBHome接口,home類,remote接口,還有獲得主鍵的方法。也提供了一個isSesson()的方法來確定在放這個home接口的對象是會話bean還是實體bean。IsStatelessSession()方法指示這個會話bean是有狀態還是無狀態的。如下代碼顯示了javax.ejb.EJBMetadata接口的定義部分的代碼。
Public javax.ejb; Public interface EJBMetaData{
EJBHome getEJBHome();
Class getHomeInterfaceClass();
Class getRemoteInterfaceClasss();
Class getPrimaryKeyClass();
Boolean isSession();
Boolean isStatelesssSession();
}
對每一個Create()方法,EJB規范定義了如下的命名約定。它的返回值是會話bean的remote接口的類型。方法的名字只能是Create()。對會話bean類中的每一個ejbCreate()方法都必須有一個Create()與之對應。 對于每一個Create()方法的參數的類型和數量都必須與會話bean類中的ejbCreate()方法相對應。方法必須拋出java.rmi.RemoteException例外。 方法必須拋出javax.rmi.CreateExeption例外。 Create()方法的參數是用來初始化新的會話bean對象的。 如下代碼顯示了一個會話bean對象的不同的Create()方法,其中必須的部分用粗體顯示:
public interface AtmHome extends javax.ejb.EJBHome{
Atm create() throws java.rmi.RemoteException,javax.ejb.CreateException;
Atm create(Profile preferredProfile)
Throws java.rmi.RemoteExeption,javax.ehrows java.rmi.RemoteException,RemoveException;
EJBMetaData getEJBMetaData() throws RemoteException;
Homehandle getHomeHandle() throws RemoteException;
}
這里提供了兩個remove()方法來刪除Enterprise bean的實例。第一個remove方法是通過句柄來刪除一個Enterprise bean的實例。第二個remove方法通過主鍵來刪除一個Enterprise bean的實例。在眾多的Enterprise bean實例中,句柄唯一的標識一個實例。一個句柄與它引用的Enterprise bean有相同的生命期。考慮一個實體對象,客戶可以通過一個句柄來重新獲得相應的Enterprise bean的實例。一個句柄能夠對應一個Enterprise bean對象的多個實例。例如,即使當Enterprise bean對象所在的主機崩潰了,或者Enterprise bean對象在不同的機器之間移動,句柄仍是有效的。這里的句柄是Serialized句柄,與CORBA中的字符串化的CORBA對象的引用是相似的概念。
在EJBHome接口中的第二個remove操作通過其主鍵來決定要刪除的Enterprise bean。主鍵可以是擴展了Java Object類的任何類型,但是,必須要實現Java的Serializable接口。主鍵是標識實體bean的主要的方法。通常,主鍵是數據庫中的一個關鍵字,唯一的定義了由實體bean代表的數據。方法getEJBMetaData()返回了Enterprise bean對象的metadata接口。這個接口允許客戶獲得Enterprise bean的metadata信息。當開發工具來編譯鏈接應用程序的時候,或者配置工具來配置的時候,可能會用到metadata信息。Javax.ejb.EJBMetadata接口提供了獲得javax.ejb.EJBHome接口,home類,remote接口,還有獲得主鍵的方法。也提供了一個isSesson()的方法來確定在放這個home接口的對象是會話bean還是實體bean。IsStatelessSession()方法指示這個會話bean是有狀態還是無狀態的。如下代碼顯示了javax.ejb.EJBMetadata接口的定義部分的代碼。
Public javax.ejb;
Public interface EJBMetaData{
EJBHome getEJBHome();
Class getHomeInterfaceClass();
Class getRemoteInterfaceClasss();
Class getPrimaryKeyClass();
Boolean isSession();
Boolean isStatelesssSession();
}
五、EJB的編程環境:
1、 使用Jbuilder
Jbuilder與EJB Container能夠進行無縫連接。Jbuilder和Inprise的應用服務器包括了所有的開發和配置Enterprise Beans的工具以及所需要的庫:運行和管理Enterprise Bean的容器、命名服務、 事務服務、Java數據庫、開發Enterprise Beans所需要的API、一個增強的java-to-iiop編譯器,支持值類型和RMI信號等等。
Jbuilder還提供了一個快速開發應用程序Enterprise Beans的工具和向導。通過簡單而且直觀的步驟,向導幫助你建立一個Enterprise Bean。自己設定某些缺省值,產生了bean的模板,在上面,我們可以增加我們自己的應用邏輯。Jbuilder也提供了一個EJB的接口生成向導。向導在Enterprise Bean的公共方法基礎上生成了Remote接口和Home接口。Jbuilder還提供一個配置器的向導幫助我們逐步的建立XML描述器文件。并將生成的Stubs集中到一個jar文件中。
2、使用Jbuilder之外的集成環境:
如果你使用其它的除了別的集成環境(IDE)。要確定使用了集成環境IDE所帶的容器工具。也要驗證IDE是否支持EJB規范的相應的版本,還要確定它是否正確的支持EJB的API。
要確定JD到所支持的EJB容器的版本。可以通過檢查Inprise的安裝說明來確定EJB容器所支持的支持JDK的版本。
在配置Enterprise Bean的時候,你必須使用Inprise的應用服務器所提供的工具。這些工具能夠編輯和修改第三方的代理商提供的Inprise配置描述器。還能夠驗證配置描述器,能夠驗證bean的源代碼。
六、一個簡單的HELLO例子
1、安裝Apusic Application Server
Note:以下以Linux為例,來說明Apusic Application Server的安裝過程。其他平臺的安裝,可參考Apusic Application Server安裝手冊。
下載JDK1.2,Apusic Application Server必須運行在JDK1.2以上環境中。可從以下站點下載最新JDK。
http://java.sun.com
下載Apusic Application Server
Apusic Application Server 試用版可從以下網址得到:
http://www.apusic.com/download/enter.jsp
在下載完成后,你可以得到一個包裹文件apusic.zip,選定安裝目錄,假設安裝到/usr下,則用以下命令:
cd /usr
jar xvf apusic.zip
/usr下會出現一個目錄apusic,Apusic Application Server的所有程序都被解壓到/usr/apusic下。
將以下路徑加入到CLASSPATH中
/usr/apusic/lib/apusic.jar
$JAVA_HOME/lib/tools.jar
用以下命令運行Apusic Application Server
java -Xms64m com.apusic.server.Main -root /usr/apusic
2、定義EJB遠程接口(Remote Interface)
任何一個EJB都是通過Remote Interface被調用,EJB開發者首先要在Remote Interface中定義這個EJB可以被外界調用的所有方法。執行Remote Interface的類由EJB生成工具生成。
以下是HelloBean的Remote Inteface程序:
package ejb.hello;
import java.rmi.RemoteException;
import java.rmi.Remote;
import javax.ejb.*;
public interface Hello extends EJBObject, Remote {
file://this method just get "Hello World" from HelloBean.
public String getHello() throws RemoteException;
}
3、定義Home Interface
EJB容器通過EJB的Home Interface來創建EJB實例,和Remote Interface一樣,執行Home Interface的類由EJB生成工具生成。
以下是HelloBean 的Home Interface程序:
package ejb.hello;
import javax.ejb.*;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.*;
/**
* This interface is extremely simple it declares only
* one create method.
*/
public interface HelloHome extends EJBHome {
public Hello create() throws CreateException,
RemoteException;
}
4、寫EJB類
在EJB類中,編程者必須給出在Remote Interface中定義的遠程方法的具體實現。EJB類中還包括一些 EJB規范中定義的必須實現的方法,這些方法都有比較統一的實現模版,編程者只需花費精力在具體業務方法的實現上。
以下是HelloBean的代碼:
package ejb.hello;
import javax.ejb.*;
import java.util.*;
import java.rmi.*;
public class HelloBean implements SessionBean {
static final boolean verbose = true;
private transient SessionContext ctx;
// Implement the methods in the SessionBean
// interface
public void ejbActivate() {
if (verbose)
System.out.println("ejbActivate called");
}
public void ejbRemove() {
if (verbose)
System.out.println("ejbRemove called");
}
public void ejbPassivate() {
if (verbose)
System.out.println("ejbPassivate called");
}
/**
* Sets the session context.
*
* @param SessionContext
*/
public void setSessionContext(SessionContext ctx) {
if (verbose)
System.out.println("setSessionContext called");
this.ctx = ctx;
}
/**
* This method corresponds to the create method in
* the home interface HelloHome.java.
* The parameter sets of the two methods are
* identical. When the client calls
* HelloHome.create(), the container allocates an
* instance of the EJBean and calls ejbCreate().
*/
public void ejbCreate () {
if (verbose)
System.out.println("ejbCreate called");
}
/**
* **** HERE IS THE BUSINESS LOGIC *****
* the getHello just return a "Hello World" string.
*/
public String getHello()
throws RemoteException
{
return("Hello World");
}
}
5、創建ejb-jar.xml文件
ejb-jar.xml文件是EJB的部署描述文件,包含EJB的各種配置信息,如是有狀態Bean(Stateful Bean) 還是無狀態Bean(Stateless Bean),交易類型等。ejb-jar.xml文件的詳細信息請參閱EJB規范。以下是HelloBean的配置文件:
<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems Inc.//DTD Enterprise JavaBeans 1.2//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_2.dtd">
<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>Hello</ejb-name>
<home>ejb.hello.HelloHome</home>
<remote>ejb.hello.Hello</remote>
<ejb-class>ejb.hello.HelloBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>Hello</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
6、編譯和部署
編譯Java源文件并將編譯后class和ejb-jar.xml打包到Hello.jar
mkdir build
mkdir build/META-INF
cp ejb-jar.xml build/META-INF
javac -d build *.java
cd build
jar cvf Hello.jar META-INF ejb
cd ..
用EJB工具生成可部署到Apusic Application Server中運行的jar文件:
java com.apusic.ejb.utils.EJBGen -d /usr/apusic/classes/Hello.jar build/Hello.jar
增加/usr/apusic/classes/Hello.jar到CLASSPATH中
將Hello.jar加入到Apusic Application Server配置文件中。在/usr/apusic/config/server.xml 加入以下幾行:
<module>
<ejb>
<ejb-uri>classes/Hello.jar</ejb-uri>
<bean>
<ejb-name>Hello</ejb-name>
<jndi-name>HelloHome</jndi-name>
</bean>
</ejb>
</module>
啟動服務器
java -Xms64m com.apusic.server.Main -root /usr/apusic
7、寫客戶端調用程序
您可以從Java Client,JSP,Servlet或別的EJB調用HelloBean。
調用EJB有以下幾個步驟:
通過JNDI(Java Naming Directory Interface)得到EJB Home Interface
通過EJB Home Interface 創建EJB對象,并得到其Remote Interface
通過Remote Interface調用EJB方法
以下是一個從Java Client中調用HelloBean的例子:
package ejb.hello;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Hashtable;
import javax.ejb.*;
import java.rmi.RemoteException;
/**
* @author Copyright (c) 2000 by Apusic, Inc. All Rights Reserved.
*/
public class HelloClient{
public static void main(String args[]){
String url = "rmi://localhost:6888";
Context initCtx = null;
HelloHome hellohome = null;
try{
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.apusic.jndi.InitialContextFactory");
env.put(Context.PROVIDER_URL, url);
initCtx = new InitialContext(env);
}catch(Exception e){
System.out.println("Cannot get initial context: " + e.getMessage());
System.exit(1);
}
try{
hellohome = (HelloHome)initCtx.lookup("HelloHome");
Hello hello = hellohome.create();
String s = hello.getHello();
System.out.println(s);
}catch(Exception e){
System.out.println(e.getMessage());
System.exit(1);
}
}
}
運行HelloClient,可得到以下輸出:
Hello World
http://yingmingpan.bokee.com/tb.b?diaryId=14817948 |
EJB2.0雨夜教程之一
EJB雨夜原創講解
初識EJB
EJB是什么呢?
EJB是一個J2EE體系中的組件.再簡單的說它是一個能夠遠程調用的javaBean.
它同普通的javaBean有兩點不同.
第一點,就是遠程調用.
第二點,就是事務的功能,我們在EJB中聲明的事務會通過容器幫助我們來處理.
支持EJB的SERVER有以下幾個:
Weblogic
Webspere
Appserver
Jboss
我選用的是weblogic+JBuilder9開發.
回過來我們繼續說這個EJB的原理.
它是分布式的.這個的意思其實很簡單不需要想太復雜.
就是我們把事情不是交給一個人去處理.而是把事情劃分一下交給多個人處理,
而多個人處理之后我們讓這個分配的人來把最后得到的結合處理.
EJB我們首先要明確它是放在服務器端的組件.
一共有三種EJB
1,SessionBean 復雜處理業務邏輯的.
2,EntityBean 處理數據
3,MessageBean 消息Bean異步,耦合的處理.
那么誰能調用EJB呢?
也或者說EJB被誰調用呢?
我們說它就是放在server的一個能夠遠程調用的javaBean.
所以它可以被Java的語言調用servlet,jsp等都ok的說.
而且它還可以被C++語言調用.很強大吧.
EJB的最基本的出發點就是推動Java在服務器端的編程能力.
所以呢它的功能就我個人來看太強悍了..= =
這個遠程調用是通過什么呢.
Java是通過RMI-JRMP(java遠程方法調用)協議來調用.EJB就是通過這個來實現的.
C++是通過Corba-iiop協議來調用的.這個協議支持兩端不同語言的調用.
Corba是公共對象請求代理服務
iiop是網絡內部對象協議.
下面我們來簡單說一下這個分布式的調用。
客戶端發送一個請求給服務器
首先是傳給stub代理類它發送參數給skeleton委托類然后這個類調用我們的實現類取得結果再遠路返回。
這樣一個分布處理就結束了。
后面會具體的結合代碼分析。
先記住這個大致的結構就好。
我們寫一個EJB需要做那些工作呢?
1,寫一個接口繼承EJBObject 這個類作為遠程接口
2,寫一個接口繼承EJBHome 這個類里有一個方法是create()它返回接口類型。
3,寫一個Bean繼承SessionBean, 這個類里包含一個create()方法和一個業務方法。
4,寫一個ejb-jar.xml 這個xml是把上面的三個文件組合起來
5,寫一個weblogic-ejb-jar.xml 這個xml是連接查找source的作用
(不同的server會是不同的。這里選用的是weblogic)
上面的兩個xml文件需要放在META-INF目錄下。
而以上這些類都需要打包在一個jar文件中然后在server部署。
這樣就完成了EJB的部署
例如:
我們寫個簡單的計算吧。
(先聲明下吧本教程的說明都是我自己的理解,
也許并不是一些文檔上的描述那么正規但是很方便理解的。
如果有實在無法茍同的地方大家多多包含。這是為了便于理解的講解)
AddCount.java這個就是我們繼承EJBObject的類.
import javax.ejb.*;
import java.rmi.*;
public interface AddCount extends EJBObject
{
public int addCount(int a,int b) throws RemoteException;
}
接口的作用.在這里強調一下吧.所有實現這個接口的類都會是這個接口的類型.
同時都包含接口的方法的實現.
這個接口中的方法也就是在以后會實現的方法.我們這里要做的是一個加法的運算.
AddCountHome.java這個是繼承EJBHome的類.它里面包含的這個create()返回的是AddCount類型對象.
import javax.ejb.*;
import java.rmi.*;
public interface AddCountHome extends EJBHome
{
public AddCount create() throws RemoteException,CreateException;
}
下面這個是我們的Bean.這個類繼承了SessionBean
import javax.ejb.*;
public class AddCountBean implements SessionBean
{
public void ejbCreate()
{
}
public void setSessionContext(SessionContext ctx)
throws EJBException,
java.rmi.RemoteException
{
}
public void ejbRemove()
throws EJBException,
java.rmi.RemoteException
{
}
public void ejbActivate()
throws EJBException,
java.rmi.RemoteException
{
}
public void ejbPassivate()
throws EJBException,
java.rmi.RemoteException
{
}
public int addCount(double a,double b)
{
return a+b;
}
}
這個里面我們實現了業務的方法addCount(){a+b;}
同時需要指出一下它的其他方法(這些僅僅簡單指出后面的教程有詳細說明)
首先是ejbCreate()這個方法實際上是對我們的遠程接口的實現類的初始化.
setSessionContext(SessionContext ctx)設置context.容器是在這個方法之后產生的實例.
ejbRemove()毫無疑問是一個實例結束移除.
ejbActivate()激活方法.它的作用是激活鈍化.
ejbPassivate()鈍化方法.當實例的內容長時間不進行處理的時候就會鈍化.也就是閑置的意思.
以上這幾個是SessionBean的基本方法.希望大家可以自己慢慢理解.
之后也會反復說到這些的.
下面我們寫xml文件
首先是ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
<enterprise-beans>
<session>
<display-name>first</display-name>
<ejb-name>add</ejb-name>
<home>AddCountHome</home>
<remote>AddCount</remote>
<ejb-class>AddCountBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>add</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
下面這個是weblogic-ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.//DTD WebLogic 8.1.0 EJB//EN' 'http://www.bea.com/servers/wls810/dtd/weblogic-ejb-jar.dtd'>
<weblogic-ejb-jar>
<weblogic-enterprise-bean>
<ejb-name>
add
</ejb-name>
<jndi-name>rainnight</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>
我們接下來寫測試類
import java.util.*;
import java.naming.*;
import java.rim.*;
public class AddTest{
public static void main(String bb[])throws Exception{
Hashtable hash = new Hashtable();
hash.put(Context.INITIAL_COUNTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
hash.put(Context.PROVIDER_URL,
"t3://localhost:7001");
Object obj = context.lookup("rainnight");
AddCountHome home = (AddCountHome)PortableRemoteObject(obj,AddCountHome.class);
AddCount local = home.create();
System.out.println(local.add(1,1));
}
}
測試類的hash是初始化我們的weblogic的xml信息.
這里的t3://localhost:7001是weblogic的特殊協議指向的是ip位置.
然后lookup("rainnight")查找jndi,而實際上也就是通過這個jndi找到我們的ejb組件.
通過得到的對象我們對比一下是不是Home類型.
然后我們執行home.create()產生AddCount的實例
最后調用方法就ok了.
下面是如何執行這個的方法.
第一步,jdk,weblogic.需要快些下載安裝啦..
第二步,配置環境變量.基本的java的環境變量如何配置我就不說了.
這里需要特別指出要把java目錄下的lib中的tools.jar加載到我們的class_path中.
然后我們再把weblogic的目錄下的server目錄下的lib中的weblogic.jar找到也加載進來.
這樣我們編譯需要的東西就ok了.
第三步,編譯java類.
第四步,打包.jar cvf AddCount.jar *.class META-INF/*.xml
第五步,java weblogic.appc AddCount.jar
第六步,部署到weblogic中.
第七步,運行Test類.
jar
功能說明:
Java歸檔工具
語法:
jar [ 命令選項 ] [manifest] destination input-file [input-files]
補充說明:
jar工具是個java應用程序,可將多個文件合并為單個JAR歸檔文件。jar是個多用途的存檔及壓縮工具,它基于ZIP和ZLIB壓縮格式。然而, 設計jar的主要目的是便于將java applet或應用程序打包成單個歸檔文件。將applet或應用程序的組件(.class 文件、圖像和聲音)合并成單個歸檔文件時,可以用java代理(如瀏覽器)在一次HTTP事務處理過程中對它們進行下載,而不是對每個組件都要求一個新連 接。這大大縮短了下載時間。jar還能壓縮文件,從而進一步提高了下載速度。此外,它允許applet的作者對文件中的各個項進行簽名,因而可認證其來 源。jar工具的語法基本上與tar命令的語法相同。
命令選項
-c 在標準輸出上創建新歸檔或空歸檔。
-t 在標準輸出上列出內容表。
-x[file] 從標準輸入提取所有文件,或只提取指定的文件。如果省略了file,則提取所有文件;否則只提取指定文件。
-f 第二個參數指定要處理的jar文件。在-c(創建)情形中,第二個參數指的是要創建的jar文件的名稱(不是在標準輸出上)。在-t(表(或-x(抽取)這兩種情形中,第二個參數指定要列出或抽取的jar文件。
-v 在標準錯誤輸出設備上生成長格式的輸出結果。
-m 包括指定的現有清單文件中的清單信息。用法舉例:“jar cmf myManifestFile myJarFile *.class”
-0 只儲存,不進行 ZIP 壓縮。
-M 不創建項目的清單文件。
-u 通過添加文件或更改清單來更新現有的 JAR 文件。例如:“jar -uf foo.jar foo.class”將文件 foo.class 添加到現有的JAR文件foo.jar中,而“jar umf manifest foo.jar”則用manifest中的信息更新foo.jar的清單。
-C 在執行 jar 命令期間更改目錄。例如:“jar -uf foo.jar -C classes *”將classes目錄內的所有文件加到foo.jar中,但不添加類目錄本身。
程序示例
1:將當前目錄下所有CLASS文件打包成新的JAR文件:
jar cf file.jar *.class
2:顯示一個JAR文件中的文件列表
jar tf file.jar
3:將當前目錄下的所有文件增加到一個已經存在的JAR文件中
jar cvf file.jar *