Velocity 是一個基于 Java 的通用模闆工具,來自于 jakarta.apache.org 。
Velocity 的介紹請參考 Velocity -- Java Web 開發新技術。這裡是它的一個應用示例。
這個例子參照了 PHP-Nuke 的結構, 即所有 HTTP 請求都以

http://www.some.com/xxx/Modules?name=xxx&arg1=xxx&bbb=xxx ;的形式進行處理。例子中所有檔案都是 .java 和 .html , 沒有其他特殊的檔案格式。除了 Modules.java 是 Java Servlet, 其餘的 .java 檔案都是普通的 Java Class.
所有 HTTP 請求都通過 Modules.java 處理。Modules.java 通過 Velocity 加載 Modules.htm。 Modules.htm 有頁頭,頁腳,頁左導航連結,頁中内容幾個部分。其中頁頭廣告、頁中内容是變化部分。頁頭廣告由 Modules.java 處理,頁中内容部分由 Modules.java dispatch 到子頁面類處理。
1) Modules.java
import javax.servlet.*; import javax.servlet.http.*; import org.apache.velocity.*; import org.apache.velocity.context.*; import org.apache.velocity.exception.*; import org.apache.velocity.servlet.*; import commontools.*; public class Modules extends VelocityServlet { public Template handleRequest(HttpServletRequest request, HttpServletResponse response, Context context) { //init response.setContentType("text/html; charset=UTF-8"); response.setCharacterEncoding("utf-8"); //prepare function page ProcessSubPage page = null; ProcessSubPage mainPage = new HomeSubPage(); String requestFunctionName = (String) request.getParameter("name"); boolean logined = false; String loginaccount = (String) request.getSession(true).getAttribute( "loginaccount"); if (loginaccount != null) { logined = true; } //default page is mainpage page = mainPage; if (requestFunctionName == null||requestFunctionName.equalsIgnoreCase("home")) { page = mainPage; } //no login , can use these page else if (requestFunctionName.equalsIgnoreCase("login")) { page = new LoginProcessSubPage(); } else if (requestFunctionName.equalsIgnoreCase("ChangePassword")) { page = new ChangePasswordSubPage(); } else if (requestFunctionName.equalsIgnoreCase("ForgetPassword")) { page = new ForgetPassword(); } else if (requestFunctionName.equalsIgnoreCase("about")) { page = new AboutSubPage(); } else if (requestFunctionName.equalsIgnoreCase("contact")) { page = new ContactSubPage(); } //for other page, need login first else if (logined == false) { page = new LoginProcessSubPage(); } else if (requestFunctionName.equalsIgnoreCase("listProgram")) { page = new ListTransactionProgramSubPage(); } else if (requestFunctionName.equalsIgnoreCase( "ViewProgramItem")) { page = new ViewTransactionProgramItemSubPage(); } else if (requestFunctionName.equalsIgnoreCase( "UpdateProgramObjStatus")) { page = new UpdateTransactionProgramObjStatusSubPage(); } else if (requestFunctionName.equalsIgnoreCase( "Search")) { page = new SearchSubPage(); } //check if this is administrator else if (Utilities.isAdministratorLogined(request)) { //Utilities.debugPrintln("isAdministratorLogined : true"); if (requestFunctionName.equalsIgnoreCase("usermanagement")) { page = new UserManagementSubPage(); } else if (requestFunctionName.equalsIgnoreCase( "UploadFiles")) { page = new UploadFilesSubPage(); } else if (requestFunctionName.equalsIgnoreCase( "DownloadFile")) { page = new DownloadFileSubPage(); } else if (requestFunctionName.equalsIgnoreCase( "Report")) { page = new ReportSubPage(); } } else { //no right to access. //Utilities.debugPrintln("isAdministratorLogined : false"); page = null; } //Utilities.debugPrintln("page : " + page.getClass().getName()); if(page != null){ context.put("function_page", page.getHtml(this, request, response, context)); }else{ String msg = "Sorry, this module is for administrator only.You are not administrator."; context.put("function_page",msg); }
context.put("page_header",getPageHeaderHTML()); context.put("page_footer",getPageFooterHTML()); Template template = null; try { template = getTemplate("/templates/Modules.htm"); //good } catch (ResourceNotFoundException rnfe) { Utilities.debugPrintln("ResourceNotFoundException 2"); rnfe.printStackTrace(); } catch (ParseErrorException pee) { Utilities.debugPrintln("ParseErrorException2 " + pee.getMessage()); } catch (Exception e) { Utilities.debugPrintln("Exception2 " + e.getMessage()); } return template; }
protected java.util.Properties loadConfiguration(ServletConfig config) throws java.io.IOException, java.io.FileNotFoundException { return Utilities.initServletEnvironment(this); } } |
2) ProcessSubPage.java , 比較簡單,隻定義了一個函數接口 getHtml
import javax.servlet.http.*; import org.apache.velocity.context.*; import org.apache.velocity.servlet.*; import commontools.*; public abstract class ProcessSubPage implements java.io.Serializable { public ProcessSubPage() { } public String getHtml(VelocityServlet servlet, HttpServletRequest request, HttpServletResponse response, Context context) { Utilities.debugPrintln( "you need to override this method in sub class of ProcessSubPage:" + this.getClass().getName()); return "Sorry, this module not finish yet."; } } |
他的 .java 檔案基本上是 ProcessSubPage 的子類和一些工具類。 ProcessSubPage 的子類基本上都是一樣的流程, 用類似
context.put("page_footer",getPageFooterHTML());
的寫法置換 .html 中的可變部分即可。如果沒有可變部分,完全是靜态網頁,比如 AboutSubPage, 就更簡單。
3) AboutSubPage.java
import org.apache.velocity.servlet.VelocityServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.velocity.context.Context; public class AboutSubPage extends ProcessSubPage { public AboutSubPage() { } public String getHtml(VelocityServlet servlet, HttpServletRequest request, HttpServletResponse response, Context context) { //prepare data //context.put("xxx","xxxx");
Template template = null; String fileName = "About.htm"; try { template = servlet.getTemplate(fileName); StringWriter sw = new StringWriter(); template.merge(context, sw); return sw.toString(); } catch (Exception ex) { return "error get template " + fileName + " " + ex.getMessage(); } } } |
其他 ProcessSubPage 的子類如上面基本類似,隻不過會多了一些 context.put("xxx","xxxx") 的語句。
通過以上的例子,我們可以看到,使用 Velocity + Servlet , 所有的代碼為: 1 個 java serverlet + m 個 java class + n 個 Html 檔案。
這裡是用了集中處理,然後分發(dispatch)的機制。不用擔心使用者在沒有登陸的情況下通路某些頁面。使用者驗證,頁眉頁腳包含都隻寫一次,易于編寫、修改和維護。代碼比較簡潔,并且很容易加上自己的頁面緩沖功能。可以随意将某個頁面的 html 在記憶體中儲存起來,緩存幾分鐘,實作頁面緩沖功能。成功、出錯頁面也可以用同樣的代碼封裝成函數,通過參數将 Message/Title 傳入即可。
因為 Java 代碼與 Html 代碼完全在不同的檔案中,美工與java代碼人員可以很好的分工,每個人修改自己熟悉的檔案,基本上不需要花時間做協調工作。而用 JSP, 美工與java代碼人員共同修改維護 .jsp 檔案,麻煩多多,噩夢多多。而且這裡沒有用 xml ,說實話,懂 xml/xls 之類的人隻占懂 Java 程式員中的幾分之一,人員不好找。
因為所有 java 代碼人員寫的都是标準 Java 程式,可以用任何 Java 編輯器,調試器,是以開發速度也會大大提高。美工寫的是标準 Html 檔案,沒有 xml, 對于他們也很熟悉,速度也很快。并且,當需要網站改版的時候,隻要美工把 html 檔案重新修飾排版即可,完全不用改動一句 java 代碼。
爽死了!!
4) 工具類 Utilities.java
import java.io.*; import java.sql.*; import java.text.*; import java.util.*; import java.util.Date; import javax.naming.*; import javax.servlet.*; import javax.servlet.http.*; import org.apache.velocity.*; import org.apache.velocity.app.*; import org.apache.velocity.context.Context; import org.apache.velocity.servlet.*; public class Utilities { private static Properties m_servletConfig = null; private Utilities() { } static { initJavaMail(); } public static void debugPrintln(Object o) { String msg = "proj debug message at " + getNowTimeString() + " ------------- "; System.err.println(msg + o); } public static Properties initServletEnvironment(VelocityServlet v) { // init only once if (m_servletConfig != null) { return m_servletConfig; } //debugPrintln("initServletEnvironment...."); try {
Properties p = new Properties(); ServletConfig config = v.getServletConfig(); // Set the Velocity.FILE_RESOURCE_LOADED_PATH property // to the root directory of the context. String path = config.getServletContext().getRealPath("/"); //debugPrintln("real path of / is : " + path); p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path); // Set the Velocity.RUNTIME_LOG property to be the file // velocity.log relative to the root directory // of the context. p.setProperty(Velocity.RUNTIME_LOG, path + "velocity.log"); // Return the Properties object. //return p; Velocity.init(p); m_servletConfig = p; return p; } catch (Exception e) { debugPrintln(e.getMessage()); //throw new ServletException("Error initializing Velocity: " + e); } return null; //this.getServletContext().getRealPath("/"); } private static void initJavaMail() { } public static Connection getDatabaseConnection() { Connection con = null; try { InitialContext initCtx = new InitialContext(); javax.naming.Context context = (javax.naming.Context) initCtx. lookup("java:comp/env"); javax.sql.DataSource ds = (javax.sql.DataSource) context.lookup( "jdbc/TestDB"); //Utilities.debugPrintln("ds = " + ds); con = ds.getConnection(); } catch (Exception e) { Utilities.debugPrintln("Exception = " + e.getMessage()); return null; } //Utilities.debugPrintln("con = " + con); return con; } public static java.sql.ResultSet excuteDbQuery(Connection con, String sql, Object[] parameters) {//請替換[] //Exception err = null; //Utilities.debugPrintln("excuteDbQuery" + parameters[0] + " ,sql=" + sql);//請替換[] try { java.sql.PreparedStatement ps = con.prepareStatement(sql); for (int i = 0; i < parameters.length; i++) { processParameter(ps, i + 1, parameters[i]); //請替換[] } return ps.executeQuery(); } catch (Exception e) { //Utilities.debugPrintln(e.getMessage()); e.printStackTrace(); } return null; } public static void excuteDbUpdate(String sql, Object[]parameters) {//請替換[] Connection con = Utilities.getDatabaseConnection(); excuteDbUpdate(con, sql, parameters); closeDbConnection(con); } public static void excuteDbUpdate(Connection con, String sql, Object[] parameters) {//請替換[] Exception err = null; try { java.sql.PreparedStatement ps = con.prepareStatement(sql); for (int i = 0; i < parameters.length; i++) { processParameter(ps, i + 1, parameters[i]);//請替換[] } ps.execute(); } catch (Exception e) { err = e; //Utilities.debugPrintln(err.getMessage()); e.printStackTrace(); } } private static void processParameter(java.sql.PreparedStatement ps, int index, Object parameter) { try { if (parameter instanceof String) { ps.setString(index, (String) parameter); } else { ps.setObject(index, parameter); } } catch (Exception e) { //Utilities.debugPrintln(e.getMessage()); e.printStackTrace(); } } public static void closeDbConnection(java.sql.Connection con) { try { con.close(); } catch (Exception e) { Utilities.debugPrintln(e.getMessage()); } } public static String getResultPage( String title, String message, String jumpLink, VelocityServlet servlet, HttpServletRequest request, HttpServletResponse response, Context context) { Template template = null; context.put("MessageTitle", title); context.put("ResultMessage", message); context.put("JumpLink", jumpLink); try { template = servlet.getTemplate( "/templates/Message.htm"); StringWriter sw = new StringWriter(); template.merge(context, sw); return sw.toString(); } catch (Exception ex) { return "error get template Message.htm " + ex.getMessage(); } } public static String mergeTemplate(String fileName, VelocityServlet servlet, Context context) { Template template = null; try { template = servlet.getTemplate(fileName); StringWriter sw = new StringWriter(); template.merge(context, sw); return sw.toString(); } catch (Exception ex) { return "error get template " + fileName + " " + ex.getMessage(); } } } |
注意:基于排版的需要,代碼中使用了中文全角空格。如果要複制代碼,請在複制後進行文字替換。