通過前面章節(jié)的學(xué)習(xí),我們了解到Velocity主要用作MVC框架View組件的腳本語(yǔ)言,。Servlets是Java下MVC框架里用得最多的編程方式,。通過把Servlets用作控制器(controller),,Velocity用作顯示層(View),把Servlets或JavaBeans作用模型(model),,這樣就創(chuàng)建了一個(gè)理想的Web開發(fā)全面解決方案,。在這一章里,我們將學(xué)習(xí)如何在Servlets里使用Velocity,。 使用Servlets 在開始學(xué)習(xí)這章內(nèi)容之前,,你必須充分理解Servlets是什么?以及它是如何工作的,?如果對(duì)此你已經(jīng)很熟悉了,,那么你可以直接跳到“用VelocityServlet擴(kuò)展Servlets”節(jié)進(jìn)行深入學(xué)習(xí)。 在動(dòng)態(tài)Web頁(yè)面開發(fā)的早期,,服務(wù)端語(yǔ)言,,比如ASP、JSP等主要用于在HTML頁(yè)面里嵌入服務(wù)器端執(zhí)行的代碼,。當(dāng)瀏覽器請(qǐng)求這個(gè)頁(yè)面的時(shí)候,,服務(wù)器立即對(duì)所請(qǐng)求的ASP/JSP頁(yè)面進(jìn)行解析,執(zhí)行里面的語(yǔ)句,,并向用戶返回一個(gè)純粹的HTML頁(yè)面,,有時(shí)還要附帶返回客戶端執(zhí)行的JavaScript或VBScript腳本?;旌?/span>HTML和服務(wù)器端代碼并不容易,,而且,服務(wù)器端腳本語(yǔ)言還要受到一些限制,,比如服務(wù)器平臺(tái)支持,、服務(wù)器支持等比較有限。 Servlets把代碼從HTML中提取出來(lái),,把它放到服務(wù)器上,,這樣,你就可以使用全功能的Java語(yǔ)言進(jìn)行Servlets邏輯開發(fā),。Servlets也能進(jìn)行分布式處理,,而且可以使用模板來(lái)向用戶提供信息顯示。 Servlets的通用格式 在進(jìn)行深入學(xué)習(xí)之前了解傳統(tǒng)的Servlets結(jié)構(gòu)是非常有必要的,。Listing 12.1顯示了一個(gè)簡(jiǎn)單的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 傳統(tǒng)servlet代碼 當(dāng)有一個(gè)web瀏覽器請(qǐng)求達(dá)到時(shí),一般情況下,,請(qǐng)求的方式為POST或GET,。在上面的代碼中你可以看到,其中有一些方法將用于處理這兩種請(qǐng)求,,分別為doPost()和doGet()方法,,其中doPost()方法只是簡(jiǎn)單委派給了doGet()方法,。在doGet()方法內(nèi)部有兩個(gè)重要的變量:request和response。request對(duì)象用于把用戶信息轉(zhuǎn)換成servlet能夠理解的信息,。特別的,,用戶在HTML窗體里的輸入將作為文本進(jìn)行處理。response對(duì)象主要負(fù)責(zé)向用戶的瀏覽器返回標(biāo)準(zhǔn)的HTML頁(yè)面,。response對(duì)象返回的信息包括HTML,、XML和圖片。response對(duì)象也可以和PrintWriter對(duì)象一起協(xié)同工作,,以用于寫入信息并返回給用戶,。 Servlets不能使用普通的Web服務(wù)器作為主機(jī),只能使用應(yīng)用程序服務(wù)器(application servers)作為主機(jī),??梢宰鳛?/span>Servlets主機(jī)的軟件有Tomcat、JBOSS,、Resin,、WEBlogic、apusic等,。在很多情況下,,應(yīng)用程序服務(wù)器被用于和Web服務(wù)器一起協(xié)同工作,比如IIS或Apache等,。當(dāng)Web服務(wù)器得到一個(gè)servlet請(qǐng)求時(shí),,這個(gè)請(qǐng)求被移交給應(yīng)用程序服務(wù)器執(zhí)行。應(yīng)用程序服務(wù)器使用java編譯器來(lái)創(chuàng)建一個(gè)可執(zhí)行的Servlets映像,,這個(gè)Servlets映像將在JVM里執(zhí)行,。我們將在下一章演示這個(gè)話題的示例。 用VelocityServlet擴(kuò)展Servlets 在某種程度上,,你可以使用Velocity引擎和servlet,,通過Velocity模板語(yǔ)言書寫代碼來(lái)為用戶創(chuàng)建信息輸出。為了讓Servlets的使用更容易,,我們將通過使用一個(gè)名叫VelocityServlet的基類和名叫handleRequest()的方法來(lái)代替?zhèn)鹘y(tǒng)Servlets的HttpServlet和doGet()/doPost()方法。 handleRequest()方法傳遞三個(gè)參數(shù)HttpServletRequest,、HttpServletResponse和Context來(lái)響應(yīng)GET/POST請(qǐng)求,。HttpServletRequest、HttpServletResponse對(duì)象和doGet(),、doPost()方法接受的是同一個(gè)對(duì)象(代碼見Listing 12.1),。上下文對(duì)象是一個(gè)用于在Servlets里使用的Velocity引擎的上下文。我們將在上下文對(duì)象里為用于向用戶顯示響應(yīng)的Velocity模板放置信息,。 handleRequest()方法返回一個(gè)模板對(duì)象,,該方法自動(dòng)把傳遞進(jìn)來(lái)的上下文對(duì)象進(jìn)行合并,。因而,在handleRequest()方法里的代碼,,主要完成設(shè)置上下文和合并返回的Velocity對(duì)象等操作,。如果返回的模板對(duì)象為null,這些代碼就不能完成合并操作,。在這種情況下,,和傳統(tǒng)的Servlets代碼一樣,這些代碼將向用戶瀏覽器返回一些放置在response對(duì)象里的PrintWriter對(duì)象里的東西,。 基本的Velocity Servlet代碼 handleRequest()方法示例代碼見Listing 12.2,,用于向?yàn)g覽器簡(jiǎn)單輸出字符串。 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. 當(dāng)瀏覽器調(diào)用Listing 12.2里的代碼時(shí),,初始化一個(gè)Velocity對(duì)象,,并在里面放置了三個(gè)字符串對(duì)象。Velocity對(duì)象通過下面的語(yǔ)句被放入上下文中,。 context.put("list", v); 注意,,我們?cè)谏舷挛睦锓胖玫氖?/span>Vector對(duì)象實(shí)體(java.util.Vector)。接著,,聲明了一個(gè)模板對(duì)象,,并使用getTemplate(String)方法來(lái)從服務(wù)器的磁盤上定位和加載模板。如果在獲取模板文件的過程中出現(xiàn)錯(cuò)誤時(shí),,response對(duì)象里的PrintWriter被獲得,,并向其中寫入錯(cuò)誤信息。最后一個(gè)操作是返回模板對(duì)象,。如果某些錯(cuò)誤導(dǎo)致這個(gè)對(duì)象為null,,系統(tǒng)將不能完成合并操作;否則,,從服務(wù)器讀入的模板將和上下文一起被合并,。Listing 12.3為這個(gè)示例所使用的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來(lái)演示Velocity模板并不僅限于使用HTML格式,。了解這個(gè)示例是如何執(zhí)行的非常重要。還記得“list”關(guān)鍵字和Vector對(duì)象關(guān)聯(lián),,在模板里的Velocity模板語(yǔ)言代碼通過#foreach循環(huán)指令使用Vector,。回想一下在List對(duì)象(如Vector)里使用#foreach指令的定義我們就可以發(fā)現(xiàn),,迭代器(iterator)自動(dòng)提取并用于獲取所有在List(在這種情況下還可以是Vector)里的獨(dú)立對(duì)象,。 所有來(lái)自Vector的每一個(gè)對(duì)象都被提出并放置到引用$value處。既然字符串不是混合對(duì)象,我們就可以簡(jiǎn)單地輸出它的值(在<number>元素標(biāo)記里),。Figure 12.1演示了執(zhí)行結(jié)果,。
HttpServletRequest和HttpServletResponse對(duì)象被傳遞給Servlets代碼,同時(shí)被放置在兩個(gè)上下文常量對(duì)象中: ■ VelocityServlet.REQUEST—Stored as req ■ VelocityServlet.RESPONSE—Stored as res
每一個(gè)對(duì)象都能通過直接調(diào)用引用名(req和res)的方式用于Velocity模板中,。 #set($username = $req.getParameter('username')) 創(chuàng)建一個(gè)MVC應(yīng)用程序 我們的第一個(gè)Velocity示例相當(dāng)簡(jiǎn)單,,現(xiàn)在我們來(lái)搞一個(gè)稍復(fù)雜一點(diǎn)的示例。在這一節(jié)里,,你將使用Servlets來(lái)構(gòu)建一個(gè)MVC應(yīng)用程序,,其中:Servlets用作控制器(controller)、Velocity模板作用視圖(view),、JavaBeans作用模型(model),。這個(gè)應(yīng)用程序是一個(gè)具有以下四個(gè)功能的CD數(shù)據(jù)庫(kù): ■增加一個(gè)CD,同時(shí)返回一個(gè)thank-you響應(yīng) ■為特定的CD增加歌曲track,,同時(shí)返回一個(gè)thank-you響應(yīng) ■通過特定的藝術(shù)家返回其所有的CD ■獲取特定CD的全部歌曲track Figure 12.2顯示了增加一個(gè)新CD時(shí)的情況,;Figure 12.3顯示了當(dāng)你使用特定的藝術(shù)家搜索CD時(shí)的情況;Figure 12.4顯示了一個(gè)特定CD的所有歌曲時(shí)的情況,。
數(shù)據(jù)庫(kù)結(jié)構(gòu) 這個(gè)應(yīng)用程序使用了一個(gè)數(shù)據(jù)庫(kù)來(lái)保存CD和所有歌曲的信息,。在這個(gè)測(cè)試環(huán)境,我們選擇MYSQL數(shù)據(jù)庫(kù)來(lái)存在數(shù)據(jù),,當(dāng)然,,你也可以選擇其他類型的數(shù)據(jù)庫(kù)。下面的SQL建表命令顯示了CD和歌曲表的數(shù)據(jù)結(jié)構(gòu),。 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));
數(shù)據(jù)庫(kù)訪問 我們的模型組件打算使用實(shí)體EJBs來(lái)訪問CD表和歌曲(tracks)表的數(shù)據(jù),,這些bean和Servlets被用于向用戶提供服務(wù)。在這個(gè)測(cè)試環(huán)境里,,我們使用了Resin應(yīng)用程序服務(wù)器,。EJBs通過JNDI(Java名字和目錄接口)資源引用來(lái)訪問數(shù)據(jù)庫(kù)。Listing 12.4顯示了在應(yīng)用程序服務(wù)器的配置文件中增加的 <resource-ref>元素,。除了數(shù)據(jù)庫(kù)驅(qū)動(dòng)和URL外,,這個(gè)元素使用的是標(biāo)準(zhǔn)規(guī)范。如果你使用的是另外數(shù)據(jù)庫(kù),,你需要改變<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)代碼 在這里,你已經(jīng)創(chuàng)建了一個(gè)數(shù)據(jù)庫(kù),,同時(shí)包含了JNDI引用,,現(xiàn)在,通過JNDI引用,,你就可以訪問數(shù)據(jù)庫(kù)了,。是時(shí)候考慮實(shí)體beans了,我們將把它用于訪問數(shù)據(jù)庫(kù)表里的數(shù)據(jù),。因?yàn)樵谖覀兊挠袃蓚€(gè)數(shù)據(jù)庫(kù)表,,因此就需要構(gòu)建兩個(gè)EJB。為了達(dá)到目的,,讓我們利用Resin的container-managed persistence (CMP)模式來(lái)創(chuàng)建bean,,同時(shí)主要把精力集中在實(shí)現(xiàn)EJB本地實(shí)例上。意思是我們只處理CMP的本地接口,,而不處理遠(yuǎn)程接口,,因此只需要很少的代碼。由于本書是關(guān)于Velocity的,,故我們只展示這兩個(gè)bean的代碼,,不作更多解釋,你可以在站點(diǎn)http://www./compbooks/gradecki處下載這個(gè)完整的示例代碼,。下面就讓我們來(lái)看一看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文件展示了這兩個(gè)bean是如何定義的,其中包含了主鍵和每一個(gè)表的字段,。這兩個(gè)實(shí)體bean都包含了一個(gè)<query>元素,,它允許數(shù)據(jù)從表中取出并用于除主鍵以外的字段。為了讓你了解在Resin應(yīng)用程序服務(wù)器里是如何書寫實(shí)體bean的,,讓我們來(lái)看一下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制造商定義的一個(gè)助手類,。這個(gè)助手類提供了所有的用于讓實(shí)體bean實(shí)現(xiàn)空body的通用方法,。事實(shí)上,把該助手類用于你的bean時(shí),,你只需要提供一個(gè)方法,,從而減少代碼量和代碼混雜。另外,,bean里剩余的代碼被用于定義與這個(gè)bean相關(guān)聯(lián)的表字段,。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. 我們?cè)谥霸?jīng)提及,,你可以為這些實(shí)體bean下載剩余的文件,。所有的實(shí)體文件被放置在Resin 應(yīng)用程序服務(wù)器主機(jī)的/classes目錄下。 The View Code 現(xiàn)在,,你需要考慮一下如何使用Velocity作為你的腳本語(yǔ)言來(lái)進(jìn)行輸出,。這里將是你施展魔法的地方,為用戶提供令人激動(dòng)的輸出,。對(duì)這個(gè)應(yīng)用程序來(lái)說(shuō),,我們需要三個(gè)Velocity模板: ■ thanks.vm—顯示thank-you信息的普通頁(yè)面 ■ displaycd.vm—向用戶顯示CD列表的頁(yè)面 ■ displaytracks.vm—顯示特定CD內(nèi)所有歌曲的頁(yè)面 首先,,讓我們來(lái)看一下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. 這個(gè)thanks.vm模板事實(shí)上是web框架集的一部分,,它提供了所有可見的邊框,。這個(gè)模板將被放置在main處,或放在body里,,是web框架集的一部分,。在這里,為了讓頁(yè)面更美觀,,你需要提供適當(dāng)?shù)谋尘邦伾蛨D片,。在這個(gè)模板的頂部是頁(yè)面背景的信息,后跟實(shí)際的Velocity元素,。 第一個(gè)元素為帶有$message引用的#if指令,。$message引用用于顯示一個(gè)信息(當(dāng)一個(gè)錯(cuò)誤出現(xiàn)并且你需要讓用戶知道這個(gè)錯(cuò)誤的時(shí)候)。這個(gè)信息只有在錯(cuò)誤發(fā)生時(shí)才會(huì)被寫入上下文中,,因此,,程序沒有發(fā)生錯(cuò)誤的時(shí)候,這個(gè)引用在上下文里是不存在的,。如果我們不用#if指令對(duì)$message進(jìn)行測(cè)試,,那么在輸出中就有可能出現(xiàn)文本字符串“$message”,這看起來(lái)不太好,,因此我們使用$if指令對(duì)$message引用是否包含有值進(jìn)行測(cè)試,,如果$message引用包含有值(意思是在上下文里可以找到這個(gè)引用),那么就顯示$message引用的值,。 在發(fā)生錯(cuò)誤的情況下,,你仍舊想感謝用戶辛苦輸入新的CD或歌曲。這個(gè)輸出就$thanks引用來(lái)產(chǎn)生,。
顯示CD 我們的應(yīng)用程序允許用戶通過特定的藝術(shù)家搜索數(shù)據(jù)庫(kù)里所有的CD,,并得到一個(gè)這些CD的列表。在每一次顯示CD列表的時(shí)候都將顯示一個(gè)按鈕,,以方便用戶可以單擊來(lái)列出特定CD的歌曲列表,。Listing 12.9里的displaycd.vm模板用于處理這些顯示任務(wù)。 <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模板一樣,,在這個(gè)模板的開頭部分包含了一些HTML標(biāo)記,。所有CD的輸出將被顯示在框架集的body內(nèi)。伴隨HTML而來(lái)的是Velocity代碼?,F(xiàn)在讓我們回憶一下需要列表顯示的兩個(gè)部分:顯示某藝術(shù)家的所有CD標(biāo)題和顯示一個(gè)用于列出每個(gè)CD的所有歌曲的按鈕,。 假如你正和你的web開發(fā)者一起工作,你決定讓代碼執(zhí)行一個(gè)操作,,把從數(shù)據(jù)庫(kù)里提出來(lái)的CD放到一個(gè)名叫$cds的Collection對(duì)象里,,并將這個(gè)$cds對(duì)象放入上下文中,。這個(gè)Collection對(duì)象將包含許多基于某特定藝術(shù)家所有CD的CDRecordBean對(duì)象。 正如你已經(jīng)看到的,,#foreach指令用于從可用的對(duì)象里提取一個(gè)迭代器(iterator),,Collection類就是這樣的一個(gè)對(duì)象。因此,,每一次循環(huán),$value引用將得到一個(gè)CDRecordBean實(shí)體對(duì)象,。利用bean的getter方法,,適當(dāng)?shù)闹祵⒈惶崛〕鰜?lái)。 許多工作都發(fā)生在循環(huán)內(nèi)部,,在這里將從數(shù)據(jù)庫(kù)提取用于顯示的CD標(biāo)題,。注意,一定要用HTML的<form>標(biāo)記包圍這個(gè)標(biāo)題,,這個(gè)<form>標(biāo)記用于顯示一個(gè)讓用戶單擊就可以顯示所有這張CD歌曲的按鈕,。 在討論完應(yīng)用程序的需求后,你和web開發(fā)者決定從CD表里提取ID,,用于鏈接歌曲表,。為了實(shí)現(xiàn)這個(gè)想法,你在<form>元素里創(chuàng)建了一個(gè)隱藏的<input>元素,。注意,,這個(gè)隱藏輸入按鈕的值用的是$value.id,意思是你可以向控制器傳遞每一個(gè)CD的ID,。 顯示歌曲(tracks) 當(dāng)用戶單擊displaycd.vm模板輸出的Tracks按鈕時(shí),,將顯示歌曲的名稱和歌曲長(zhǎng)度。Listing 12.10顯示的displaytracks.vm模板將用于完成這些任務(wù),。這個(gè)模板再次包含了HTML標(biāo)記,,以便更美觀地顯示這個(gè)模板。在HTML標(biāo)記之后,,你將看到另一個(gè)#foreach循環(huán),。 <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開發(fā)者已經(jīng)指出了你所需要的另外一個(gè)Collection對(duì)象,,用于存儲(chǔ)某張CD上所有的歌曲,。這個(gè)Collection對(duì)象被命名為$tracks,它通過控制器組件放置在上下文里,。 控制器代碼 你目前已經(jīng)擁有了模型和視圖組件,,現(xiàn)在你需要一個(gè)控制器組件來(lái)把這兩個(gè)組件聯(lián)系在一起,。Listing 12.11顯示了這個(gè)Velocity Servlets,,它將完成這個(gè)工作,。 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 這個(gè)控制器組件是一個(gè)Velocity Servlets,它使用VelocityServlet類的handleRequest()方法,。在這里有一些讓Servlets訪問Velocity模板和實(shí)體EJB的步驟,。 Velocity模板被放置在Servlets應(yīng)用程序的根目錄,這個(gè)目錄是Servlets必須的,。為了確定Servlets能夠訪問這個(gè)應(yīng)用程序的根目錄,需要為FILE_RESOURCE_LOADER_PATH屬性設(shè)置真實(shí)的目錄,。你可以在loadConfiguration()方法里完成這個(gè)設(shè)置操作,,這個(gè)方法將在handleRequest()方法調(diào)用之前被自動(dòng)調(diào)用。 接著,,你需要獲得實(shí)體EJB home接口的訪問權(quán),,你可以在init()方法里完成這個(gè)任務(wù),init()方法將Servlets第一次執(zhí)行時(shí)被調(diào)用,,這個(gè)方法將為comp/env/cmp JNDI引用獲得一個(gè)naming.Context上下文對(duì)象,。這個(gè)引用將用于在系統(tǒng)里訪問EJB。接下來(lái),,你需要查找每一個(gè)bean,,并從這兩個(gè)bean中返回適當(dāng)?shù)?/span>home對(duì)象。這些home對(duì)象將用于構(gòu)建實(shí)體bean,。 在handleRequest()方法里的代碼用于檢查用戶請(qǐng)求的是這四個(gè)操作的哪一個(gè)操作(增加CD,、搜索特定藝術(shù)家、查看某CD的全部歌曲,、為CD增加歌曲),。讓我分別看一下這四個(gè)操作: 增加新CD 當(dāng)用戶為一張新的CD輸入標(biāo)題、藝術(shù)家和該CD的所有歌曲時(shí),,代碼必須能夠完全把這些數(shù)據(jù)放到數(shù)據(jù)庫(kù)表里,。代碼首先確定home接口對(duì)象是有效的,如果該接口無(wú)效,,一個(gè)錯(cuò)誤信息將會(huì)指派給“message”引用,,該引用將增加到上下文中。 如果home接口對(duì)象是有效的,,則從HTML的<form>傳遞過來(lái)的歌曲字符串被轉(zhuǎn)換成integer,。接著,,歌曲和從<form>傳遞過來(lái)的值的剩余部分一同被傳遞給CD bean的home接口的create()方法。調(diào)用該方法的結(jié)果有可能是null,,也可能是一個(gè)用于呈現(xiàn)CD表行的新的實(shí)體EJB,。如果該方法的值不是null,那么“thanks”引用被指向一個(gè)文本字符串,,同時(shí)被加入到上下文對(duì)象中,。否則,一個(gè)失敗的信息將會(huì)加入到上下文中,。 在任意一種情況下,,模板對(duì)象都會(huì)被設(shè)置成從getTemplate()方法(使用thanks.vm文件名參數(shù))返回的值。如果沒有異常發(fā)生,,這個(gè)新模板對(duì)象會(huì)從handleRequest()方法返回,同時(shí)用戶會(huì)看到適當(dāng)?shù)妮敵觥?/span> 增加新CD歌曲(Track) 增加新CD歌曲的代碼基本上和增加新CD的代碼一樣,,只是接口不同,,在此用的是track接口。在一個(gè)生產(chǎn)型(production)系統(tǒng)里,,你必須確定歌曲應(yīng)該加入到的CD表,,以保證CD和歌曲能夠正確關(guān)聯(lián)。 通過藝術(shù)家搜索CD 通過特定的藝術(shù)家來(lái)獲取CD列表和增加CD只有一小點(diǎn)的不同就是獲取CD列表需要查詢數(shù)據(jù)庫(kù),。你應(yīng)該記得這兩個(gè)實(shí)體EJB的EJB文件里都定義了一個(gè)<query>元素,。在CD數(shù)據(jù)表的情況下,我們定義的查詢將返回特定藝術(shù)家所有的CD表行,。 代碼通過findByArtist(String)方法來(lái)調(diào)用這個(gè)查詢,。從這個(gè)方法返回的值是一個(gè)Collection對(duì)象,它包含了從CD數(shù)據(jù)表返回的零到若干個(gè)實(shí)體對(duì)象,。不管有多少個(gè)對(duì)象在collection里,,我們將使用下面的命令把這個(gè)Collection加入到上下文中 context.put ("cds", cds); 當(dāng)Collection對(duì)象被加入到上下文中后,,displaycds.vm模板就從服務(wù)器的磁盤取出,之后,,這個(gè)模板對(duì)象被顯示給用戶。 列出CD歌曲 當(dāng)用戶想顯示特定CD的歌曲時(shí),,代碼將獲取從<form>里的ID變量傳遞過來(lái)的ID,,以及將這個(gè)ID傳遞給findByCdID(int)方法。這個(gè)方法執(zhí)行TracksRecordBean的EJB文件里的<query>元素,,該方法的結(jié)果是一個(gè)Collection對(duì)象,,它包含了這張?zhí)囟?/span>CD的所有歌曲。這個(gè)Collection對(duì)象通過$tracks引用被加入到上下文中,。 高級(jí)Servlet功能 作為VelocityServlet基礎(chǔ)類,,許多附加的方法可以被重載,。這些方法如下: ■ Properties loadConfiguration(ServletConfig)—一個(gè)允許附加的屬性被加入到Servlets的屬性中的方法。這些目前被定義到Velocity運(yùn)行時(shí)的屬性是: l static java.lang.String--COUNTER_INITIAL_VALUE—初始化#foreach指令的計(jì)數(shù)器值 l static java.lang.String--COUNTER_NAME—初始化#foreach指令的計(jì)數(shù)器名稱 l static java.lang.String--DEBUG_PREFIX—日志信息前綴 l static java.lang.String--DEFAULT_RUNTIME_DIRECTIVES—默認(rèn)運(yùn)行時(shí)指令 l static java.lang.String--DEFAULT_RUNTIME_PROPERTIES—默認(rèn)運(yùn)行時(shí)屬性 l static java.lang.String--ENCODING_DEFAULT—默認(rèn)編碼類型 l static java.lang.String--ERROR_PREFIX—錯(cuò)誤信息前綴 l static java.lang.String--ERRORMSG_END—錯(cuò)誤信息的結(jié)束標(biāo)記,,通過在#include指令里傳遞一個(gè)不允許參數(shù)時(shí)觸發(fā) l static java.lang.String--ERRORMSG_START—錯(cuò)誤信息的開始標(biāo)記,,通過在#include指令里傳遞一個(gè)不允許參數(shù)時(shí)觸發(fā) l static java.lang.String--FILE_RESOURCE_LOADER_CACHE—在FileResourceLoader里打開一個(gè)公用的緩存 l static java.lang.String--FILE_RESOURCE_LOADER_PATH—在FileResourceLoader里設(shè)置一個(gè)公用的路徑 l static java.lang.String--INFO_PREFIX—消息通知前綴 l static java.lang.String--INPUT_ENCODING—模板編碼集 l static java.lang.String--INTERPOLATE_STRINGLITERALS—字符串篡改開關(guān) 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—你想創(chuàng)建的解析器數(shù)量 l static java.lang.String--OUTPUT_ENCODING—輸出流編碼集 l static java.lang.String--PARSE_DIRECTIVE_MAXDEPTH—#parse指令允許的最大遞歸深度 l static java.lang.String--PARSER_POOL_SIZE—解析器在池里的總數(shù) l static java.lang.String--RESOURCE_LOADER—用于重新找回資源加載器名稱的關(guān)鍵字 l static java.lang.String--RESOURCE_MANAGER_CACHE_CLASS—實(shí)現(xiàn)資源管理緩存的類 l static java.lang.String--RESOURCE_MANAGER_CLASS—實(shí)現(xiàn)資源管理器的類 l static java.lang.String--RESOURCE_MANAGER_LOGWHENFOUND—用于確定是否對(duì)找到資源進(jìn)行日志 l static java.lang.String--RUNTIME_LOG—定位Velocity日志文件 l static java.lang.String--RUNTIME_LOG_ERROR_STACKTRACE—堆棧跟蹤錯(cuò)誤信息輸出 l static java.lang.String--RUNTIME_LOG_INFO_STACKTRACE—堆棧跟蹤報(bào)告信息輸出 l static java.lang.String--RUNTIME_LOG_LOGSYSTEM—用于指定外面的日志系統(tǒng) l static java.lang.String--RUNTIME_LOG_LOGSYSTEM_CLASS—你想要使用的日志系統(tǒng)類 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本地作用域開關(guān),默認(rèn)為false l static java.lang.String--VM_LIBRARY—本地Velocimacro庫(kù)模板名稱 l static java.lang.String--VM_LIBRARY_AUTORELOAD—一個(gè)自動(dòng)加載VM資源庫(kù)的開關(guān),,用于開發(fā)階段 l static java.lang.String--VM_MESSAGES_ON—VM消息開關(guān),,默認(rèn)為true l static java.lang.String--VM_PERM_ALLOW_INLINE—布爾值,默認(rèn)為true,,既是否允許內(nèi)聯(lián)(在模板里)宏定義 l static java.lang.String--VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL—布爾值,,默認(rèn)為false,即是否允許內(nèi)聯(lián)(在模板里)宏定義替換現(xiàn)有的定義 l static java.lang.String--VM_PERM_INLINE_LOCAL—是否強(qiáng)迫內(nèi)聯(lián)宏為本地的,,默認(rèn)為false l static java.lang.String--WARN_PREFIX—警告信息前綴 ■ Context createContext(HttpServletRequest, HttpServletResponse)—該方法允許開發(fā)者創(chuàng)建他們自己的上下文對(duì)象,,可以被用作私有merge()方法。 ■ void setContentType( HttpServletRequest,HttpServletResponse)—默認(rèn)情況下,,handleRequest()方法將輸出HTML格式的文本,,但是,你可以更改成其他格式,,比如XML,,甚至是圖片文件。 ■ void mergeTemplate(Template, Context, HttpServletResponse)—如果你想自己控制輸出來(lái)代替handleRequest()方法,,那么你可以使用createContext()方法得到你自己的上下文,,并且把它們和模板進(jìn)行合并,之后通過handleRequest()方法輸出給response對(duì)象,。mergeTemplate()方法得到所有的三個(gè)對(duì)象,,并且產(chǎn)生輸出。 ■ void requestCleanup(HttpServletRequest, HttpServletResponse, Context)—如果你想自己處理輸出,,你應(yīng)該重載requestCleanup()方法,,來(lái)處理任何最后的結(jié)果。默認(rèn)情況下,,這個(gè)方法并沒有實(shí)現(xiàn),。 ■ protected void error(HttpServletRequest, HttpServletResponse, Exception)—當(dāng)在處理用戶請(qǐng)求發(fā)生一個(gè)異常時(shí),該方法將被調(diào)用,。你可以重載這個(gè)方法來(lái)提供更多高級(jí)的錯(cuò)誤處理能力,。默認(rèn)實(shí)現(xiàn)只是發(fā)送一個(gè)錯(cuò)誤信息和一個(gè)堆棧跟蹤信息給用戶。 增加報(bào)表(report) 我們的應(yīng)用程序在此已經(jīng)把注意力集中到產(chǎn)生一個(gè)HTML窗體格式的輸出,。但是,,如果你不想要任何HTML樣式的表,而是只想得到一個(gè)基于文本格式的所有數(shù)據(jù)庫(kù)里CD的報(bào)表該怎么辦?Ok,,現(xiàn)在讓我們考慮一下Listing 12.12里的Velocity模板,。
Listing 12.12里的Velocity模板用于輸出一個(gè)頁(yè)面的頭部,最多列出50張數(shù)據(jù)庫(kù)里的CD,,之后,,產(chǎn)生另一個(gè)頁(yè)面頭部。在兩個(gè)頭部之間的CD數(shù)量可以改變,,以適應(yīng)于不同的輸出,。為了產(chǎn)生的完整的CD報(bào)表,你需要在主CD index.html頁(yè)面增加一個(gè)按鈕,。下面給出這個(gè)按鈕的代碼: <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> 這個(gè)新窗體在index頁(yè)面上顯示了一個(gè)名叫FullReport的按鈕,。當(dāng)用戶單擊這個(gè)按鈕的時(shí)候,控件被傳遞到cdVelocityHandler servlet(在Listing 12.11里定義的),。Listing 12.13的代碼將用于處理這個(gè)新按鈕,。 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實(shí)體bean的findAllCDs查詢外,這些代碼沒有什么特別的地方,。新報(bào)表的查詢見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數(shù)據(jù)庫(kù)表從提出所有的行。一旦所有的行被提出,,結(jié)果Collection對(duì)象就會(huì)被放入上下文中,。最后,,Velocity模板fullreport.vm被調(diào)用,,用于輸出這個(gè)查詢的結(jié)果。輸出的結(jié)果見Figure 12.5
正如你所看見的一樣,,這個(gè)輸出并不像我們希望的一樣,,只輸出文本樣式,而不需要HTML進(jìn)行潤(rùn)色,。如果我們想生成一個(gè)文本格式的文件并可以進(jìn)行下載該怎么做,?讓我們來(lái)考慮一下Listing 12.15里的模板。
Listing 12.15里Velocity模板使用tab占位符來(lái)控制從數(shù)據(jù)庫(kù)生成數(shù)據(jù)的輸出格式,。這也就意味著,,Artis和Title對(duì)應(yīng)的字符串將擁有一個(gè)適當(dāng)?shù)母袷胶蛯?duì)齊方式,同時(shí)不再顧及這些字符串的長(zhǎng)度,。你肯定不想看到任何參差不齊的行排列樣式,。為了完成這個(gè)任務(wù),在知道Artist的值后,,你必須生成一個(gè)正確的tab個(gè)數(shù),。如果你看一下模板代碼,你將會(huì)看到下面這行: $value.artist$stringlength.tabs($value.artist)$value.title 這一個(gè)行是由下面三行代碼構(gòu)成的: $value.artist $stringlength.tabs($value.artist) $value.title $value.artist和$value.title命令只簡(jiǎn)單為當(dāng)前行產(chǎn)生將要顯示的artist和title數(shù)據(jù),。這個(gè)命令里最有趣的部分是$stringlength.tabs($value.artist),。這個(gè)$stringlength引用和一個(gè)放置在上下文的對(duì)象(使用StringLength類)關(guān)聯(lián),。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類只有一個(gè)簡(jiǎn)單的任務(wù):暴露一個(gè)名叫tabs()的方法。這個(gè)方法的作用是計(jì)算并返回一個(gè)確定個(gè)數(shù)的tabs字符串,,以用于排列artist和title的值,。藝術(shù)家的名稱被傳遞給tabs()方法,之后確定數(shù)量的tabs被返回,。 現(xiàn)在,,你的Velocity模板已經(jīng)能夠返回正確的輸出,OK,,讓我們考慮一下如何讓你的控件Servlets生成一個(gè)文件,,以便用戶下載。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查詢被用于從數(shù)據(jù)庫(kù)中提取所有的CD信息,同時(shí),,模板fullreport.vm被用于輸出,。其中增加了兩個(gè)新命令: res.setContentType("APPLICATION/OCTET-STREAM"); res.setHeader("Content-Disposition","attachment; filename=report.txt"); 這兩個(gè)命令用于告訴用戶瀏覽器,從<form>返回的信息是一個(gè)名叫report.txt的文件,,這個(gè)信息將導(dǎo)致出現(xiàn)一個(gè)下載對(duì)話框,,用于用戶下載文件。Figure 12.6顯示了使用這個(gè)應(yīng)用程序產(chǎn)生的文件下載情況,。
本章小節(jié)和下章介紹 在這一章里,,我們介紹了一種可能對(duì)開發(fā)者有用的混合Velocity和Servlets的技術(shù)。在下一章里,,我們將演示如何擴(kuò)展Velocity驅(qū)動(dòng)的站點(diǎn),,以適當(dāng)國(guó)際化需要的技術(shù)。 |
|
來(lái)自: 木木的陽(yáng)光 > 《Velocity》