在Struts中有两种使用Velocity的方法,一种是利用Velocity的vm模板进行页面展示,一种则是利用Velocity来生成静态页面。以下介绍在Struts 1.*版本中使用Velocity的vm模板显示。
在Struts 1.*版本中,并未支持对vm模板的显示,所以当ActionForward指向一个vm模板时,只会将模板中的Velocit语句当做普通字符内容显示出来,而不对其中的Velocity语句进行任何解析及赋值。所以在Struts 1.*版本中使用Velocity,需要在web.xml中配置VelocityViewServlet,以处理后缀为.vm的模板文件。
在web.xml中进行如下配置:
<servlet>
<servlet-name>velocity</servlet-name>
<servlet-class>
org.apache.velocity.tools.view.servlet.VelocityViewServlet
</servlet-class>
<init-param>
<param-name>org.apache.velocity.toolbox</param-name>
<param-value>/WEB-INF/toolbox.xml</param-value>
</init-param>
<load-on-startup>10</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>velocity</servlet-name>
<url-pattern>*.vm</url-pattern>
</servlet-mapping>
这样当ActionForward指向一个后缀为vm的模板时,该Servlet便会进行对vm模板的解析及赋值工作。
在web.xml中配置Struts 1.2的ActionServlet步骤省略。
vm模板:namelist.vm
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>StrutsSample_1</title>
</head>
<body>
<h3>StrutsSample</h3>
<table width="400" border="1">
#foreach($name in $list)
<tr> <td>$name</td> </tr>
#end
</table>
</body>
</html>
Struts Action
public class StrutsSample extends DispatchAction {
public ActionForward showNameList(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
ActionForward forward = new ActionForward();
ActionMessages errors = new ActionMessages();
List<String> list = new ArrayList<String>();
String name = null;
for(int i=0;i<20;i++){
name = "Hoffman "+i+" Name";
list.add(name);
}
request.setAttribute("list", list);
if (!errors.isEmpty()) {
saveErrors(request, errors);
forward = new ActionForward(mapping.getInput());
} else {
forward = new ActionForward("/namelist.vm");
}
return forward;
}
}
这里需要注意forward = new ActionForward("/namelist.vm");这句,因为在velocity.properties中已经配置了模板存放位置,所以这里直接可以指定模板名称。需要注意的是这里和Servlet中应用不同,Servlet是通过getTemplate("sample.vm")来获取模板,而Struts则是通过转发,所以模板前需要加上/。关于在servlet中使用velocity,请参考本博客的《Velocity简单示例源码解析》一文)
Struts配置
<struts-config>
<action-mappings>
<action path="/StrutsSample" parameter="dispatch" scope="request"
type="com.mixele.velocity.struts.StrutsSample" validate="true">
<forward name="namelist" path="/templates/namelist.vm" redirect="false"/>
</action>
</action-mappings>
</struts-config>
浏览器输入:http://localhost:8080/Mixele_Velocity/StrutsSample.do?dispatch=showNameList
便可看到处理结果。
在Struts的Action中,只做了对用户请求的处理,以及回复工作,和vm模板解析相关的所有工作,由VelocityViewServlet完成。
在web.xml中配置VelocityViewServlet后,当系统启动时,VelocityViewServlet类会初始化。
VelocityViewServlet初始化过程
public void init(ServletConfig config) throws ServletException {
super.init(config);
initVelocity(config); //初始化Velocity
initToolbox(config); //初始化Servlet的toolbox
//当Velocity初始化后,可以获取以下这些(默认内容类型、模板字符编码)
defaultContentType = RuntimeSingleton.getString(CONTENT_TYPE, DEFAULT_CONTENT_TYPE);
String encoding = RuntimeSingleton.getString(
RuntimeSingleton.OUTPUT_ENCODING, DEFAULT_OUTPUT_ENCODING);
if (!DEFAULT_OUTPUT_ENCODING.equalsIgnoreCase(encoding)) {
int index = defaultContentType.lastIndexOf("charset");
if (index < 0) {
defaultContentType += "; charset=" + encoding;
} else {
Velocity.warn("VelocityViewServlet: Charset was already "
+ "specified in the Content-Type property. "
+ "Output encoding property will be ignored.");
}
}
Velocity.info("VelocityViewServlet: Default content-type is: " + defaultContentType);
}
/** 初始化Velocity */
protected void initVelocity(ServletConfig config) throws ServletException {
Velocity.setApplicationAttribute(SERVLET_CONTEXT_KEY, getServletContext());
// default to servletlogger, which logs to the servlet engines log
Velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
ServletLogger.class.getName());
// by default, load resources with webapp resource loader
Velocity.setProperty(RuntimeConstants.RESOURCE_LOADER, "webapp");
Velocity.setProperty("webapp.resource.loader.class", WebappLoader.class.getName());
// Try reading an overriding Velocity configuration
try {
ExtendedProperties p = loadConfiguration(config); //读取配置文件(velocity.properties)
Velocity.setExtendedProperties(p);
} catch (Exception e) {
……源码省略……
}
try {
Velocity.init(); //now all is ready - init Velocity
} catch (Exception e) {
getServletContext().log(
"VelocityViewServlet: PANIC! unable to init() - " + e);
throw new ServletException(e);
}
}
/** 读取Velocity配置文件velocity.properties中的配置
* 若该文件不存在,使用默认配置,即org/apache/velocity/tools/view/servlet/velocity.properties */
protected ExtendedProperties loadConfiguration(ServletConfig config) throws IOException {
ServletContext servletContext = config.getServletContext();
//获取Velocity文件的路径
String propsFile = config.getInitParameter(INIT_PROPS_KEY);
if (propsFile == null || propsFile.length() == 0) {
propsFile = servletContext.getInitParameter(INIT_PROPS_KEY);
}
ExtendedProperties p = new ExtendedProperties();
if (propsFile != null) {
p.load(servletContext.getResourceAsStream(propsFile));
Velocity.info("VelocityViewServlet: Custom Properties File: " + propsFile);
} else {
Velocity.info("VelocityViewServlet: No custom properties found. "
+ "Using default Velocity configuration.");
}
return p;
}
/** 读取Velocity Tools配置文件toolbox.xml中的配置
* 若该文件不存在,使用Velocity Tools的默认配置 */
protected void initToolbox(ServletConfig config) throws ServletException {
ServletContext servletContext = config.getServletContext();
/* 检查Servlet配置中关于toolbox的配置(即/WEB-INF/toolbox.xml) */
String file = config.getInitParameter(TOOLBOX_KEY);
System.out.println("VelocityViewServlet == initToolbox == file = "+file);
/* check the servlet context for a toolbox */
if (file == null || file.length() == 0) {
file = servletContext.getInitParameter(TOOLBOX_KEY);
}
/* if we have a toolbox, get a manager for it */
if (file != null) {
toolboxManager = ServletToolboxManager.getInstance(servletContext, file);
} else {
Velocity.info("VelocityViewServlet: No toolbox entry in configuration.");
}
}
和在Servlet中使用Velocity一样,只是在初始化的过程中增加了读取Velocity Tools配置文件的步骤。
当Action跳转打到VM文件时,VelocityViewServlet类会对VM类型文件进行解析和赋值处理。
过程如下:
doGet() --> doRequest() --> createContext() --> setContentType() --> handleRequest() --> getTemplate() --> mergeTemplate() --> getResponseWriter() --> requestCleanup()
doGet():复写的HttpServlet方法,以获取用户的request
doRequest():这是处理vm的主要方法,在其中调用了以下方法:
createContext() – 获取Velocity的上下文
setContentType() – 设置HttpServletResponse的内容类型
handleRequest() – 使用模板处理请求(这个方法通过模板路径返回一个模板对象)
mergeTemplate() – 将模板和嵌入模板中的值合并
requestCleanup() – 应该是资源回收的方法,不过源码里是个空方法
通过以上的方法,VelocityViewServlet类对vm的解析赋值完成。