Velocity空間

          快速構建JAVA應用
          隨筆 - 11, 文章 - 15, 評論 - 5, 引用 - 0
          數據加載中……

          CHATPER 12 在Servlets里使用 Velocity

           

          通過前面章節的學習,我們了解到Velocity主要用作MVC框架View組件的腳本語言。ServletsJavaMVC框架里用得最多的編程方式。通過把Servlets用作控制器(controller),Velocity用作顯示層(View),把ServletsJavaBeans作用模型(model),這樣就創建了一個理想的Web開發全面解決方案。在這一章里,我們將學習如何在Servlets里使用Velocity。

          使用Servlets

          在開始學習這章內容之前,你必須充分理解Servlets是什么?以及它是如何工作的?如果對此你已經很熟悉了,那么你可以直接跳到VelocityServlet擴展Servlets”節進行深入學習。

          在動態Web頁面開發的早期,服務端語言,比如ASP、JSP等主要用于在HTML頁面里嵌入服務器端執行的代碼。當瀏覽器請求這個頁面的時候,服務器立即對所請求的ASP/JSP頁面進行解析,執行里面的語句,并向用戶返回一個純粹的HTML頁面,有時還要附帶返回客戶端執行的JavaScriptVBScript腳本。混合HTML和服務器端代碼并不容易,而且,服務器端腳本語言還要受到一些限制,比如服務器平臺支持、服務器支持等比較有限。

          Servlets把代碼從HTML中提取出來,把它放到服務器上,這樣,你就可以使用全功能的Java語言進行Servlets邏輯開發。Servlets也能進行分布式處理,而且可以使用模板來向用戶提供信息顯示。

          Servlets的通用格式

          在進行深入學習之前了解傳統的Servlets結構是非常有必要的。Listing 12.1顯示了一個簡單的Servlets

          import java.io.*;

          import java.sql.*;

          import javax.servlet.*;

          import javax.servlet.http.*;

          import javax.naming.*;

          public class ViewAccount extends HttpServlet {

          public void init() throws ServletException {

          }

          public void doGet(HttpServletRequest request,

          HttpServletResponse response)

          throws IOException, ServletException {

          response.setContentType("text/html");

          PrintWriter out = response.getWriter();

          out.println("<HTML>");

          out.println("</HTML>");

          } catch (SQLException e) {

          e.printStackTrace();

          }

          }

          public void doPost(HttpServletRequest request,

          HttpServletResponse response)

          throws ServletException, IOException {

          doGet(request, response);

          }

          }

          Listing 12.1 傳統servlet代碼

          當有一個web瀏覽器請求達到時,一般情況下,請求的方式為POSTGET。在上面的代碼中你可以看到,其中有一些方法將用于處理這兩種請求,分別為doPost()doGet()方法,其中doPost()方法只是簡單委派給了doGet()方法。在doGet()方法內部有兩個重要的變量:requestresponse。request對象用于把用戶信息轉換成servlet能夠理解的信息。特別的,用戶在HTML窗體里的輸入將作為文本進行處理。response對象主要負責向用戶的瀏覽器返回標準的HTML頁面。response對象返回的信息包括HTMLXML和圖片。response對象也可以和PrintWriter對象一起協同工作,以用于寫入信息并返回給用戶。

          Servlets不能使用普通的Web服務器作為主機,只能使用應用程序服務器(application servers)作為主機??梢宰鳛?/span>Servlets主機的軟件有Tomcat、JBOSS、ResinWEBlogic、apusic等。在很多情況下,應用程序服務器被用于和Web服務器一起協同工作,比如IISApache等。當Web服務器得到一個servlet請求時,這個請求被移交給應用程序服務器執行。應用程序服務器使用java編譯器來創建一個可執行的Servlets映像,這個Servlets映像將在JVM里執行。我們將在下一章演示這個話題的示例。

          VelocityServlet擴展Servlets

          在某種程度上,你可以使用Velocity引擎和servlet,通過Velocity模板語言書寫代碼來為用戶創建信息輸出。為了讓Servlets的使用更容易,我們將通過使用一個名叫VelocityServlet的基類和名叫handleRequest()的方法來代替傳統ServletsHttpServletdoGet()/doPost()方法。

          handleRequest()方法傳遞三個參數HttpServletRequest、HttpServletResponseContext來響應GET/POST請求。HttpServletRequestHttpServletResponse對象和doGet()doPost()方法接受的是同一個對象(代碼見Listing 12.1)。上下文對象是一個用于在Servlets里使用的Velocity引擎的上下文。我們將在上下文對象里為用于向用戶顯示響應的Velocity模板放置信息。

          handleRequest()方法返回一個模板對象,該方法自動把傳遞進來的上下文對象進行合并。因而,在handleRequest()方法里的代碼,主要完成設置上下文和合并返回的Velocity對象等操作。如果返回的模板對象為null,這些代碼就不能完成合并操作。在這種情況下,和傳統的Servlets代碼一樣,這些代碼將向用戶瀏覽器返回一些放置在response對象里的PrintWriter對象里的東西。

          基本的Velocity Servlet代碼

          handleRequest()方法示例代碼見Listing 12.2,用于向瀏覽器簡單輸出字符串。

          import java.util.Vector;

          import javax.servlet.http.HttpServletRequest;

          import javax.servlet.http.HttpServletResponse;

          import org.apache.velocity.Template;

          import org.apache.velocity.context.Context;

          import org.apache.velocity.servlet.VelocityServlet;

          import org.apache.velocity.exception.*;

          public class VelocityServletExample extends VelocityServlet {

          public Template handleRequest( HttpServletRequest request,

          HttpServletResponse response,

          Context context ) {

          Vector v = new Vector();

          v.add("one");

          v.add("two");

          v.add("three");

          context.put("list", v);

          Template template = null;

          try {

          template = getTemplate("displaylist.vm");

          } catch( Exception e ) {

          PrintWriter out = response.getWriter();

          out.println("Error getting template");

          }

          return template;

          }

          }

          Listing 12.2 A Velocity servlet example.

          當瀏覽器調用Listing 12.2里的代碼時,初始化一個Velocity對象,并在里面放置了三個字符串對象。Velocity對象通過下面的語句被放入上下文中。

          context.put("list", v);

          注意,我們在上下文里放置的是Vector對象實體(java.util.Vector)。接著,聲明了一個模板對象,并使用getTemplate(String)方法來從服務器的磁盤上定位和加載模板。如果在獲取模板文件的過程中出現錯誤時,response對象里的PrintWriter被獲得,并向其中寫入錯誤信息。最后一個操作是返回模板對象。如果某些錯誤導致這個對象為null,系統將不能完成合并操作;否則,從服務器讀入的模板將和上下文一起被合并。Listing 12.3為這個示例所使用的Velocity模板。

          <?xml version="1.0" encoding="ISO-8859-1" ?>

          <list>

          #foreach( $value in $list )

          <number>$value</number>

          #end

          </list>

          Listing 12.3 The displaylist.vm Velocity code.

          Listing 12.3里的模板不是HTML格式的,我們使用XML來演示Velocity模板并不僅限于使用HTML格式。了解這個示例是如何執行的非常重要。還記得“list”關鍵字和Vector對象關聯,在模板里的Velocity模板語言代碼通過#foreach循環指令使用Vector。回想一下在List對象(如Vector)里使用#foreach指令的定義我們就可以發現,迭代器(iterator)自動提取并用于獲取所有在List(在這種情況下還可以是Vector)里的獨立對象。

          所有來自Vector的每一個對象都被提出并放置到引用$value處。既然字符串不是混合對象,我們就可以簡單地輸出它的值(在<number>元素標記里)。Figure 12.1演示了執行結果。


          Figure 12.1
          Output from the Velocity template.

          HttpServletRequestHttpServletResponse對象被傳遞給Servlets代碼,同時被放置在兩個上下文常量對象中:

          VelocityServlet.REQUEST—Stored as req

          VelocityServlet.RESPONSE—Stored as res

           

          每一個對象都能通過直接調用引用名(reqres)的方式用于Velocity模板中。

          #set($username = $req.getParameter('username'))

          創建一個MVC應用程序

          我們的第一個Velocity示例相當簡單,現在我們來搞一個稍復雜一點的示例。在這一節里,你將使用Servlets來構建一個MVC應用程序,其中:Servlets用作控制器(controller)、Velocity模板作用視圖(view)、JavaBeans作用模型(model)。這個應用程序是一個具有以下四個功能的CD數據庫:

          增加一個CD,同時返回一個thank-you響應

          為特定的CD增加歌曲track,同時返回一個thank-you響應

          通過特定的藝術家返回其所有的CD

          獲取特定CD的全部歌曲track

          Figure 12.2顯示了增加一個新CD時的情況;Figure 12.3顯示了當你使用特定的藝術家搜索CD時的情況;Figure 12.4顯示了一個特定CD的所有歌曲時的情況。


          Figure 12.2
          Adding a new CD.


          Figure 12.3
          Listing all CDs for an artist.

          數據庫結構

          這個應用程序使用了一個數據庫來保存CD和所有歌曲的信息。在這個測試環境,我們選擇MYSQL數據庫來存在數據,當然,你也可以選擇其他類型的數據庫。下面的SQL建表命令顯示了CD和歌曲表的數據結構。

          create table cd (

          id int not null primary key auto_increment,

          title varchar(128),

          artist varchar(64),

          tracks int);

          create table tracks(

          id int not null primary key auto_increment,

          cd_id int,

          name varchar(64),

          length varchar(16));


          Figure 12.4
          Listing all tracks on a CD.

          數據庫訪問

          我們的模型組件打算使用實體EJBs來訪問CD表和歌曲(tracks)表的數據,這些beanServlets被用于向用戶提供服務。在這個測試環境里,我們使用了Resin應用程序服務器。EJBs通過JNDI(Java名字和目錄接口)資源引用來訪問數據庫。Listing 12.4顯示了在應用程序服務器的配置文件中增加的 <resource-ref>元素。除了數據庫驅動和URL外,這個元素使用的是標準規范。如果你使用的是另外數據庫,你需要改變<init-param>元素以滿足需要。

          <resource-ref>

          <res-ref-name>jdbc/ProductsDB</res-ref-name>

          <res-type>javax.sql.ConnectionPoolDataSource</res-type>

          <init-param driver-name="org.gjt.mm.mysql.Driver"/>

          <init-param url="jdbc:mysql://localhost:3306/products"/>

          <init-param user=""/>

          <init-param password=""/>

          <init-param max-connections="20"/>

          <init-param max-idle-time="30"/>

          <init-param max-active-time="1"/>

          <init-param max-pool-time="1"/>

          <init-param connection-wait-time="1"/>

          </resource-ref>

          Listing 12.4 The resin.config resource text.

          模型(Model)代碼

          在這里,你已經創建了一個數據庫,同時包含了JNDI引用,現在,通過JNDI引用,你就可以訪問數據庫了。是時候考慮實體beans了,我們將把它用于訪問數據庫表里的數據。因為在我們的有兩個數據庫表,因此就需要構建兩個EJB。為了達到目的,讓我們利用Resincontainer-managed persistence (CMP)模式來創建bean,同時主要把精力集中在實現EJB本地實例上。意思是我們只處理CMP的本地接口,而不處理遠程接口,因此只需要很少的代碼。由于本書是關于Velocity的,故我們只展示這兩個bean的代碼,不作更多解釋,你可以在站點http://www.wiley.com/compbooks/gradecki處下載這個完整的示例代碼。下面就讓我們來看一看EJB文件的定義(Listing 12.5)。

          <ejb-jar>

          <enterprise-beans>

          <entity>

          <ejb-name>CDRecordBean</ejb-name>

          <local-home>cd.CDRecordHome</local-home>

          <local>cd.CDRecord</local>

          <ejb-class>cd.CDRecordBean</ejb-class>

          <prim-key-class>int</prim-key-class>

          <primkey-field>id</primkey-field>

          <persistence-type>Container</persistence-type>

          <reentrant>True</reentrant>

          <abstract-schema-name>CDTable</abstract-schema-name>

          <sql-table>cd</sql-table>

          <cmp-field><field-name>id</field-name></cmp-field>

          <cmp-field><field-name>title</field-name></cmp-field>

          <cmp-field><field-name>artist</field-name></cmp-field>

          <cmp-field><field-name>tracks</field-name></cmp-field>

          <query>

          <query-method>

          <method-name>findByArtist</method-name>

          </query-method>

          <ejb-ql>SELECT o FROM CDTable o WHERE o.artist like

          ?1</ejb-ql>

          </query>

          </entity>

          <entity>

          <ejb-name>TracksRecordBean</ejb-name>

          <local-home>cd.TracksRecordHome</local-home>

          <local>cd.TracksRecord</local>

          <ejb-class>cd.TracksRecordBean</ejb-class>

          <prim-key-class>int</prim-key-class>

          <primkey-field>id</primkey-field>

          <persistence-type>Container</persistence-type>

          <reentrant>True</reentrant>

          <abstract-schema-name>TrackTable</abstract-schema-name>

          <sql-table>tracks</sql-table>

          <cmp-field><field-name>id</field-name></cmp-field>

          <cmp-field><field-name>cd_id</field-name></cmp-field>

          <cmp-field><field-name>name</field-name></cmp-field>

          <cmp-field><field-name>length</field-name></cmp-field>

          <query>

          <query-method>

          <method-name>findByCdID</method-name>

          </query-method>

          <ejb-ql>SELECT o FROM TrackTable o WHERE o.cd_id=?1</ejb-ql>

          </query>

          </entity>

          </enterprise-beans>

          </ejb-jar>

          Listing 12.5 The CDRecordBean EJB file.

          Listing 12.5里的EJB文件展示了這兩個bean是如何定義的,其中包含了主鍵和每一個表的字段。這兩個實體bean都包含了一個<query>元素,它允許數據從表中取出并用于除主鍵以外的字段。為了讓你了解在Resin應用程序服務器里是如何書寫實體bean的,讓我們來看一下Listing 12.6里的CDRecordBean類。

          package cd;

          import javax.ejb.*;

          public abstract class CDRecordBean

          extends com.caucho.ejb.AbstractEntityBean {

          public abstract String getTitle();

          public abstract String getArtist();

          public abstract int getTracks();

          public abstract int getId();

          public abstract void setTitle(String title);

          public abstract void setArtist(String artist);

          public abstract void setTracks(int tracks);

          public abstract void setId(int id);

          public int ejbCreate(String title, String artist, int tracks)

          throws CreateException {

          setId(0);

          setTitle(title);

          setArtist(artist);

          setTracks(tracks);

          return 1;

          }

          public void ejbPostCreate(String title, String artist, int

          tracks) {

          // since there are no relations, this is empty.

          }

          }

          Listing 12.6 The CDRecordBean class.

          Listing 12.6里的CDRecordBean類繼承自AbstractEntityBean類,AbstractEntityBean類是Resin制造商定義的一個助手類。這個助手類提供了所有的用于讓實體bean實現空body的通用方法。事實上,把該助手類用于你的bean時,你只需要提供一個方法,從而減少代碼量和代碼混雜。另外,bean里剩余的代碼被用于定義與這個bean相關聯的表字段。Listing 12.7顯示了TracksRecordBean類。

          package cd;

          import javax.ejb.*;

          import java.sql.*;

          public abstract class TracksRecordBean

          extends com.caucho.ejb.AbstractEntityBean {

          public abstract int getId();

          public abstract int getCd_id();

          public abstract String getName();

          public abstract String getLength();

          public abstract void setId(int id);

          public abstract void setCd_id(int cd_id);

          public abstract void setName(String name);

          public abstract void setLength(String tracks);

          public int ejbCreate(int cd_id, String name, String length)

          throws CreateException {

          setCd_id(cd_id);

          setName(name);

          setLength(length);

          return 1;

          }

          public void ejbPostCreate(int cd_id, String name, String length) {

          // since there are no relations, this is empty.

          }

          }

          Listing 12.7 The TracksRecordBean class.

          我們在之前曾經提及,你可以為這些實體bean下載剩余的文件。所有的實體文件被放置在Resin 應用程序服務器主機的/classes目錄下。

          The View Code

          現在,你需要考慮一下如何使用Velocity作為你的腳本語言來進行輸出。這里將是你施展魔法的地方,為用戶提供令人激動的輸出。對這個應用程序來說,我們需要三個Velocity模板:

          thanks.vm—顯示thank-you信息的普通頁面

          displaycd.vm—向用戶顯示CD列表的頁面

          displaytracks.vm—顯示特定CD內所有歌曲的頁面

          首先,讓我們來看一下thanks.vm模板,見Listing 12.8

          <HTML>

          <HEAD>

          <TITLE></TITLE>

          <link rel="stylesheet" type="text/css" href="defaultpage.css">

          </HEAD>

          <BODY BGCOLOR="#F79C19" link="ffffff" alink="999999"

          vlink="ffffff" topmargin="0" leftmargin="0" marginheight="0"

          marginwidth="0">

          <BR>

          #if ($message)

          $message

          #end

          $thanks

          <img src="/images/tune_big.gif" height="250" width=283></td></tr>

          </table>

          </BODY>

          </HTML>

          Listing 12.8 The thanks.vm template.

          這個thanks.vm模板事實上是web框架集的一部分,它提供了所有可見的邊框。這個模板將被放置在main處,或放在body里,是web框架集的一部分。在這里,為了讓頁面更美觀,你需要提供適當的背景顏色和圖片。在這個模板的頂部是頁面背景的信息,后跟實際的Velocity元素。

          第一個元素為帶有$message引用的#if指令。$message引用用于顯示一個信息(當一個錯誤出現并且你需要讓用戶知道這個錯誤的時候)。這個信息只有在錯誤發生時才會被寫入上下文中,因此,程序沒有發生錯誤的時候,這個引用在上下文里是不存在的。如果我們不用#if指令對$message進行測試,那么在輸出中就有可能出現文本字符串“$message”,這看起來不太好,因此我們使用$if指令對$message引用是否包含有值進行測試,如果$message引用包含有值(意思是在上下文里可以找到這個引用),那么就顯示$message引用的值。

          在發生錯誤的情況下,你仍舊想感謝用戶辛苦輸入新的CD或歌曲。這個輸出就$thanks引用來產生。

           

          顯示CD

          我們的應用程序允許用戶通過特定的藝術家搜索數據庫里所有的CD,并得到一個這些CD的列表。在每一次顯示CD列表的時候都將顯示一個按鈕,以方便用戶可以單擊來列出特定CD的歌曲列表。Listing 12.9里的displaycd.vm模板用于處理這些顯示任務。

          <HTML>

          <HEAD>

          <TITLE></TITLE>

          <link rel="stylesheet" type="text/css" href="defaultpage.css">

          </HEAD>

          <BODY BGCOLOR="#F79C19" link="ffffff" alink="999999"

          vlink="ffffff" topmargin="0" leftmargin="0" marginheight="0"

          marginwidth="0">

          <BR><BR>

          <font color="ffffff">

          #foreach($value in $cds)

          <form action="http://localhost:8080/cd/cdVelocityHandler"

          method="post">

          <b> Title: $value.title </b>

          <input type="hidden" name="id" value="$value.id">

          <input type="submit" name="submit" value="tracks">

          </form>

          #end

          <BR>

          <img src="/images/tune_big.gif" height="250" width="150">

          </BODY>

          </HTML>

          Listing 12.9 The displaycd.vm template.

          thanks.vm模板一樣,在這個模板的開頭部分包含了一些HTML標記。所有CD的輸出將被顯示在框架集的body內。伴隨HTML而來的是Velocity代碼?,F在讓我們回憶一下需要列表顯示的兩個部分:顯示某藝術家的所有CD標題和顯示一個用于列出每個CD的所有歌曲的按鈕。

          假如你正和你的web開發者一起工作,你決定讓代碼執行一個操作,把從數據庫里提出來的CD放到一個名叫$cdsCollection對象里,并將這個$cds對象放入上下文中。這個Collection對象將包含許多基于某特定藝術家所有CDCDRecordBean對象。

          正如你已經看到的,#foreach指令用于從可用的對象里提取一個迭代器(iterator),Collection類就是這樣的一個對象。因此,每一次循環,$value引用將得到一個CDRecordBean實體對象。利用beangetter方法,適當的值將被提取出來。

          許多工作都發生在循環內部,在這里將從數據庫提取用于顯示的CD標題。注意,一定要用HTML<form>標記包圍這個標題,這個<form>標記用于顯示一個讓用戶單擊就可以顯示所有這張CD歌曲的按鈕。

          在討論完應用程序的需求后,你和web開發者決定從CD表里提取ID,用于鏈接歌曲表。為了實現這個想法,你在<form>元素里創建了一個隱藏的<input>元素。注意,這個隱藏輸入按鈕的值用的是$value.id,意思是你可以向控制器傳遞每一個CDID

          顯示歌曲(tracks)

          當用戶單擊displaycd.vm模板輸出的Tracks按鈕時,將顯示歌曲的名稱和歌曲長度。Listing 12.10顯示的displaytracks.vm模板將用于完成這些任務。這個模板再次包含了HTML標記,以便更美觀地顯示這個模板。在HTML標記之后,你將看到另一個#foreach循環。

          <HTML>

          <HEAD>

          <TITLE></TITLE>

          <link rel="stylesheet" type="text/css" href="defaultpage.css">

          </HEAD>

          <BODY BGCOLOR="#F79C19" link="ffffff" alink="999999"

          vlink="ffffff" topmargin="0" leftmargin="0" marginheight="0"

          marginwidth="0">

          #if ($message)

          $message

          #end

          <BR>

          #foreach($value in $tracks)

          <b> Track: $value.name - Length: $value.length</b><BR>

          #end

          <br>

          <img src="/images/tune_big.gif" height="250" width=283></td></tr>

          </table>

          </BODY>

          </HTML>

          Listing 12.10 The displaytracks.vm template.

          在這里,你的web開發者已經指出了你所需要的另外一個Collection對象,用于存儲某張CD上所有的歌曲。這個Collection對象被命名為$tracks,它通過控制器組件放置在上下文里。

          控制器代碼

          你目前已經擁有了模型和視圖組件,現在你需要一個控制器組件來把這兩個組件聯系在一起。Listing 12.11顯示了這個Velocity Servlets,它將完成這個工作。

          import java.io.*;

          import java.util.*;

          import javax.servlet.*;

          import javax.servlet.http.*;

          import org.apache.velocity.Template;

          import org.apache.velocity.context.Context;

          import org.apache.velocity.servlet.VelocityServlet;

          import org.apache.velocity.exception.*;

          import javax.naming.*;

          import javax.ejb.*;

          import cd.*;

          import org.apache.velocity.app.Velocity;

          public class cdVelocityHandler extends VelocityServlet {

          private CDRecordHome cdHome = null;

          private TracksRecordHome tracksHome = null;

          protected Properties loadConfiguration(ServletConfig config )

          throws IOException, FileNotFoundException {

          Properties p = new Properties();

          String path =

          config.getServletContext().getRealPath("/");

          if (path == null) {

          System.out.println("

          Unable to get the current webapp root");

          path = "/";

          }

          p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH,

          path );

          return p;

          }

          public void init() throws ServletException {

          try {

          javax.naming.Context cmp = (javax.naming.Context)

          new InitialContext().lookup("java:comp/env/cmp");

          cdHome = (CDRecordHome) cmp.lookup("CDRecordBean");

          tracksHome = (TracksRecordHome)

          cmp.lookup("TracksRecordBean");

          } catch (NamingException e) {

          e.printStackTrace();

          }

          }

          public Template handleRequest( HttpServletRequest req,

          HttpServletResponse res, Context context ) {

          Template template = null;

          if (req.getParameter("submit").equals("new")) {

          try {

          if (cdHome == null) {

          context.put("message", "Sorry we had an error");

          } else {

          int tracks =

          Integer.parseInt(req.getParameter("tracks"));

          CDRecord cd =

          cdHome.create(req.getParameter("title"),

          req.getParameter("artist"), tracks);

          if (cd != null) {

          context.put("thanks",

          "Thank you for the new CD<BR>");

          } else {

          context.put("thanks",

          "We are sorry but your request failed<BR>");

          }

          try {

          template = getTemplate("thanks.vm");

          } catch( Exception e ) {

          e.printStackTrace();

          }

          }

          } catch(Exception e) {

          e.printStackTrace();

          }

          } else if (req.getParameter("submit").equals("obtain")) {

          try {

          if (cdHome == null) {

          context.put("message", "Sorry we had an error");

          } else {

          Collection cds =

          cdHome.findByArtist(req.getParameter("artist"));

          context.put ("cds", cds);

          try {

          template = getTemplate("displaycds.vm");

          } catch( Exception e ) {

          e.printStackTrace();

          }

          }

          } catch(Exception e) {

          e.printStackTrace();

          }

          } else if (req.getParameter("submit").equals("tracks")) {

          try {

          if (tracksHome == null) {

          context.put("message", "Sorry we had an error");

          } else {

          int id = Integer.parseInt(req.getParameter("id"));

          Collection tracks = tracksHome.findByCdID(id);

          context.put ("tracks", tracks);

          try {

          template = getTemplate("displaytracks.vm");

          } catch( Exception e ) {

          System.out.println("Error " + e);

          }

          }

          } catch(Exception e) {

          e.printStackTrace();

          }

          } else if (req.getParameter("submit").equals("addtrack")) {

          try {

          if (tracksHome == null) {

          context.put("message", "Sorry we had an error");

          } else {

          int id= Integer.parseInt(req.getParameter("id"));

          TracksRecord track = tracksHome.create(id,

          req.getParameter("name"),

          req.getParameter("length"));

          if (track!= null) {

          context.put("thanks",

          "Thank you for the new track<BR>");

          } else {

          context.put("thanks",

          "We are sorry but your request failed<BR>");

          }

          try {

          template = getTemplate("thanks.vm");

          } catch( Exception e ) {

          e.printStackTrace();

          }

          }

          } catch(Exception e) {

          e.printStackTrace();

          }

          } else {

          }

          return template;

          }

          }

          Listing 12.11 The controller servlet for the CD example

          這個控制器組件是一個Velocity Servlets,它使用VelocityServlet類的handleRequest()方法。在這里有一些讓Servlets訪問Velocity模板和實體EJB的步驟。

          Velocity模板被放置在Servlets應用程序的根目錄,這個目錄是Servlets必須的。為了確定Servlets能夠訪問這個應用程序的根目錄,需要為FILE_RESOURCE_LOADER_PATH屬性設置真實的目錄。你可以在loadConfiguration()方法里完成這個設置操作,這個方法將在handleRequest()方法調用之前被自動調用。

          接著,你需要獲得實體EJB home接口的訪問權,你可以在init()方法里完成這個任務,init()方法將Servlets第一次執行時被調用,這個方法將為comp/env/cmp JNDI引用獲得一個naming.Context上下文對象。這個引用將用于在系統里訪問EJB。接下來,你需要查找每一個bean,并從這兩個bean中返回適當的home對象。這些home對象將用于構建實體bean。

          handleRequest()方法里的代碼用于檢查用戶請求的是這四個操作的哪一個操作(增加CD、搜索特定藝術家、查看某CD的全部歌曲、為CD增加歌曲)。讓我分別看一下這四個操作:

          增加新CD

          當用戶為一張新的CD輸入標題、藝術家和該CD的所有歌曲時,代碼必須能夠完全把這些數據放到數據庫表里。代碼首先確定home接口對象是有效的,如果該接口無效,一個錯誤信息將會指派給“message”引用,該引用將增加到上下文中。

          如果home接口對象是有效的,則從HTML<form>傳遞過來的歌曲字符串被轉換成integer。接著,歌曲和從<form>傳遞過來的值的剩余部分一同被傳遞給CD beanhome接口的create()方法。調用該方法的結果有可能是null,也可能是一個用于呈現CD表行的新的實體EJB。如果該方法的值不是null,那么“thanks”引用被指向一個文本字符串,同時被加入到上下文對象中。否則,一個失敗的信息將會加入到上下文中。

          在任意一種情況下,模板對象都會被設置成從getTemplate()方法(使用thanks.vm文件名參數)返回的值。如果沒有異常發生,這個新模板對象會從handleRequest()方法返回,同時用戶會看到適當的輸出。

          增加新CD歌曲(Track

          增加新CD歌曲的代碼基本上和增加新CD的代碼一樣,只是接口不同,在此用的是track接口。在一個生產型(production)系統里,你必須確定歌曲應該加入到的CD表,以保證CD和歌曲能夠正確關聯。

          通過藝術家搜索CD

          通過特定的藝術家來獲取CD列表和增加CD只有一小點的不同就是獲取CD列表需要查詢數據庫。你應該記得這兩個實體EJBEJB文件里都定義了一個<query>元素。在CD數據表的情況下,我們定義的查詢將返回特定藝術家所有的CD表行。

          代碼通過findByArtist(String)方法來調用這個查詢。從這個方法返回的值是一個Collection對象,它包含了從CD數據表返回的零到若干個實體對象。不管有多少個對象在collection里,我們將使用下面的命令把這個Collection加入到上下文中

          context.put ("cds", cds);

          Collection對象被加入到上下文中后,displaycds.vm模板就從服務器的磁盤取出,之后,這個模板對象被顯示給用戶。

          列出CD歌曲

          當用戶想顯示特定CD的歌曲時,代碼將獲取從<form>里的ID變量傳遞過來的ID,以及將這個ID傳遞給findByCdID(int)方法。這個方法執行TracksRecordBeanEJB文件里的<query>元素,該方法的結果是一個Collection對象,它包含了這張特定CD的所有歌曲。這個Collection對象通過$tracks引用被加入到上下文中。

          高級Servlet功能

          作為VelocityServlet基礎類,許多附加的方法可以被重載。這些方法如下:

          Properties loadConfiguration(ServletConfig)—一個允許附加的屬性被加入到Servlets的屬性中的方法。這些目前被定義到Velocity運行時的屬性是:

          l         static java.lang.String--COUNTER_INITIAL_VALUE—初始化#foreach指令的計數器值

          l         static java.lang.String--COUNTER_NAME—初始化#foreach指令的計數器名稱

          l         static java.lang.String--DEBUG_PREFIX—日志信息前綴

          l         static java.lang.String--DEFAULT_RUNTIME_DIRECTIVES—默認運行時指令

          l         static java.lang.String--DEFAULT_RUNTIME_PROPERTIES—默認運行時屬性

          l         static java.lang.String--ENCODING_DEFAULT—默認編碼類型

          l         static java.lang.String--ERROR_PREFIX—錯誤信息前綴

          l         static java.lang.String--ERRORMSG_END—錯誤信息的結束標記,通過在#include指令里傳遞一個不允許參數時觸發

          l         static java.lang.String--ERRORMSG_START—錯誤信息的開始標記,通過在#include指令里傳遞一個不允許參數時觸發

          l         static java.lang.String--FILE_RESOURCE_LOADER_CACHE—FileResourceLoader里打開一個公用的緩存

          l         static java.lang.String--FILE_RESOURCE_LOADER_PATH—FileResourceLoader里設置一個公用的路徑

          l         static java.lang.String--INFO_PREFIX—消息通知前綴

          l         static java.lang.String--INPUT_ENCODING—模板編碼集

          l         static java.lang.String--INTERPOLATE_STRINGLITERALS—字符串篡改開關

          l         static java.lang.String--LOGSYSTEM_LOG4J_EMAIL_BUFFER_SIZE--log4J配置

          l         static java.lang.String--LOGSYSTEM_LOG4J_EMAIL_FROM--log4J配置

          l         static java.lang.String--LOGSYSTEM_LOG4J_EMAIL_SERVER--log4J配置

          l         static java.lang.String--LOGSYSTEM_LOG4J_EMAIL_SUBJECT--log4J配置

          l         static java.lang.String--LOGSYSTEM_LOG4J_EMAIL_TO--log4J配置

          l         static java.lang.String--LOGSYSTEM_LOG4J_FILE_BACKUPS--log4J配置

          l         static java.lang.String--LOGSYSTEM_LOG4J_FILE_SIZE--log4J配置

          l         static java.lang.String--LOGSYSTEM_LOG4J_PATTERN--log4J配置

          l         static java.lang.String--LOGSYSTEM_LOG4J_REMOTE_HOST--log4J配置

          l         static java.lang.String--LOGSYSTEM_LOG4J_REMOTE_PORT--log4J配置

          l         static java.lang.String--LOGSYSTEM_LOG4J_SYSLOGD_FACILITY--log4J配置

          l         static java.lang.String--LOGSYSTEM_LOG4J_SYSLOGD_HOST--log4J配置

          l         static int--NUMBER_OF_PARSERS—你想創建的解析器數量

          l         static java.lang.String--OUTPUT_ENCODING—輸出流編碼集

          l         static java.lang.String--PARSE_DIRECTIVE_MAXDEPTH—#parse指令允許的最大遞歸深度

          l         static java.lang.String--PARSER_POOL_SIZE—解析器在池里的總數

          l         static java.lang.String--RESOURCE_LOADER—用于重新找回資源加載器名稱的關鍵字

          l         static java.lang.String--RESOURCE_MANAGER_CACHE_CLASS—實現資源管理緩存的類

          l         static java.lang.String--RESOURCE_MANAGER_CLASS—實現資源管理器的類

          l         static java.lang.String--RESOURCE_MANAGER_LOGWHENFOUND—用于確定是否對找到資源進行日志

          l         static java.lang.String--RUNTIME_LOG—定位Velocity日志文件

          l         static java.lang.String--RUNTIME_LOG_ERROR_STACKTRACE—堆棧跟蹤錯誤信息輸出

          l         static java.lang.String--RUNTIME_LOG_INFO_STACKTRACE—堆棧跟蹤報告信息輸出

          l         static java.lang.String--RUNTIME_LOG_LOGSYSTEM—用于指定外面的日志系統

          l         static java.lang.String--RUNTIME_LOG_LOGSYSTEM_CLASS—你想要使用的日志系統類

          l         static java.lang.String--RUNTIME_LOG_REFERENCE_LOG_INVALID—非法引用日志

          l         static java.lang.String--RUNTIME_LOG_WARN_STACKTRACE—堆棧跟蹤警告信息輸出

          l         static java.lang.String--UNKNOWN_PREFIX—未知信息前綴

          l         static java.lang.String--VM_CONTEXT_LOCALSCOPE—VM本地作用域開關,默認為false

          l         static java.lang.String--VM_LIBRARY—本地Velocimacro庫模板名稱

          l         static java.lang.String--VM_LIBRARY_AUTORELOAD—一個自動加載VM資源庫的開關,用于開發階段

          l         static java.lang.String--VM_MESSAGES_ON—VM消息開關,默認為true

          l         static java.lang.String--VM_PERM_ALLOW_INLINE—布爾值,默認為true,既是否允許內聯(在模板里)宏定義

          l         static java.lang.String--VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL—布爾值,默認為false,即是否允許內聯(在模板里)宏定義替換現有的定義

          l         static java.lang.String--VM_PERM_INLINE_LOCAL—是否強迫內聯宏為本地的,默認為false

          l         static java.lang.String--WARN_PREFIX—警告信息前綴

          Context createContext(HttpServletRequest, HttpServletResponse)—該方法允許開發者創建他們自己的上下文對象,可以被用作私有merge()方法。

          void setContentType( HttpServletRequest,HttpServletResponse)—默認情況下,handleRequest()方法將輸出HTML格式的文本,但是,你可以更改成其他格式,比如XML,甚至是圖片文件。

          void mergeTemplate(Template, Context, HttpServletResponse)—如果你想自己控制輸出來代替handleRequest()方法,那么你可以使用createContext()方法得到你自己的上下文,并且把它們和模板進行合并,之后通過handleRequest()方法輸出給response對象。mergeTemplate()方法得到所有的三個對象,并且產生輸出。

          void requestCleanup(HttpServletRequest, HttpServletResponse, Context)—如果你想自己處理輸出,你應該重載requestCleanup()方法,來處理任何最后的結果。默認情況下,這個方法并沒有實現。

          protected void error(HttpServletRequest, HttpServletResponse, Exception)—當在處理用戶請求發生一個異常時,該方法將被調用。你可以重載這個方法來提供更多高級的錯誤處理能力。默認實現只是發送一個錯誤信息和一個堆棧跟蹤信息給用戶。

          增加報表(report

          我們的應用程序在此已經把注意力集中到產生一個HTML窗體格式的輸出。但是,如果你不想要任何HTML樣式的表,而是只想得到一個基于文本格式的所有數據庫里CD的報表該怎么辦?Ok,現在讓我們考慮一下Listing 12.12里的Velocity模板。



          Listing 12.12 A Velocity template that produces a text report.

          Listing 12.12里的Velocity模板用于輸出一個頁面的頭部,最多列出50張數據庫里的CD,之后,產生另一個頁面頭部。在兩個頭部之間的CD數量可以改變,以適應于不同的輸出。為了產生的完整的CD報表,你需要在主CD index.html頁面增加一個按鈕。下面給出這個按鈕的代碼:

          <h3>Reports</h3>

          <form action="http://localhost:8080/cd/cdVelocityHandler" method="post">

          <input type="submit" name="submit" value="fullreport"> -

          download 'report.txt' to your local system

          </form>

          這個新窗體在index頁面上顯示了一個名叫FullReport的按鈕。當用戶單擊這個按鈕的時候,控件被傳遞到cdVelocityHandler servlet(在Listing 12.11里定義的)。Listing 12.13的代碼將用于處理這個新按鈕。

          else if (req.getParameter("submit").equals("fullreport")) {

          try {

          if (cdHome == null) {

          context.put("message", "Sorry we had an error");

          } else {

          Collection cds = cdHome.findAllCDs();

          context.put ("cds", cds);

          try {

          template = getTemplate("fullreport.vm");

          } catch( Exception e ) {

          e.printStackTrace();

          }

          }

          } catch(Exception e) {

          e.printStackTrace();

          }

          }

          Listing 12.13 The control servlet report task.

          除了配合CDRecordBean實體beanfindAllCDs查詢外,這些代碼沒有什么特別的地方。新報表的查詢見Listing 12.14。

          <query>

          <query-method>

          <method-name>findAllCDs</method-name>

          </query-method>

          <ejb-ql>SELECT o FROM CDTable o</ejb-ql>

          </query>

          Listing 12.14 The bean query.

          新查詢從CDTable數據庫表從提出所有的行。一旦所有的行被提出,結果Collection對象就會被放入上下文中。最后,Velocity模板fullreport.vm被調用,用于輸出這個查詢的結果。輸出的結果見Figure 12.5


          Figure 12.5
          The report output in a browser.

          正如你所看見的一樣,這個輸出并不像我們希望的一樣,只輸出文本樣式,而不需要HTML進行潤色。如果我們想生成一個文本格式的文件并可以進行下載該怎么做?讓我們來考慮一下Listing 12.15里的模板。


          Listing 12.15
          The Velocity template for text only.

          Listing 12.15Velocity模板使用tab占位符來控制從數據庫生成數據的輸出格式。這也就意味著,ArtisTitle對應的字符串將擁有一個適當的格式和對齊方式,同時不再顧及這些字符串的長度。你肯定不想看到任何參差不齊的行排列樣式。為了完成這個任務,在知道Artist的值后,你必須生成一個正確的tab個數。如果你看一下模板代碼,你將會看到下面這行:

          $value.artist$stringlength.tabs($value.artist)$value.title

          這一個行是由下面三行代碼構成的:

          $value.artist

          $stringlength.tabs($value.artist)

          $value.title

          $value.artist$value.title命令只簡單為當前行產生將要顯示的artisttitle數據。這個命令里最有趣的部分是$stringlength.tabs($value.artist)。這個$stringlength引用和一個放置在上下文的對象(使用StringLength類)關聯。StringLength類代碼見Listing 12.16

          public class StringLength {

          public StringLength(){}

          public String tabs(String st) {

          String s = new String();

          for (int i=3;i>st.length()/5;i--)

          s = s + ""t";

          return s;

          }

          }

          Listing 12.16 The StringLength class.

          StringLength類只有一個簡單的任務:暴露一個名叫tabs()的方法。這個方法的作用是計算并返回一個確定個數的tabs字符串,以用于排列artisttitle的值。藝術家的名稱被傳遞給tabs()方法,之后確定數量的tabs被返回。

          現在,你的Velocity模板已經能夠返回正確的輸出,OK,讓我們考慮一下如何讓你的控件Servlets生成一個文件,以便用戶下載。Listing 12.17的代碼顯示了Full-Report按鈕的控件Servlets代碼。

          else if (req.getParameter("submit").equals("fullreport")) {

          try {

          if (cdHome == null) {

          context.put("message", "Sorry we had an error");

          } else {

          Collection cds = cdHome.findAllCDs();

          context.put ("cds", cds);

          try {

          res.setContentType("APPLICATION/OCTET-STREAM");

          res.setHeader("Content-Disposition","attachment;

          filename=report.txt");

          template = getTemplate("fullreport.vm");

          } catch( Exception e ) {

          e.printStackTrace();

          }

          }

          } catch(Exception e) {

          e.printStackTrace();

          }

          }

          Listing 12.17 The servlet code for downloading the report.

          正如你所看到的一樣,findAllCDs查詢被用于從數據庫中提取所有的CD信息,同時,模板fullreport.vm被用于輸出。其中增加了兩個新命令:

          res.setContentType("APPLICATION/OCTET-STREAM");

          res.setHeader("Content-Disposition","attachment; filename=report.txt");

          這兩個命令用于告訴用戶瀏覽器,從<form>返回的信息是一個名叫report.txt的文件,這個信息將導致出現一個下載對話框,用于用戶下載文件。Figure 12.6顯示了使用這個應用程序產生的文件下載情況。


          Figure 12.6
          The report output.

          本章小節和下章介紹

          在這一章里,我們介紹了一種可能對開發者有用的混合VelocityServlets的技術。在下一章里,我們將演示如何擴展Velocity驅動的站點,以適當國際化需要的技術。

          posted on 2008-10-24 16:35 KINGWEE 閱讀(845) 評論(0)  編輯  收藏 所屬分類: Velocity

          主站蜘蛛池模板: 新和县| 察隅县| 固安县| 无棣县| 凌海市| 沾化县| 新巴尔虎右旗| 北安市| 修文县| 谢通门县| 双鸭山市| 古田县| 东阳市| 西丰县| 曲阳县| 时尚| 山阴县| 永年县| 澄江县| 龙州县| 乐清市| 塘沽区| 甘谷县| 象州县| 同江市| 邯郸市| 苍溪县| 三河市| 宾阳县| 杂多县| 常德市| 新疆| 云安县| 任丘市| 高密市| 钦州市| 叙永县| 车致| 克东县| 哈尔滨市| 宁德市|