XDoclet

来源:互联网 发布:双肩包 男 知乎 编辑:程序博客网 时间:2024/04/30 11:24
Rick Hightower
2003年8月

關於本技術教學

本技術教學的目的

本技術教學為 J2EE 開發人員講述如何使用 XDoclet 來加速開發。XDoclet 使用屬性導向程式化來簡化元件之間的連續整合。透過生成部署描述子和支援程式碼,它使您能夠大大地減少開發時間,從而使您能夠把重點放在應用程式邏輯上。

如果您是 J2EE 開發的老手,那麼您已意識到保持程式碼與部署描述子之間的同步是一個障礙。元件常常需要與其它應用程式一起被重用,或者在其它環境(例如其它應用程式伺服器)中被重用,或者與其它資料庫系統一起被重用。您需要儲存每個應用程式/環境組合的不同的 部署描述子,即使只修改了長長的部署描述子中的一行或兩行,您也需要每個可能的設定的部署描述子。這確實可能降低開發速度。您有時會覺得使部署描述子同步比寫 程式碼更費時。

XDoclet 簡化了自動的部署描述子的生成。作為程式碼生成實用程式,它使您能夠使用類似 JavaDoc 標示的東西來把 metadata 新增到語言部件(例如類別、方法和欄位)中。然後,它使用額外的 metadata 來生成類似部署描述子和原始碼的相關文件。這個概念被命名為 屬性導向程式化(attribute-oriented programming,以避免與另一個「AOP」(aspect-oriented programming)混淆)。

XDoclet 透過解析相關文件(解析的方式類似於 JavaDoc 引擎解析原始碼來建立 JavaDoc 文件的方式)來生成這些相關文件。事實上,XDoclet 的較早的版本為 JavaDoc。與 JavaDoc 相類似,XDoclet 不只有可以存取這些您以 JavaDoc 標示的形式新增到程式碼中的額外的 metadata ,還可以存取原始碼的結構,即 package 、類別、方法和欄位。然後它把這個資料的層次結構應用於範本。它使用所有這些和您可以定義的範本來生成支援文件,否則,您得手動地、單調地建立支援文件。本技術教學把重點放在 XDoclet 附帶的現有的範本的使用上。

XDoclet 附帶了使您能夠建立 web.xmlejb-jar.xml 和許多其它文件的 Ant 工作。在本技術教學中,您將用 XDoclet 和 webdoclet Ant 任務來生成 Web 應用程式部署描述子。此外,您將生成 Enterprise JavaBeans(EJB)支援文件。請注意,Ant 的標準的分發版沒有附帶 XDoclet Ant 工作。您需要到 Sourceforge.net 上的 XDoclet 網站下載 XDoclet Ant 工作。

本技術教學透過實踐的方式來使您學會如何用 XDoclet 開發 J2EE 元件。在本技術教學結束之前,您將使用 XDoclet 來建置幾個 J2EE 元件。您將建置 Servlet 和 custom tag(標示庫)並開發 3 個 EJB 元件。

您可能在想︰「為什麼我應該在乎呢?我是優秀的 Java/J2EE Web 開發人員,我從來不需要 XDoclet。」簡而言之,您不知道錯過了什麼。一旦您開始使用 XDoclet,您就不會停用它。XDoclet 是您的 J2EE 開發過程中缺少的東西。它將加速開發。在掌握了基礎知識之後,您可以繼續生成基於您自己的定製 XDoclet 範本的程式碼。

在學習本技術教學前我需要知道什麼?

本技術教學假定您熟悉 Java 技術和 XML。J2EE 技術和 Ant 的知識是有用的,但不是理解重要概念的前提。Ant 被用來建置和部署範例應用程式。在本技術教學最後的參考資料部分中有 Ant、Java 技術、J2EE、XML 和 EJB 技術的入門教材的連結。

我已用 Tomcat 和 Resin EE 測試了本技術教學中的原始碼。這些應用程式易於移植到其它符合 J2EE 的應用程式伺服器(例如 IBM WebSphere Application Server)。

我使用 Eclipse Framework 來建立這些範例。執行這些範例的最簡便的方法是下載 Eclipse 2.1 或更高的版本和 Eclipse 的 J2EE 應用程式伺服器外掛程式。Eclipse 對 Ant 的支援非常好,這簡化了直接從 IDE 環境中執行 Ant XDoclet 工作。

本技術教學有哪些內容

本技術教學講述如何開始使用 XDoclet 來加速 J2EE 開發。本技術教學有三個循序漸進的範例,這些範例把 XDoclet 開發應用於 Servlet、 custom tag 和 EJB。所有的範例附帶了一組 Ant 建置腳本,以使您能夠透過重用樣本建置文件來容易地建立您自己的定製解決方案。

關於作者

Rick Hightower 是喜歡使用 Java 程式化語言、Ant 和 XDoclet 的開發人員。Rick 目前是 Trivera Technologies(主營企業開發的全球性培訓、顧問和咨詢公司)的 CTO。

Rick 還寫了 Mastering Tomcat 一書中的兩章,這兩章的主題是 Struts 技術教學和用 Ant 和 XDoclet 的 Tomcat 開發,他還寫了許多其它出版品。

Rick 還在今年(2003)的 JavaOne 上發表有關 EJB CMP/CMR 和 XDoclet 的演講,在 TheServerSide.com 軟體討論會上發表有關使用 XDoclet 的 J2EE 開發的演講。Rick 已在 JDJEdge、WebServicesEdge 和 Complete Programmer Network 軟體討論會上發表演講。

學習本技術教學所需的工具

您需要 JDK 的目前版本。本技術教學中的所有範例使用 J2SE SDK 1.4.1。

所有的範例使用 Ant 建置腳本來建置和部署包括範例的 Web 應用程式。這並不令人感到意外,因為 XDoclet 依賴於 Ant,而且 XDoclet 的唯一界面是透過 Ant。您可在 Ant 首頁上找到 Ant。這些範例使用 Ant 1.5.3。

您當然需要 XDoclet 本身,您可在 XDoclet 網站找到 XDoclet。與 Ant 相似,XDoclet 是開放式原始碼。本技術教學中的範例使用 XDoclet 1.2 beta 2 版本。當您學習本技術教學時,XDoclet 的 beta 版可能已被更高的版本所取代,而且最近 XDoclet 已被接受為 Apache Jakarta 專案,因此,如果您在以上連結處找不到它,請到 Apache Jakarta 網站找它。

我建議您使用整合開發環境(Integrated development environment,IDE),例如 Eclipse 專案的 IDE,因為有不少 jar 文件需管理。所有的範例都附帶在可以免費獲得的 Eclipse IDE 中完成的專案,並且與 Eclipse 和 WebSphere Studio Application Developer(WebSphere Studio)相容。只要您按照建議來設定環境,您就可以使用 Eclipse 專案文件而不必付出太多的額外的勞力。Eclipse 或 WebSphere Studio Application Developer(WebSphere Studio)並不是必備的,但是您可分別在 Eclipse Web 網站和 WebSphere Studio 試用下載中找到它們。沒有使用 Eclipse 的要求,但是提供了 Eclipse 專案文件以方便 Eclipse 和 WebSphere Studio 使用者。WebSphere Studio 建置在 Eclipse 之上。Eclipse 被用來建置樣本應用程式。

Servlet 範例

Servlet XDoclet 範例

現在本技術教學開始了,我們先講述簡單的 Servlet 和 XDoclet 組合。別忘了 XDoclet 繼承了 JavaDoc 引擎的概念從而使您能夠根據定製 JavaDoc 標示來生成程式碼和其它文件。XDoclet 附帶的 Ant 任務使您能夠建立 web.xml 、ejb-jar.xml 和許多其它文件。在這一部分中,您將使用 XDoclet 和 webdoclet Ant 任務來生成 Web 應用程式部署描述子。這是本技術教學中最簡單的嘗試。請注意標準的 Ant 分發版沒有附帶 XDoclet Ant 工作。

如果您以前用過 J2EE 技術,那麼您知道 web.xml 是用來設定 Web 應用程式的。web.xml 是 Web 應用程式的部署描述子。XDoclet 使您能夠在 Servlet 原始碼中使用類似 JavaDoc 的標示來生成 web.xml 部署描述子。

以下 Servlet 指定了被用來生成 web.xml 的 XDoclet 標示。我們來快速地預覽一下,然後我將詳細介紹這些標示如何映射到 Web 應用程式部署描述子(web.xml)中生成的元素。

 /*  * BasicServlet.java  *   */  package rickhightower.servlet;  import javax.servlet.*; import javax.servlet.http.*; import javax.sql.*; import java.sql.*; import javax.naming.*;   /**  *  * @author  Rick Hightower  *  * @version 1.0  * @web.servlet name="BasicServlet"                  display-name="Basic Servlet"                  load-on-startup="1"  * @web.servlet-init-param name="hi" value="Ant is cool!"  * @web.servlet-init-param name="bye" value="XDoc Rocks!"  * @web.resource-ref description="JDBC resource"  *                   name="jdbc/mydb"  *                   type="javax.sql.DataSource"  *                   auth="Container"  * @web.servlet-mapping url-pattern="/Basic/*"  * @web.servlet-mapping url-pattern="*.Basic"  * @web.servlet-mapping url-pattern="/BasicServlet"  */ public class BasicServlet extends HttpServlet {          /** Initializes the servlet.      */     public void init(ServletConfig config) throws ServletException {         super.init(config);              }          /** Destroys the servlet.      */     public void destroy() {              }          /** Processes requests for both HTTP GET and POST methods.      * @param request servlet request      * @param response servlet response      */     protected void processRequest(HttpServletRequest request,                                    HttpServletResponse response)                                   throws ServletException, java.io.IOException {         ServletConfig config = this.getServletConfig();         String hi = config.getInitParameter("hi");         String bye = config.getInitParameter("bye");                  try{             response.setContentType("text/html");              java.io.PrintWriter out = response.getWriter();             out.println("<html>");             out.println("<head>");             out.println("<title>Basic Servlet</title>");             out.println("</head>");             out.println("<body>");             out.println("<h1> bye:" + bye + "</h1>");             out.println("<h1> hi:" + hi + "</h1>");             getJdbcPool(out);             out.println("</body>");             out.println("</html>");             out.close();         }catch(Exception e){             throw new ServletException(e);         }     }          /** Handles the HTTP GET method.      * @param request servlet request      * @param response servlet response      */     protected void doGet(HttpServletRequest request,                           HttpServletResponse response)                          throws ServletException, java.io.IOException {         processRequest(request, response);     }          /** Handles the HTTP POST method.      * @param request servlet request      * @param response servlet response      */     protected void doPost(HttpServletRequest request,                            HttpServletResponse response)                           throws ServletException, java.io.IOException {         processRequest(request, response);     }          /** Returns a short description of the servlet.      */     public String getServletInfo() {         return "XDoc Rules";     }          private void getJdbcPool(java.io.PrintWriter out)throws Exception{         out.println("</ br>");                  Object obj = new InitialContext().                                  lookup("java:comp/env/jdbc/mydb");         DataSource pool = (DataSource)obj;         if (pool == null) return;         Connection connection = pool.getConnection();                  out.println("<table>");         try{                         ResultSet rs =                   connection.getMetaData().                   getTables(null,null,null,null);             while(rs.next()){                 out.println("<table-row><table-cell>");                 out.println(rs.getString("TABLE_NAME"));             }         }finally{              connection.close();         }         out.println("</table>");                                    out.println("</ br>");     }      } 

當您把 XDoclet Ant 任務 webdoclet 應用於以上相關文件時,您將獲取以下部署描述子。

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web  Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">  <web-app>  ...     <servlet>       <servlet-name>BasicServlet</servlet-name>       <display-name>Basic Servlet</display-name>      <servlet-class>rickhightower.servlet.BasicServlet</servlet-class>        <init-param>          <param-name>hi</param-name>          <param-value>Ant is cool!</param-value>       </init-param>       <init-param>          <param-name>bye</param-name>          <param-value>XDoc Rocks!</param-value>       </init-param>        <load-on-startup>1</load-on-startup>     </servlet>     <servlet-mapping>       <servlet-name>BasicServlet</servlet-name>       <url-pattern>/Basic/*</url-pattern>    </servlet-mapping>    <servlet-mapping>       <servlet-name>BasicServlet</servlet-name>       <url-pattern>*.Basic</url-pattern>    </servlet-mapping>    <servlet-mapping>       <servlet-name>BasicServlet</servlet-name>       <url-pattern>/BasicServlet</url-pattern>    </servlet-mapping>  ...     <resource-ref>       <description>JDBC resource</description>       <res-ref-name>jdbc/mydb</res-ref-name>       <res-type>javax.sql.DataSource</res-type>       <res-auth>Container</res-auth>    </resource-ref>  ...  </web-app> 

接著將循序漸進地解釋什麼標示對應於 Web 應用程式部署描述子的哪些部分。

第一步︰定義 Servlet 元素

XDoclet 可能看起來有點可怕,但是映射是很自然的。使用 webdoclet 的第一步是在您的類別中把 servlet 元素定義為類別層級的 XDoclet JavaDoc 標示(如下所示)︰

 ... * @web.servlet name="BasicServlet"                  display-name="Basic Servlet"                  load-on-startup="1" ...  */  public class BasicServlet extends HttpServlet {  

這段程式碼生成 web.xml 中 servlet 元素和子元素(如下所示)︰

    <servlet>       <servlet-name>BasicServlet</servlet-name>       <display-name>Basic Servlet</display-name>      <servlet-class>rickhightower.servlet.BasicServlet</servlet-class>  ...        <load-on-startup>1</load-on-startup>     </servlet>      

您可能在想 servlet-class 是如何被確定的。因為 XDoclet 工作的工作原理類似於 JavaDoc API,所以它像 JavaDoc API 為 JavaDoc 獲取完整的類別名稱那樣獲取 servlet 的完整的類別名稱。這不只有減少了輸入,還減少了犯錯誤的可能。後來,當您重構和決定變更類別名稱或 package 結構的時候,您不必手動地變更所有的部署描述子。太好了﹗

第二步︰定義 Servlet 的初始參數

在使用 Servlet 元素定義了 servlet 之後,您可以定義映射和初始參數。servlet-init-param 被定義在 JavaDoc 註釋中(如下所示)︰

 ...  * @web.servlet-init-param name="hi" value="Ant is cool!"  * @web.servlet-init-param name="bye" value="XDoc Rocks!" ... */ public class BasicServlet extends HttpServlet {     
這些參數將生成部署描述子中的以下 >init-param>

    <servlet>        <servlet-name>BasicServlet</servlet-name> ...        <init-param>          <param-name>hi</param-name>          <param-value>Ant is cool!</param-value>       </init-param>       <init-param>          <param-name>bye</param-name>          <param-value>XDoc Rocks!</param-value>       </init-param> ...    </servlet>     

希望您在看了這個範例後大叫「等等」﹗為什麼要等?我剛把初始參數硬編碼到原始碼中。這是否令您發抖?不?這應使您思考。我曾在有關 XDoclet 的會議上演講,此時有人讓我停下並就 <init-param> 現在被硬編碼提出警告。別這麼快……請學下去。

第三步︰綜合應用 Ant 和 XDoclet 來設定元件

您一般願把初始化參數硬編碼到原始碼中。有關使用 <init-param> 的全部概念是使 Web 元件可被應用程式組譯器(application assembler)定製為 J2EE 應用程式。

更好的方法是把初始參數設為指向類似 @bye@ 和 @hi@ 中的標示,然後使用啟用了過濾的 Ant 複製來為正確的應用程式傳遞正確的標示值。這裡假定您使用 Ant 來建置專案。

您可以使用 Ant 過濾來把設定文件中的標示置換成它們在部署環境中的正確的值。過濾器是支援設定多個應用程式的 J2EE 元件的另一種方式。以下是根據條件來設定 web.xml 的範例 Ant 腳本︰

 <project name="filtering" default="run">    <target name="spanishSetup" if="spanish">         <filter token="bye" value="adios"/>         <filter token="hi" value="hola"/>   </target>    <target name="englishSetup" unless="spanish">         <filter token="bye" value="goodbye"/>          <filter token="hi" value="hello"/>   </target>    <target name="setup" depends="spanishSetup,englishSetup"/>    <target name="run" depends="setup">         <copy todir="${workspace}/WEB-INF" filtering="true">               <fileset dir="./WEB-INF"/>         </copy>   </target>   </project>  
在上面的 Ant 範例中,englishSetup 目標中的過濾器把 bye 標示設為 goodbye,而 spanishSetup 目標中的過濾器把 bye 標示設為 adios

稍後,當腳本使用啟用了過濾的複製工作時,它把過濾器應用於複製任務指定的文件集中的所有的文件。若 spanish 屬性被設定,則啟用了過濾的複製任務把所有的字串 @bye@ 取代成為 adios,若 spanish 屬性沒有被設定,則 goodbye

更容易的方法……
雖然這是解決這個問題的一個方法,但是還有更容易的方法。Ant 屬性可被用來設定 XDoclet 中的每個屬性值。因為您用 XDoclet Ant 任務來生成相關的文件,所以 XDoclet 可存取所有的 ant 屬性。因此,您可以這樣來設定值︰

 ... * @web.servlet-init-param     name="hi"   *                     value="${basic.servlet.hi}"  *   * @web.servlet-init-param     name="bye"   *                     value="${basic.servlet.bye}" ... */ public class BasicServlet extends HttpServlet {  

然後,當您生成 web.xml 時,hibye 初始化參數被設定成 Ant 建置腳本中設定的 basic.servlet.hibasic.servlet.bye 屬性的目前值。XDoclet 和 Ant 相互配合從而使 J2EE 元件被設定成應用程式。主要的好處是可按每個應用程式來設定元件。想像一下每個應用程式的主建置文件,這個文件呼叫每個元件的建置文件,把設定某個應用程式的元件所需的訊息傳遞給建置文件。

第四步︰定義 Servlet 映射

XDoclet JavaDoc 標示也可被用來定義 servlet 映射(如下所示)︰

  * @web.servlet-mapping url-pattern="/Basic/*"  * @web.servlet-mapping url-pattern="*.Basic"  * @web.servlet-mapping url-pattern="/BasicServlet" ... */ public class BasicServlet extends HttpServlet { 

這將生成 web.xml 中的以下項目︰

    <servlet-mapping>       <servlet-name>BasicServlet</servlet-name>        <url-pattern>/Basic/*</url-pattern>    </servlet-mapping>    <servlet-mapping>       <servlet-name>BasicServlet</servlet-name>       <url-pattern>*.Basic</url-pattern>    </servlet-mapping>    <servlet-mapping>       <servlet-name>BasicServlet</servlet-name>       <url-pattern>/BasicServlet</url-pattern>    </servlet-mapping> 

短短的三行程式碼對 12 行 XML 元素和子元素。您開始體會到 XDoclet 的功效(和快樂)嗎?XML 應該由計算機來解析,由人類來閱讀,但沒有必要由人類來編寫。

第五步︰定義 J2EE 資源

除了上述內容外,您可以在 web.xml 中為類似 JDBC 資料源的資源定義資源參照,甚至定義 ejb 參照。Java 文件在類別層級包括這些 XDoclet JavaDoc 樣式的標示(如下所示)︰

 /** ...  * @web.resource-ref description="JDBC resource"  *                   name="jdbc/mydb"  *                   type="javax.sql.DataSource"  *                   auth="Container" ... */ public class BasicServlet extends HttpServlet {     

以上程式碼生成 web.xml 中如下元素︰

 ...     <resource-ref>       <description>JDBC resource</description>       <res-ref-name>jdbc/mydb</res-ref-name>       <res-type>javax.sql.DataSource</res-type>       <res-auth>Container</res-auth>    </resource-ref>  
使用 webdoclet 任務來生成 Web 應用程式部署描述子

這樣都很好,但是您如何使用帶有漂亮的類似 JavaDoc 標示的 Java 相關文件並生成 web.xml ?為此,您需要編寫使用 XDoclet 的 webdoclet 工作的 Ant 建置文件。

webdoclet 任務是生成各式支援文件的 Ant 工作。在這個範例中,您將使用 deploymentdescriptor 工作。但是在使用前,您必須設定 XDoclet 以便在 Ant 腳本中存取它。在設定 XDoclet 時,您最好設定一下環境。

在執行 webdoclet 任務前,您需要設定帶有 XDoclet 和 Ant 的環境。這就要求您安裝並設定 XDoclet。然後,讓您的 Ant 建置文件瞭解您的應用程式伺服器 設定。

為了滿足所有這些要求,您需要︰

  1. 安裝 XDoclet
  2. 下載和安裝範例
  3. 為找到 XDoclet 安裝而設定範例
  4. 為找到應用程式伺服器的實際的部署目錄而設定範例
  5. 為找到應用程式伺服器的 lib 目錄而設定範例
安裝 XDoclet

下載 XDoclet 1.2 或更高版本。請存取 sourceforge 上 XDoclet 專案的文件部分。尋找類似 xdoclet-bin-1.2xxx.zip 的文件。按一下它。

把 zip 文件解壓到根目錄的子目錄 xdoclet。如果您願意,您可以解壓到另一個目錄。

我已包括了匯入屬性文件的範例 build.xml 文件。屬性文件為您設定所有的屬性而外部參照極少。如果您未在根目錄下安裝 XDoclet(如果您使用 Unix,那麼這很有可能),那麼您需要在 build.properties 文件中設定 xdocletlib 屬性(下一段將對此作詳細介紹)。

安裝和設定範例

您能夠以 zip 文件的形式下載範例程式。在根目錄中解壓這個文件。這將建立 tutorials 文件夾。

如果您使用這個範例而且把 XDoclet 安裝在另一個位置,那麼您只需修改 build.properties 文件(tutorials/J2EEXdoclet/webdoclet/build.properites)中的 xdocletlib 變數。

以下是 build.properties 文件的清單︰

 ################ Change These for your environment ###################      # This is where you installed xdoclet xdocletlib=/xdoclet/lib      # Change these for your app servers.     # This is the deployment directory. webapps=/tomcat4/webapps     # This lib is where the ant script expects to find the j2ee jar files. lib=/tomcat4/common/lib   # You should not have to change these src=./src WEBINF=./web/WEB-INF dest=${WEBINF}/classes  docroot=./web output=./tmp  appname=webdoclet  # You may change these at will. These get used by the Servlet example. basic.servlet.bye=dude basic.servlet.hi=mom 

build.properties 文件中的說明足以使資深的 Ant/J2EE 開發人員繼續往下學。對於 Ant 的初學者或其它有疑問的讀者來說,請仔細閱讀下一段。

有關為您的環境而設定 build.properites 的詳細資訊

您需要這樣來變更 build.properties 文件(tutorials/J2EEXdoclet/webdoclet/build.properites)中的三個設定︰

  1. 設定 XDoclet 安裝的位置
  2. 設定 J2EE 應用程式伺服器的部署目錄
  3. 設定 J2EE 應用程式伺服器的 lib 目錄
設定 XDoclet 庫
xdocletlib 的預設設定是 /xdoclet/lib。如果您在另一位置中安裝 XDoclet,請相應地調整這一設定。

設定部署目錄
為了匹配 J2EE 應用程?1/lib 使用 Sun 的參考實作目錄。Ant 建置文件使用這個屬性來為編譯設定類別路徑上的 J2EE 庫 jar 文件。

如果您沒有看懂所有有關 Ant 的內容,也就是說,您是 Ant 的初學者,那麼我為您準備了最完美的東西。請透過存取以下連結來閱讀 Mastering Tomcat on Developing Web Components with Ant(作者就是本人)中的樣本章節︰「http://www.rickhightower.com/AntPrimer.pdf」。它是有關使用 Ant 的優秀的初級技術教學。

定義 webdoclet 任務

為了執行 webdoclet 工作,您必須在 ant 建置文件中用 taskdef 來定義 XDoclet webdoclet 任務(如下所示)︰

       <taskdef name="webdoclet"                classname="xdoclet.modules.web.WebDocletTask"                 classpathref="xdocpath"       />  
您必須這樣做,因為 XDoclet 不是內置的 Ant 工作。注意 Ant 任務處理程式的類別名稱是 xdoclet.modules.web.WebDocletTask。還要注意您參照預先定義的類別路徑 xdocpath。先前已定義了 xdocpath,它使用先前設定的 xdocletlib 屬性(如下所示)︰
      <path id="cpath">          <fileset dir="${lib}"/>     </path>          <path id="xdocpath">         <path refid="cpath"/>         <fileset dir="${xdocletlib}">             <include name="*.jar"/>         </fileset>     </path>  
taskdef 必須在它的類別路徑和 J2EE lib 目錄中包括 XDoclet jar 文件。在定義了 webdoclet taskdef 之後,您就可以使用 webdoclet 任務了。

您可在 ant 建置文件 build.xml 中找到這些任務和標示。

使用 webdoclet 任務

為了從 Java 原始碼生成 web.xml ,您需要使用 XDoclet 的 webdoclet 工作,如下所示︰

       <webdoclet destdir="${dest}">           <fileset dir="${src}">             <include name="**/*Servlet.java" />          </fileset>           <deploymentdescriptor servletspec="2.3"                                   destdir="${WEBINF}" />        </webdoclet> 

webdoclet 任務被用於 Ant 目標 generateDD。輸出目錄由 webdoclet 工作的 destdir 屬性 webdoclet destdir="${dest}" 來指定。build.properties 文件(/tutorials/J2EEXdoclet/webdoclet/web/WEB-INF)中設定了 dest 屬性。

webdoclet 工作的輸入文件由 fileset 來指定︰fileset dir="${src}"。在 build.properties 文件(/tutorials/webdoclet/src)中設定了 src 屬性。fileset 使用過濾器從而使只有以 Servlet 為結尾的類別才被選擇(例如 BasicServlet)︰<include name="**/*Servlet.java"/>。這就避免了處理所有的原始碼而不是只有使用 XDoclet 標示的原始碼。

deploymentdescriptor 子任務使用 destdir 屬性來指定生成的部署描述子的位置︰deploymentdescriptor ... destdir="${WEBINF}" deploymentdescriptor 是實際生成 web.xml 的子工作。

執行 Ant

為了執行樣本 Ant 建置文件,請到包括專案文件的目錄。為了執行 Ant,請瀏覽到 /tutorials/J2EEXdoclet/webdoclet 目錄,然後輸入︰ant deploy

我已在前面說過,Ant 將搜尋預設的建置文件名 build.xml。(您可能需要調整 build.properties 文件。)在這個範例中,您應當看到以下指令行輸出︰

 C:/tutorials/J2EEXdoclet/webdoclet>ant deploy Buildfile: build.xml  init:     [mkdir] Created dir: C:/tutorials/J2EEXdoclet/webdoclet/tmp/war  compile:     [javac] Compiling 2 source files to      C:/tutorials/J2EEXdoclet/webdoclet/web/WEB-INF/classes  generateDD: [webdoclet] Running <deploymentdescriptor/> [webdoclet] Generating web.xml.  package:       [war] Building war: C:/tutorials/J2EEXdoclet/webdoclet/tmp/war/webdoclet.war  deploy:      [copy] Copying 1 file to C:/tomcat4/webapps  BUILD SUCCESSFUL Total time: 13 seconds 

既然您已部署了它,讓我們來測試它。請存取 http://localhost:8080/webdoclet/BasicServlet。根據您的應用程式伺服器,您可能需要調整連接埠和上下文。

您應當看到與右圖相似的瀏覽器︰

現在開啟 build.properties 文件並變更以下屬性(如下所示)︰

 basic.servlet.hi=I love XDoclet basic.servlet.bye=Feel the power of XDoclet 

下一步,如果您有支援重構的 IDE(如 Eclipse),請把 Servlet 的 package 名改為 com.foobar.ibm。現在,像前面那樣再次執行 ant 建置文件(先執行清除目標,然後部署),再次執行該應用程式。您可以看到︰web.xml 與新的變更同步。請看 XDoclet 的功能。您可以按照需要來隨意地變更類別名稱或類別的 package 。XDoclet 將使 web.xml 同步。請感受強大的 XDoclet﹗

用 Eclipse 來執行 Ant

如果您使用 Eclipse,那麼您可以執行 Ant 和應用程式伺服器而不必離開 IDE(有許多應用程式伺服器的許多外掛程式)。

為了執行 Ant,請用滑鼠右鍵按一下 build.xml 文件,選擇「Run Ant... 」以啟動該腳本。

如果您在使用 EJB 技術時不在 Eclipse 中執行 Ant 建置文件,那麼您必須執行重新整理和重建專案來使 Eclipse 同步;但是,如果您在 Eclipse 中執行 Ant 建置文件,那麼它將自動重新整理。

Custom Tag 範例(標示庫)

使用 XDoclet 的 webdoclet 任務來建立 custom tag  TLD


 custom tag 的類別層級 JavaDoc 標示

與前面相似,映射是很自然的。第一步是定義 jsp 標示(使用 jsp.tag 並傳遞 custom tag 的名稱),如下所示︰

 * @jsp.tag name="BasicTag"     
這段程式碼生成 TLD 文件中的以下程式碼︰
     <tag>         <name>BasicTag</name>         <tag-class>tomcatbook.customtag.BasicTag</tag-class> ...     </tag>     
別忘了,XDoclet 使用 JavaDoc API 來獲取 custom tag 處理程式的完整的類別名稱。接著,您定義使用 custom tag 的 JSP 頁面可用的任何變數。在這個範例中,您定義三個變數。其中一個變數被用於開始標示後,另一個只有用於結束標示後,還有一個只有用於主體中,如下所示︰

  * @jsp.variable name-given="currentIter"   *               class="java.lang.Integer" scope="NESTED"  * @jsp.variable name-given="atBegin"   *               class="java.lang.Integer" scope="AT_BEGIN"  * @jsp.variable name-given="atEnd"   *               class="java.lang.Integer" scope="AT_END"  

XDoclet 的另一個好處是它使這個 custom tag 構成的所有東西被儲存在一個文件中。此外,它也很適合於記錄構成這個 custom tag 的東西。想想如果不用 XDoclet,您必須在長長的 TLD 文件中尋找正確的項目(struts-html.tld 文件長達 3000 行﹗)才能瞭解這個標示定義的變數。

這段程式碼在 TLD 文件的基本標示定義中生成以下程式碼︰

        </p><p>         <variable>                 <name-given>currentIter</name-given>                 <variable-class>java.lang.Integer</variable-class>                 <scope>NESTED</scope>         </variable>                    <variable>                 <name-given>atBegin</name-given>                 <variable-class>java.lang.Integer</variable-class>                 <scope>AT_BEGIN</scope>         </variable>         <variable>                 <name-given>atEnd</name-given>                 <variable-class>java.lang.Integer</variable-class>                 <scope>AT_END</scope>         </variable>     
 custom tag 的方法層級的 JavaDoc 標示

現在來講有點不同的內容。在 servlet 範例中,所有的特別的 JavaDoc 標示是類別層級的。 custom tag 範例使用方法層級的 JavaDoc 標示來為這個範例中的三個屬性(includeBodyincludePageiterate)定義 custom tag 屬性︰

     /** Getter for property includePage.       * @return Value of property includePage.      * @jsp.attribute   required="true"       *                  rtexprvalue="true"      *                  description="The includePage attribute"      */     public boolean isIncludePage() {         return this.includePage;     }      ...          /** Getter for property includeBody.      * @return Value of property includeBody.      * @jsp:attribute   required="true"       *                  rtexprvalue="true"      *                  description="The includeBody attribute"      */     public boolean isIncludeBody() {         return this.includeBody;     } ...     /** Getter for property iterate.      * @return Value of property iterate.      * @jsp:attribute   required="true"       *                  rtexprvalue="true"      *                  description="The iterate attribute"      */     public int getIterate() {         return this.iterate;     } 

請注意,JavaDoc 標示 jsp.attribute 被用來把這個屬性(property)定義為屬性(attribute)。這段程式碼生成 TLD 文件的定義中的以下程式碼︰

iterate true true The iterate attribute includeBody true true The includeBody attribute includePage true true The includePage attribute  custom tag 及其所有的變數和屬性是難以管理和保持同步的。您可以從這個範例看到,XDoclet 可以使複雜的事情變簡單,它使您能夠在一個(而不是兩個)文件中定義所有這些所需的 metadata 。這大大簡化了 custom tag 的使用 - 可能足夠的簡單,以至於您可以開始把 custom tag 應用於您的專案。

設想一下重構和變更對應於屬性的 getter 和 setter 方法的名稱。如果沒有 XDoclet,那麼您不得不在 TLD 文件中搜尋。太痛苦了﹗

這樣都很好,但是您如何從 Java 相關文件生成 TLD 文件?

把標示庫生成新增到 XDoclet

您需要編寫使用 XDoclet 的 webdoclet 工作的 Ant 腳本。為了在目標 generateDD 下加入對 custom tag 的支援,下面的清單修改了前面 webdoclet 清單中的程式碼。和前面一樣,webdoclet 工作的輸入文件由巢狀的 fileset 來指定,有一點例外,這次您加入了新的 include 偽指令以包括標示處理程式(即 <include name="**/*Tag.java" />)。jsptaglib 子任務生成 TLD 文件,如下所示︰

       <webdoclet destdir="${dest}">           <fileset dir="${src}">             <include name="**/*Servlet.java" />             <include name="**/*Tag.java" />          </fileset>           <deploymentdescriptor servletspec="2.3"                                   destdir="${WEBINF}" >                 <taglib uri="mytaglib"                         location="WEB-INF/tlds/mytaglib.tld"                 />                                            </deploymentdescriptor>           <jsptaglib                      jspversion="1.2"                  destdir="${WEBINF}/tlds"                 shortname="basic"                  filename="mytaglib.tld"/>        </webdoclet>             
請注意,為了建立 Web 應用程式的 WEB-INF 目錄下的 TLD 文件 mytaglib,您加入了 jsptaglib 子工作,如下所示︰
          <jsptaglib     jspversion="1.2"                          destdir="${WEBINF}/tlds"                         shortname="basic"                          filename="mytaglib.tld"/>  
另外請注意,為了在 web.xml 中生成標示庫宣告,您加入了 deploymentdescriptor 子任務下的子元素,如下所示︰
          <deploymentdescriptor servletspec="2.3"                                   destdir="${WEBINF}" >                 <taglib uri="mytaglib"                         location="WEB-INF/tlds/mytaglib.tld"                 />  
以上程式碼在 web.xml 中生成以下項目,如下所示︰
     <taglib>       <taglib-uri>mytaglib</taglib-uri>       <taglib-location>WEB-INF/tlds/mytaglib.tld</taglib-location>    </taglib>  
測試新的 JSP  custom tag  

是的,這個 JSP  custom tag 是基本的且沒有什麼功能 - 但它確實可以執行並說明了可用 custom tag 來實作的許多功能。在這個專案的 docroot 中有 JSP 文件 happy.jsp。在執行了 Ant 部署目標後,您可以編輯這個 JSP 文件並嘗試所有的可能。簡要地說,它按照您用 iterate 屬性指定的次數來迭代主體。includeBody 屬性標誌指定是否應包括主體,includePage 屬性指定是否應對剩下的 JSP 文件求值。我嘗試了許多排列,它能像宣稱的那樣執行。

 <%@page contentType="text/html"%> <%@taglib uri="mytaglib" prefix="mytag"%> <html> <head><title>I am a happy JSP page. Yeah!</title></head> <body>  <mytag:BasicTag includePage="true" includeBody="true" iterate="2">     Current iteration is <%=currentIter%> <br /> </mytag:BasicTag>  </body> </html>  

EJB 技術範例

XDoclet 和 EJB 技術

由於 EJB 技術,我開始使用 XDoclet。它使我能夠把我的 EJB 元件移植到許多 J2EE 應用程式伺服器。這對於 CMP 尤為重要,因為 XDoclet 生成供應商特有的從 CMP/CMR 到 RDBMS 的映射。我用 Jython 編寫了自己的 EJB 程式碼生成器,但是我的同伴 Erik Hatcher 不斷地告訴我 XDoclet 的優點直至最後我被說服並嘗試它。

XDoclet 不只有只有可以簡化 EJB 元件的移植,它還大大簡化了 EJB 開發。為什麼?現在,您不必再把 5(或更多)個文件用於一個 EJB 元件,您只需使用一個相關文件。這將使任何 EJB 開發人員成為 XDoclet 的支援者。

想一想。現在,您不必再維護主鍵類別、本地和遠端介面、本地和遠端 home、值類別、部署描述子、多個供應商特有的部署描述子等等;我只需維護一個文件。我只使用 JavaDoc 標示來標示我的實作類別,XDoclet 負責其餘的事情。這不只有只有是酷。這簡化了 EJB 技術的使用。這有助於實作元件體系結構的設想。

一些供應商已開始在 J2EE 應用程式伺服器(JRun)附帶的工具中支援 XDoclet。希望這將成為潮流。我很高興地看到 IBM 和 BEA 的產品中附帶了 XDoclet 支援。目前,供應商特有的 XDoclet 範本比發行版晚幾個月。不要為催促它們而煩惱。XDoclet 開發人員將對您說︰您親自實作它們。別忘了,XDoclet 是開放式 原始碼。實際上,範本是很容易修改的,也許在下一個技術教學中,我將把 CMP/CMR 支援新增到某個供應商產品中。

請注意,EJB 的初學者可以看看參考資料部分中列出的許多參考資料,這將有助於您更好地理解這部分。但是,這部分假設您有一些使用 EJB 技術的經驗。

XDoclet 適合於 Servlet,也確實適合於 custom tag ,對於 EJB 技術是至關重要的。我將在下一個循序漸進的範例中講述如何把 XDoclet 用於 EJB。

繼續講述 XDoclet 和 EJB 技術

為了說明 XDoclet 的功能,我們來定義兩個 CMP 實體 bean(Dept 和 Employee)和Session bean(HRSystem),Dept 和 Employee 是一對多的關係(Dept 有許多 Employee),HRSystem 可存取它們。

以下是完成這部分的步驟︰

  1. 使用 ejb.bean 標示來宣告 EJB 元件的結構
  2. 使用 ejb.home 和 ejb.interface 標示來宣告需生成的 home 和遠端介面的名稱
  3. 指定類別層級物件關係(Object Relation,OR)映射︰用供應商特有的標示來把實體映射到表中
  4. 使用 ejb.finder 標示來指定實體 Bean 的 finder 方法
  5. 使用 ejb.create 標示來標示 create 方法
  6. 使用 ejb.pk-field 來標示主鍵 cmp 欄位
  7. 標示持久的欄位
  8. 設定供應商特有的 cmp 欄位的 OR 映射
  9. 使用 ejb.interface 來把方法新增到介面中
  10. 使用 ejb.relation 標示來設定兩個實體之間的關係
  11. 設定兩個實體之間的 OR 關係映射
  12. 使用Session bean
  13. 使用 ejb.ejb-ref 標示來加入從Session到實體的 EJB 參照
  14. 使用 XDoclet Ant 標示 ejbdoclet 來生成 EJB 支援文件
XDoclet 和 EJB︰ejb.bean

ejb.bean 使您能夠指定 bean 的類型。我將講述的第一個 bean 是 CMP 2.0 實體 bean。您需要指定它是 CMP bean,也就是說使用 cmp-version 2.x,命名它的模式名,指定它的主鍵(如果它不使用復合主鍵)和主鍵類型。以下是用 Dept bean 說明的用於 ejb 的 Xdoclet 標示︰

  /**...  * @ejb.bean  *    type="CMP"  *    cmp-version="2.x"  *    name="DeptBean"  *    schema="Dept"  *    local-jndi-name="DeptBean"       *    view-type="local"  *    primkey-field="id"  *  *   * @ejb.pk                  class="java.lang.Integer"  *     */ public abstract class DeptBean implements EntityBean {      

請注意,主鍵類別由標示 class 屬性來指定。這些名稱與您所期望的名稱非常接近。這是相當顯然的,因為相應的 metadata 與您在 ejb-jar.xml 文件中發現的東西相匹配。view-type 參數可能不是顯然的。view-type 參數指定這將是本地 bean、遠端 bean 或兩者。以上 XDoclet 標示將生成 EJB 部署描述子(ejb-jar.xml)中的以下元素︰

       <entity >          <description>This entity bean represents a Department of Employees.          </description>           <ejb-name>DeptBean</ejb-name>          ...           <ejb-class>ejb.DeptBean</ejb-class>          <persistence-type>Container</persistence-type>          <prim-key-class>java.lang.Integer</prim-key-class>          <reentrant>False</reentrant>          <cmp-version>2.x</cmp-version>          <abstract-schema-name>Dept</abstract-schema-name>          ...          <primkey-field>id</primkey-field>       

請注意,<primkey-field> 指定了用於主鍵的單個 cmp 欄位。如果您使用復合主鍵,那麼您需要建立主鍵類別。XDoclet 可以容易地為您生成主鍵類別。

您從不直接指定 EJB 元件的類別,這也是值得注意的。解析 Java 程式碼的 XDoclet 可以存取解析樹,所以它已經知道 package 名和類別名稱。這也意味著如果您變更了名稱或 package 名,那麼您不必手動地更新部署描述子。

XDoclet 和 EJB 技術︰ejb.home 和 ejb.interface 標示

XDoclet 將生成類別的 home 和介面。您可以使用 ejb.homeejb.interface 標示來命名將被生成的介面。或者您可以讓 XDoclet 按照您指定的模式來命名。在這個範例中,您在類別文件中指定本地 home 和本地介面的名稱。

 /**...  * @ejb.home generate="local" local-class="ejb.DeptHome"  * @ejb.interface generate="local" local-class="ejb.Dept"  *  ...  */ public abstract class DeptBean implements EntityBean {      

請注意,您可以使用 ejb.homeejb.interface 來指定生成 local、remote 或 both。以上程式碼將導致生成 ejb.DeptHome home 介面和本地介面 ejb.Dept。此外,根據以上標示,以下元素將被定義在 ejb-jar.xml 部署描述子中。

       <entity >          ...           <ejb-name>DeptBean</ejb-name>           <local-home>ejb.DeptHome</local-home>          <local>ejb.Dept</local>          ...     
XDoclet 和 EJB 技術︰ejb.persistence 標示

EJB 規範未定義標準的 OR 映射。但是多數實作 EJB CMP CMR 的應用程式伺服器使用相似的原則,即把實體映射到類別,把欄位映射到列。DeptBean EJB 元件將被映射到 TBL_USER 表。

在較舊的 XDoclet 版本中,每個供應商實作都有它們自己的 EJB CMP CMR 支援,但是它們似乎都為相同的目的選擇不同的標示名。為了指定映射實體的表名,較新的 XDoclet 版本定義了 ejb.persistence 標示。

我注意到 Resin XDoclet 範本尚不支援新的 ejb.persistence 標示。我加入了支援但是新範本沒有被提交到專案中。實際上,為加入對新標示的支援而修改該範本是很容易的。

以下程式碼使用供應商特有的 Resin 映射和中立於供應商的方式把資料庫表映射到實體。目前,並不是所有的供應商產品都受到新標示的支援。如果供應商的範本已被更新以支援 ejb.persistence,那麼供應商特有的映射標示是不必要的。

 /**...  * @resin-ejb.entity-bean     sql-table="DEPT"  * @ejb.persistence           table-name="TBL_USER"  *  ...  */ public abstract class DeptBean implements EntityBean {      

以上標示生成供應商特有的映射文件中的以下程式碼。以 Resin 為例,程式碼如下。

     <!-- generated from ejb.DeptBean -->         <entity>             <ejb-name>DeptBean</ejb-name>             <sql-table>DEPT</sql-table>              ...     

請注意,這個範例只適用於這些應用程式伺服器,但是您也可以使用許多其它伺服器。目前,XDoclet 支援 Orion、Pramati、IBM WebSphere、BEA WebLogic、JRun、JBoss 和幾個其它產品。

並不是每個實作都有最好的支援和最新的範本。它是開放式原始碼,所以各種支援有所不同。但是,為適用於其它供應商而更新現有的範本是相當容易的。

Macromedia 的 JRun 為簡化開發而支援範本本身,也就是說,這個商業公司支援和更新 XDoclet 範本和模組。我希望這成為潮流。但是,修改範本和建立模組是相當容易的,有許多範例可供您參考。

XDoclet 和 EJB 技術︰ejb.finder

為了定義實體 bean 的 finder 方法,您只需在類別層級上加入 ejb.finder 標示,這個標示識別資料了 finder 方法特性符及其 EJB-QL 查詢,如下所示︰

 /**  *   * @ejb.finder  *    signature="Dept findByDeptName(java.lang.String name)"  *    unchecked="true"  *    query="SELECT OBJECT(dept) FROM Dept dept where dept.name = ?1"  *    result-type-mapping="Local"  *  * @ejb.finder  *    signature="Collection findAll()"  *    unchecked="true"  *    query="SELECT OBJECT(dept) FROM Dept dept"  *    result-type-mapping="Local" *  */ public abstract class DeptBean implements EntityBean {  

這將定義生成的 home 中的兩個 finder 方法和 EJB 部署描述子中的 <query> 元素定義,如下所示︰

 /*  * Generated by XDoclet - Do not edit!  */ package ejb;  /**  * Local home interface for DeptBean.  */ public interface DeptHome    extends javax.ejb.EJBLocalHome {    ...     public ejb.Dept findByDeptName(java.lang.String name)       throws javax.ejb.FinderException;     public java.util.Collection findAll()       throws javax.ejb.FinderException;     public ejb.Dept findByPrimaryKey(java.lang.Integer pk)       throws javax.ejb.FinderException;  }  
       <entity >          <description>This entity bean represents a Department of Employees.</description>           <ejb-name>DeptBean</ejb-name>           <local-home>ejb.DeptHome</local-home>          <local>ejb.Dept</local>            ...            <query>             <query-method>                <method-name>findByDeptName</method-name>                <method-params>                   <method-param>java.lang.String</method-param>                </method-params>             </query-method>             <result-type-mapping>Local</result-type-mapping>             <ejb-ql>SELECT OBJECT(dept) FROM Dept dept where dept.name =?1             </ejb-ql>          </query>          <query>             <query-method>                <method-name>findAll</method-name>                <method-params>                </method-params>             </query-method>             <result-type-mapping>Local</result-type-mapping>             <ejb-ql>SELECT OBJECT(dept) FROM Dept dept</ejb-ql>          </query>       <!-- Write a file named ejb-finders-DeptBean.xml if you        want to define extra finders. -->       </entity>  
XDoclet 和 EJB︰ejb.create 標示

ejb.create 標示被用來識別資料 create 方法。所有的 create 方法(ejbCreateXXX)使 XDoclet 在生成的 home 中生成相應的 create 方法。以下是使用 ejb.create 標示的範例。

 public abstract class DeptBean implements EntityBean {         /**     *     * @ejb.create-method     */     public Integer ejbCreate(String name)                                                    throws CreateException {         setName(name);                  return null;     }  

以上 ejb.create 標示將導致 XDoclet 在 home 中生成以下 create 方法。

 /*  * Generated by XDoclet - Do not edit!  */ package ejb;  /**  * Local home interface for DeptBean.  */ public interface DeptHome    extends javax.ejb.EJBLocalHome {    ...    public ejb.Dept create(java.lang.String name)       throws javax.ejb.CreateException;     ...  }  
XDoclet 和 EJB 技術︰ejb.pk-field

ejb.pk-field 把 CMP 欄位標示為作為復合鍵的一部分的參與者。在這個範例中,您不需要它。但是,如果 DeptBean 有復合鍵,那麼您需要它。

        /**         * This is a cmp field.         * And it is the primary key.         *         * @ejb:pk-field         */         public abstract Integer getId();     public abstract void setId(Integer id);      
XDoclet 和 EJB 技術︰ejb.persistent-field

ejb.persistent-field 標示把 getter 方法標示為 CMP 欄位宣告的一部分。這將導致在 EJB 部署描述子中生成相應的 cmp-field 元素︰

        /**         * This is a cmp field.          * And it is the primary key.         *         * @ejb.persistent-field         ...         */         public abstract Integer getId();     public abstract void setId(Integer id);      

以上 XDoclet 標示將導致以下 <cmp-field> 被生成。

       <entity >          <description>This entity bean represents a Department of                        Employees.</description>           <ejb-name>DeptBean</ejb-name>            ...          <cmp-field >             <description>This is a cmp field.</description>             <field-name>id</field-name>          </cmp-field>      

請注意,透過使用 ejb.persistent-field,您無需指定欄位名。因此,如果您變更了 CMP 欄位名,即 getTheIDsetTheID,那麼您不必手動地使部署描述子同步。這是 XDoclet 的優點;它知道宣告的上下文。這些是簡化程式碼重構的功能。

XDoclet 和 EJB 技術︰用供應商特有的標示來設定供應商特有的 OR 映射(或不)

ejb.persistence 標示使您能夠指定從 cmp 欄位到表的列的映射。以下是把 ejb.persistence 標示用於 DeptBean 的識別資料 CMP 欄位的範例︰

        /**          * This is a cmp field. The cmp field is read only.         * And it is the primary key.         *         * @ejb.pk-field         * @ejb.persistent-field         * @ejb.interface-method view-type="local"         * @ejb.persistence         column-name="DEPTID"         * @resin-ejb.cmp-field     sql-column="DEPTID"         */         public abstract Integer getId();     public abstract void setId(Integer id);     

請注意,以上 ejb.persistence 把識別資料 CMP 欄位映射到 DEPTID 列。(在這個範例中,我還使用供應商特有的映射標示,因為並不是所有的供應商範本都支援新的 ejb.persistence 標示。)以上 XDoclet 標示將在供應商特有的 RDBMS 映射文件中生成以下映射。

Resin 的 resin.ejb

     <!-- generated from ejb.DeptBean -->         <entity>             <ejb-name>DeptBean</ejb-name>             <sql-table>DEPT</sql-table>              <cmp-field>                 <field-name>id</field-name>                 <sql-column>DEPTID</sql-column>             </cmp-field>      
XDoclet 和 EJB 技術︰ejb.interface 標示

為了宣告實作中的方法應當出現在遠端或本地介面中,請使用 ejb.interface 標示,如下所示︰

        /**       ...         * @ejb:interface-method view-type="local"         ...         */         public abstract Integer getId();     public abstract void setId(Integer id);      

ejb.interface 標示指定 view-type 參數。view-type 參數告訴 XDoclet 應當在哪裡生成方法︰local、remote 或 both,分別表示本地介面、遠端介面或兩種介面。以上 XDoclet 標示將導致在本地介面中個標示相當簡單,因為屬性名非常接近地匹配 EJB 部署描述子中的相應的元素名。

以下範例設定了一(DeptBean)對多(EmployeeBean)的關係。

 ... public abstract class DeptBean implements EntityBean {          ...                 /**         * @return return employees in this department         *         * @ejb.interface-method view-type="local"         *         * @ejb.transaction type="Required"         *         * @ejb.relation         *    name="EmployeesInADepartmentRelation"         *    role-name="DeptHasEmployees"         *    target-role-name="EmployeeInADept"         *    target-cascade-delete="no"         */                 public abstract Collection getEmployees();         /** @ejb.interface-method view-type="local" */     public abstract void setEmployees(Collection collection);              ...     

請注意,該關係被命名。然後您描述該關係中 DeptBean 的角色。您還可以用 target-cascade-delete 屬性來指定在刪除這個 bean 的案例的時候也刪除該關係中它的所有的子元素。請注意,您還指定 target-role-name。實際上以 target 開頭的屬性只有適用於單向關係。由於這個範例是雙向的,您可以不包括它。關係的多重性是基於 cmr 欄位中 getter 方法的返回類型。因為 getEmployees 返回 Collection,所以 XDoclet 推斷 Employee 有較大的多重性。

無論薄煎餅是多麼地平,它總有兩面,這類似於 CMR 關係。以下是 CMR 關係的另一面。

 public abstract class EmployeeBean implements EntityBean {        ...      /**     * @return Return the group this user is in.     *     * @ejb.interface-method view-type="local"     *     * @ejb.transaction type="Required"     *     * @ejb.relation     *    name="EmployeesInADeptartmentRelation"     *    role-name="EmployeeInADept"     *    target-role-name="DeptHasEmployees"     *     */     public abstract Dept getDept();     /** @ejb.interface-method view-type="local" */     public abstract void setDept(Dept dept);       

同樣,目標屬性是一點多餘的訊息,實際上這點訊息只有適用於單向關係,但是它在這裡是額外。請注意,關係名與關係的 Dept 一側的關係名相同。這被 XDoclet 用來使關係的兩個成員相關。

以上 ejb.relation 標示將生成部署描述子中的以下項目。

    <!-- Relationships -->    <relationships >       <ejb-relation >          <ejb-relation-name>EmployeesInADeptartmentRelation</ejb-relation-name>           <ejb-relationship-role >             <ejb-relationship-role-name>EmployeeInADept</ejb-relationship-role-name>             <multiplicity>Many</multiplicity>             <relationship-role-source >                <ejb-name>EmployeeBean</ejb-name>             </relationship-role-source>             <cmr-field >                <cmr-field-name>dept</cmr-field-name>             </cmr-field>          </ejb-relationship-role>           <ejb-relationship-role >             <ejb-relationship-role-name>DeptHasEmployees</ejb-relationship-role-name>             <multiplicity>One</multiplicity>             <relationship-role-source >                <ejb-name>DeptBean</ejb-name>             </relationship-role-source>             <cmr-field >                <cmr-field-name>employees</cmr-field-name>                <cmr-field-type>java.util.Collection</cmr-field-type>             </cmr-field>          </ejb-relationship-role>        </ejb-relation>    </relationships>       

使用 XDoclet 時,您必須為關係的每一側編寫一個帶有兩個屬性的標示。請比較這個和您不使用 XDoclet 而必須編寫的 26 行 XML。我不明白為什麼有人不想把 XDoclet 用於 EJB 開發。

XDoclet 和 EJB 技術︰設定兩個實體之間 OR 關係映射

至此,您已定義了兩個 bean 之間的關係。這總共沒有多少 bean,除非您加入有關底層資料庫表的關係映射的詳細資訊。生成的供應商實作需要這些映射,這樣它就知道如何實作關係。

很不幸,XDoclet 沒有指定 OR 關係映射的共同的方式。目前,您必須學習每一組供應商特有的標示。以下範例使用中立於供應商的標示和 Resin 的 OR 關係映射標示。

     /**     * @return Return the group this user is in.     *         ...     *     * @resin-ejb.relation    sql-column="DEPTID"     *      *     */     public abstract Dept getDept();     /** @ejb:interface-method view-type="local" */     public abstract void setDept(Dept dept);      

resin-ejb.relation 標示的一個參數(sql-column)指向關係中的列,即外鍵。這裡假定外鍵指向關係的另一側的主鍵。

某些供應商特有的映射帶有兩個參數︰外鍵和外鍵指向的另一個表(Dept)中的列名稱。為了看清楚這一點,我們來顯示這些表的實際的 SQL DDL。

 CREATE TABLE DEPT (     DEPTID      INT         IDENTITY    PRIMARY KEY,     NAME        VARCHAR (80) );   CREATE TABLE EMPLOYEE (     EMPID       INT         IDENTITY     PRIMARY KEY,     FNAME       VARCHAR (80),     LNAME       VARCHAR (80),     PHONE       VARCHAR (80),     DEPTID      INT,     CONSTRAINT  DEPTFK FOREIGN KEY (DEPTID)                        REFERENCES DEPT (DEPTID) );     

employee 表的外鍵 DEPTID 參照部門的主鍵 DEPTID

XDoclet 和 EJB 技術︰使用Session bean

既然您已定義了一些實體 bean,我們來建立存取實體 bean 的Session bean(如下所示)︰

 /**  * Provides a session facade that works with cmp/cmr from EJB 2.0   * based entity beans.  *    * This bean must uses container-managed transactions.   * It works with the others entity beans to provide authentication services.  * This bean does not maintain any state; thus, it can be stateless.  *  *  * @ejb.bean name="HRSystem" type="Stateless"  *     local-jndi-name="HRSystem"              *   *   * @ejb.ejb-ref ejb-name="DeptBean" view-type="local"  * @ejb.ejb-ref ejb-name="EmployeeBean" view-type="local"  *   */ public class HRSystemBean implements SessionBean {      /**      * Get a list of all the depts.      *      * @return All the group names.      *        * This method is part of the ejb interface.      * @ejb.interface-method view-type="local"      * @ejb.transaction type="Required"      */     public String[] getDepts() {         ArrayList deptList = new ArrayList(50);         Collection collection = LocalFinderUtils.findAll("DeptBean");         Iterator iterator = collection.iterator();         while (iterator.hasNext()) {                 Dept group = (Dept) iterator.next();                 deptList.add(group.getName());         }         return (String[]) deptList.toArray(new String[deptList.size()]);     }      

請注意,Session bean 使用相同的 ejb.bean 標示,但是它不指定 CMP,而指定這是無狀態 bean(如下所示)︰

  /**  * Provides a session facade that works with cmp/cmr from EJB 2.0   * based entity beans.  *    *  *  * @ejb.bean name="HRSystem" type="Stateless"  *     local-jndi-name="HRSystem"              *   ...  *   */ public class HRSystemBean implements SessionBean {       ...       

以上程式碼導致 XDoclet 生成以下清單。

    <enterprise-beans>        <!-- Session Beans -->       <session >          <description>provides a session facade that works with           cmp/cmr from EJB 2.0 based entity beans.</description>            <ejb-name>HRSystem</ejb-name>           <home>ejb.HRSystemHome</home>          <remote>ejb.HRSystem</remote>          <local-home>ejb.HRSystemLocalHome</local-home>          <local>ejb.HRSystemLocal</local>          <ejb-class>ejb.HRSystemBean</ejb-class>          <session-type>Stateless</session-type>          <transaction-type>Container</transaction-type>          ...     

我很高興,XDoclet 生成這個清單而我不必編寫它。     
XDoclet 和 EJB 技術︰使用 ejb.ejb-ref 標示來加入從Session到實體的 EJB 參照

為使Session bean 存取您建立的實體 bean,您需要加入實體 bean 的參照。為此,您可以使用 ejb.ejb-ref 標示(如下所示)︰

 /**  ...   * @ejb.ejb-ref ejb-name="DeptBean" view-type="local"  * @ejb.ejb-ref ejb-name="EmployeeBean" view-type="local"  *   */ public class HRSystemBean implements SessionBean {      

請注意,短短的兩行標示程式碼生成以下 ejb-local-ref 標示清單。

    <enterprise-beans>        <!-- Session Beans -->       <session >          ...          <ejb-name>HRSystem</ejb-name>          ...           <ejb-local-ref >             <ejb-ref-name>ejb/DeptBean</ejb-ref-name>             <ejb-ref-type>Entity</ejb-ref-type>             <local-home>ejb.DeptHome</local-home>             <local>ejb.Dept</local>             <ejb-link>DeptBean</ejb-link>          </ejb-local-ref>          <ejb-local-ref >             <ejb-ref-name>ejb/EmployeeBean</ejb-ref-name>             <ejb-ref-type>Entity</ejb-ref-type>             <local-home>ejb.EmployeeHome</local-home>             <local>ejb.Employee</local>             <ejb-link>EmployeeBean</ejb-link>          </ejb-local-ref>       ...      

兩行 XDoclet 標示對應於部署描述子中的 15 行 XML,當(如果)您重構包括的類別時,部署描述子可保持同步。這是很顯著的。

XDoclet 和 EJB 技術︰使用 XDoclet Ant 任務 ejbdoclet

既然您定義了所有的 bean,您需要修改 Ant 建置腳本(如下所示),這樣才能使用 XDoclet JavaDoc 標示來生成從屬文件︰

   <target name="ejbdoclet" >                   <taskdef             name="ejbdoclet"             classname="xdoclet.modules.ejb.EjbDocletTask"             classpathref="xdocpath"             />                  <ejbdoclet                   ejbspec="2.0"                               mergeDir="${src}"             destDir="${gen.src}"          >                      <fileset dir="${src}">                 <include name="ejb/*Bean.java" />             </fileset>                          <localinterface/>             <localhomeinterface />             <remoteinterface/>             <homeinterface />                                  <entitypk/>                                       <deploymentdescriptor                           destdir="META-INF"                       destinationFile="ejb-jar.xml"                       validatexml="true" />                                                  <deploymentdescriptor                           destdir="${WEBINF}"                       destinationFile="cmp-xdoclet.ejb"                      validatexml="true" />                                <resin-ejb-xml destDir="${WEBINF}"/>                       </ejbdoclet>     

以上 ant 腳本用 taskdef 任務定義了 ejbdoclet 工作。然後它使用 ejbdoclet 任務來生成各種支援文件。它使用巢狀的 fileset 來選擇 XDoclet 將使用的原始碼文件(在這裡,所有以 Bean 為結尾的類別)。接著它使用以下子任務︰localinterfacelocalhomeinterfaceremoteinterfacehomeinterfaceentitypk 來分別生成本地介面、本地 home、遠端介面、遠端 home 和主鍵類別(這個範例沒有主鍵類別,因為實體使用簡單的一個欄位的鍵)。

xPetstore

本技術教學只有只有介紹了一小部分功能

我只有介紹了 XDoclet 和 Ant 的一小部分功能。EJB 技術本身就有許多令人眼花繚亂的功能和支援。XDoclet 為您的工具箱提供了強大的工具。XDoclet 模組簡化了 Web 開發。Ant 是強大的工具,XDoclet 是如何為簡化 Web 開發而擴充 Ant 的範例。我簡要地介紹了 Ant。對有些人來說,這已足夠了;對其它人來說,已準備了更詳細的描述。

請參閱參考資料部分,其中有使用 XDoclet 的其它範例。

本技術教學只有只有講述了一小部分功能,xPetstore 範例介紹了更多的細節,但是仍沒有包括全部功能。

xPetstore

xPetstore 使用 XDoclet 重新實作了 Sun Microsystems 的 PetStore。它帶有很好的範例,您可以從中獲取有關如何使用 XDoclet 的有用的訊息。xPetstore 說明了使用 Ant 和 XDoclet 來建置 WODRA(Write Once,Deploy and Run Anywhere(一次編寫,處處部署和執行))J2EE 應用程式。目前有兩個版本的 xPetsore。

xPetstore 的第一個版本是使用 EJB 元件(CMPCMR 2.0)、JSP、Struts 和 Sitemesh 的純粹的 J2EE 版本。xPetstore 的第二個版本是使用 Velocity、WebWork、Sitemesh、POJO 和 Hibernate 的基於 Servlet 的解決方案。XDoclet 為 Struts、WebWork 和 Hibernate 提供了定製處理程式、範本和 Ant 工作,這類似於本技術教學中說明的把 XDoclet 用於 EJB、 custom tag 和 Servlet。xPetstore 說明了如何使用 XDoclet 來開發 EJB(CMP CMR 2.0)、 custom tag (標示庫)、JSP、Struts、WebWork、Servlet 過濾器和 Hibernate。在您開始在自己的專案中使用 XDoclet 之前,請看看 xPetstore。

xPestore 已在以下平臺上被部署和測試過︰
作業系統︰

  • Linux
  • Windows
應用程式伺服器︰
  • JBoss 3.x
  • WebLogic 7.x
資料庫︰
  • Hypersonic SQL
  • PostgreSQL
  • SapDB
  • MySQL
  • Oracle
  • MS SQL Server
xPetstore 說明了以下 XDoclet 的使用︰生成 EJB 2.0 文件,EJB 的 home 和商業介面(本地和遠端),EJB 部署描述子(ejb-jar.xml),供應商特有的部署描述子,ejb 設計模式,J2EE 1.3 功能部件(如 CMP 2.0 和 CMR)的使用,生成 Servlet、Web 過濾器和 JSP 標示庫的 Web 部署描述子。

此外,這個範例說明了如何生成 Struts 部署描述子和 Webwork 部署描述子,如何使用類似 Velocity 的技術,如何使用類似 Hibernate 的持久層,還有 XDoclet 合併點的使用。所有的元件都用 JUnitEE 測試框架測試過。

xPetstore 是學習如何使用 XDoclet 模組的很好的參考資料。

總結

透過講述三個使用 XDoclet 的循序漸進的技術教學,本技術教學為 J2EE 開發人員說明了如何使用 XDoclet 來加速開發。

透過使用屬性導向程式化,XDoclet 簡化了導向元件開發中的連續整合和重構。透過生成部署描述子和支援程式碼,XDoclet 使您能夠大大地減少開發時間從而把重點放在應用程式邏輯程式碼上。

您學會了如何把 XDoclet 用於 Servlet、 custom tag 和 EJB Session和實體 bean。

XDoclet 透過解析相關文件來生成這些相關的支援文件,這類似於 JavaDoc 引擎解析原始碼來建立 JavaDoc 文件。與 JavaDoc 相似,XDoclet 不只有可以存取您加入的額外的標示,還可以存取原始碼的結構。XDoclet 把這個資料和上下文的層次結構樹應用於範本。它使用所有這些來生成單調的支援文件。

為了加速開發,XDoclet 比相應的部署描述子更簡短,使原始碼與部署描述子和支援文件同步從而支援重構,最後生成許多支援文件;以 EJB 為例,一個原始碼文件對五個生成的文件是常見的。

參考資料

如果您是 EJB 技術的初學者︰

  • 請看看 developerWorks Java 技術專區上 Brett McLaughlin 的 EJB 最佳做法專欄。
  • 請學習分五部分的系列中的第一個技術教學︰Introduction to container-managed persistence and relationships(範例程式碼使用 XDoclet),作者是 Rick Hightower(developerWorks,2002 年 3 月)。
  • The Developer's Guide to Understanding EJB 2.0(更深刻地理解該規範)
如果您是 custom tag 的初學者︰
  • JSP taglibs: Better usability by design,作者是 Noel J. Bergman(developerWorks,2001 年 12 月)
  • J2EE 技術教學︰ custom tag 技術教學
如果您想瞭解有關 Ant 的更多的詳細資訊︰
  • Ant 初級技術教學
  • 圖書︰Java Tools for Extreme Programming covers Ant
如果您想學習使用 Struts 和 XDoclet︰
  • Mastering Struts(使用 XDoclet 的 Struts 技術教學)
如果您需要移植到 JBoss、Resin 和其它 EJB 伺服器的更複雜的 EJB 範例(例如,多對多關係和主鍵類別),請下載有關使用 EJB 技術及 CMP/CMR 2.0 的分五部分的系列的原始碼。
  • 更多 XDoclet 範例

特別感謝 XDoclet 小組,因為他們開發了這個有用的工具。

原创粉丝点击