现在的位置: 首页 > 综合 > 正文

6、在servlet中使用FreeMarker

2018年02月05日 ⁄ 综合 ⁄ 共 8685字 ⁄ 字号 评论关闭

        FreeMarker将输出内容写到你传递给Template.process方法的Writer对象中,它并不关心Writer对象将输出内容打印到控制台或是一个文件中,或是HttpServletResponse对象的输出流中。FreeMarker并不知道servlets和Web;它仅仅是使用模板文件来合并Java对象,之后从它们中间生成输出文本。

        许多框架都是基于“Model 2”架构的,JSP页面来控制显示。

      一、  在“Model 2”中使用FreeMarker

        许多框架依照HTTP请求转发给用户自定义的“action”类,将数据作为属性放在ServletContext,HttpSession和HttpServletRequest对象中,之后请求被框架派发到一个JSP页面中(视图层),使用属性传递过来的数据来生成HTML页面,这样的策略通常就是所指的Model 2模型。

使用这样的框架,你就可以非常容易的用FTL文件来代替JSP文件。但是,因为你的Servlet容器(Web应用程序服务器),不像JSP文件,它可能并不知道如何处理FTL文件,那么就需要对Web应用程序进行一些额外的配置。
1. 复制freemarker.jar到(从FreeMarker发布包的lib目录中)你的Web应用程序的WEB-INF/lib目录下。
2. 将下面的部分添加到Web应用程序的WEB-INF/web.xml文件中(调整它是否需要)。

  <servlet>
	<servlet-name>freemarker</servlet-name>
	<servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
	<!-- FreemarkerServlet 设置: -->
	<init-param>
		<param-name>TemplatePath</param-name>
		<param-value>/</param-value>
	</init-param>
	<init-param>
		<param-name>NoCache</param-name>
		<param-value>true</param-value>
	</init-param>
	<init-param>
		<param-name>ContentType</param-name>
		<param-value>text/html; charset=UTF-8</param-value>
		<!-- 强制使用 UTF-8作为输出编码格式! -->
	</init-param>
	<!-- FreeMarker 设置: -->
	<init-param>
		<param-name>template_update_delay</param-name>
		<param-value>0</param-value>
		<!-- 0 只对开发使用! 否则使用大一点的值. -->
	</init-param>
	<init-param>
		<param-name>default_encoding</param-name>
		<param-value>ISO-8859-1</param-value>
		<!-- 模板文件的编码方式. -->
	</init-param>
	<init-param>
		<param-name>number_format</param-name>
		<param-value>0.##########</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
	</servlet>
<servlet-mapping>
	<servlet-name>freemarker</servlet-name>
	<url-pattern>*.ftl</url-pattern>
</servlet-mapping>
...
<!-- 为了阻止从Servlet容器外部访问MVC的视图层组件。
RequestDispatcher.forward/include应该起到作用。
移除下面的代码可能开放安全漏洞!
-->
<security-constraint>
<web-resource-collection>
<web-resource-name>
FreeMarker MVC Views
</web-resource-name>
<url-pattern>*.ftl</url-pattern>
</web-resource-collection>
<auth-constraint>
<!-- 不允许任何人访问这里 -->
</auth-constraint>
</security-constraint>

在这之后,你可以像使用JSP(*.jsp)文件那样使用FTL文件(*.ftl)了。(当然你可以选择除ftl之外的扩展名;这只是惯例)
它是怎么工作的?让我们来看看JSP是怎么工作的。许多servlet容器处理JSP时使用一个映射为*.jsp的servlet请求URL格式。这样servlet就会接收所有URL是以.jsp结尾的请求,查找请求URL地址中的JSP文件,内部编译完后交给Servlet,然后调用生成信息的serlvet来生成页面。这里为URL类型是*.ftl映射的FreemarkerServlet也是相同功能,只是FTL文件不会编译给Servlet,而是给Template对象,之后Template对象的process方法就会被调用来生成页面。

比如,代替这个JSP页面(注意它使用了Struts标签库来保存设计,而不是嵌入可怕的Java代码):

<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html>
<head><title>Acmee Products International</title>
<body>
<h1>Hello <bean:write name="user"/>!</h1>
<p>These are our latest offers:
<ul>
<logic:iterate name="latestProducts" id="prod">
<li><bean:write name="prod" property="name"/>
for <bean:write name="prod" property="price"/> Credits.
</logic:iterate>
</ul>
</body>
</html>

你可以使用这个FTL文件(使用ftl扩展名而不是jsp):

<html>
<head><title>Acmee Products International</title>
<body>
<h1>Hello ${user}!</h1>
<p>These are our latest offers:
<ul>
<#list latestProducts as prod>
<li>${prod.name} for ${prod.price} Credits.
</#list>
</ul>
</body>
</html>

注意:在FreeMarker中,<html:form action="/query">...</html:form>仅仅被视为是静态文本,所以它会按照原本输出出来了,就像其他XML或HTML标记一样。JSP标签也仅仅是FreeMarker的指令,没有什么特殊之处,所以你可以使用FreeMarker语法形式来调用它们,而不是JSP语法:<@html.form action="/query">...</@html.form>。注意在FreeMarker语法中你不能像JSP那样在参数中使用${...},而且不能给参数值加引号。所以这样是不对的:

<#-- WRONG: -->
<@my.jspTag color="${aVariable}" name="aStringLiteral"
width="100" height=${a+b} />

但下面这样是正确的:

<#-- Good: -->
<@my.jspTag color=aVariable name="aStringLiteral"
width=100 height=a+b />

在这两个模板中,当你要引用user和latestProduct时,它会首先试图去查找一个名字已经在模板中创建的变量(比如prod;如果你使用JSP:这是一个page范围内的属性)。如果那样做不行,它会尝试在对HttpServletRequest象中查找那个名字的属性,如果没有找到就在HttpSession中找,如果还没有找到那就在ServletContext中找。FTL按这种情况工作是因为FreemarkerServlet创建数据模型由上面提到的3个对象中的属性而来。那也就是说,这种情况下根哈希表不是java.util.Map(正如本手册中的一些例子那样),而是ServletContext+HttpSession+HttpServletRequest;FreeMarker在处理数据模型类型的时候非常灵活。所以如果你想将变量”
name”放到数据模型中,那么你可以调用servletRequest.setAttribute("name", "Fred");这是模型2的逻辑,而FreeMarker将会适应它。
FreemarkerServlet也会在数据模型中放置3个哈希表,这样你就可以直接访问3个对象中的属性了。这些哈希表变量是:Request,Session,Application(和ServletContext对应)。它还会暴露另外一个名为RequestParameters的哈希表,这个哈希表提供访问HTTP请求中的参数。
FreemarkerServlet也有很多初始参数。它可以被设置从任意路径来加载模板,从类路径下,或相对于Web应用程序的目录。你可以设置模板使用的字符集。你还可以设置想使用的对象包装器等。
通过子类别,FreemarkerServlet易于定制特殊需要。那就是说,你需要对所有模板添加一个额外的可用变量,使用servlet的子类,覆盖preTemplateProcess()方法,在模板被执行前,将你需要的额外数据放到模型中。或者在servlet的子类中,在Configuration中设置这些全局的变量作为共享变量。

二、包含其它Web应用程序资源中的内容

你可以使用由FreemarkerServlet(2.3.15版本之后)提供的客户化标签<@include_page path="..."/>来包含另一个Web应用资源的内容到输出内容中;这对于整合JSP页面(在同一Web服务器中生活在FreeMarker模板旁边)的输出到FreeMarker模板的输出中非常有用。使用:

<@include_page path="path/to/some.jsp"/>

和使用JSP指令是相同的:

<jsp:include page="path/to/some.jsp">

注意:
<@include_page ...>不能和<#include ...>搞混,后者是为了包含FreeMarker模板而不会牵涉到Servlet容器。使用<#include ...>包含的模板和包含它的模板共享模板处理状态,比如数据模型和模板语言变量,而<@include_page ...>开始一个独立的HTTP请求处理。
路径可以是相对的,也可以是绝对的。相对路径被解释成相对于当前HTTP请求(一个可以触发模板执行的请求)的URL,而绝对路径在当前的servlet上下文(当前的Web应用)中是绝对的。你不能从当前Web应用的外部包含页面。注意你可以包含任意页面,而不仅仅是JSP页面;我们仅仅使用以.jsp结尾的页面作为说明。
除了参数path之外,你也可以用布尔值(当不指定时默认是true)指定一个名为inherit_params可选的参数来指定被包含的页面对当前的请求是否可见HTTP请求中的参数。
最后,你可以指定一个名为params的可选参数,来指定被包含页面可见的新请求参数。如果也传递继承的参数,那么指定参数的值将会得到前缀名称相同的继承参数的值。params的值必须是一个哈希表类型,它其中的每个值可以是字符串,或者是字符串序列(如果你需要多值参数)。这里给出一个完整的示例:

<@include_page path="path/to/some.jsp" inherit_params=true params={"foo": "99", "bar": ["a", "b"]}/>

这会包含path/to/some.jsp页面,传递它的所有的当前请求的参数,除了“foo”和“bar”,这两个会被分别设置为“99”和多值序列“a”,“b”。如果原来请求中已经有这些参数的值了,那么新值会添加到原来存在的值中。那就是说,如果“foo”有值“111”和“123”,那么现在它会有“99”,“111”,“123”。
事实上使用params给参数传递非字符串值是可能的。这样的一个值首先会被转换为适合的Java对象(数字,布尔值,日期等),之后调用它们Java对象的toString()方法来得到字符串值。最好不要依赖这种机制,作为替代,明确参数值在模板级别不能转换成字符串类型之后,在使用到它的地方可以使用内建函数?string和?c。

三、在FTL中使用JSP客户化标签

FreemarkerServlet将一个哈希表类型的JspTaglibs放到数据模型中,就可以使用它来访问JSP标签库了。JSP客户化标签库将被视为普通用户自定义指令来访问。例如,这是使用了Struts标签库的JSP文件:

<%@page contentType="text/html;charset=ISO-8859-2" language="java"%>
<%@taglib uri="/WEB-INF/struts-html.tld" prefix="html"%>
<%@taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>
<html>
<body>
<h1><bean:message key="welcome.title"/></h1>
<html:errors/>
<html:form action="/query">
Keyword: <html:text property="keyword"/><br>
Exclude: <html:text property="exclude"/><br>
<html:submit value="Send"/>
</html:form>
</body>
</html>

这是一个(近似)等价的FTL:

<#assign html=JspTaglibs["/WEB-INF/struts-html.tld"]>
<#assign bean=JspTaglibs["/WEB-INF/struts-bean.tld"]>
<html>
<body>
<h1><@bean.message key="welcome.title"/></h1>
<@html.errors/>
<@html.form action="/query">
Keyword: <@html.text property="keyword"/><br>
Exclude: <@html.text property="exclude"/><br>
<@html.submit value="Send"/>
</@html.form>
</body>
</html>

因为JSP客户化标签是在JSP环境中来书写操作的,它们假设变量(在JSP中常被指代“beans”)被存储在4个范围中:page范围,request范围,session范围和application范围。FTL没有这样的表示法(4种范围),但是FreemarkerServlet给客户化标签提供仿真的环境,这样就可以维持JSP范围中的“beans”和FTL变量之间的对应关系。对于自定义的JSP标签,请求,会话和应用范围是和真实JSP相同的:javax.servlet.ServletContext,HttpSession和ServerRequest对象中的属性。从FTL的角度来看,这三种范围都在数据模型中。page范围和FTL全局变量(参见global指令)是对应的。那也就是,如果你使用global指令创建一个变量,通过仿真的JSP环境,它会作为page范围变量对自定义标签可见。而且,如果一个JSP标签创建了一个新的page范围变量,那么结果和用global指令创建的是相同的。要注意在数据模型中的变量作为page范围的属性对JSP标签是不可见的,尽管它们在全局是可见的,因为数据模型和请求,会话,应用范围是对应的,而不是page范围。
在JSP页面中,你可以对所有属性值加引号,这和参数类型是字符串,布尔值或数字没有关系,但是因为在FTL模板中自定义标签可以被用户自定义FTL指令访问到,你将不得不在自定义标签中使用FTL语法规则,而不是JSP语法。所以当你指定一个属性的值时,那么在等号的右边是一个FTL表达式。因此,你不能对布尔值和数字值的参数加引号(比如:<@tiles.insert page="/layout.ftl" flush=true/>),否则它们将被解释为字符串值,当FreeMarker试图传递值到期望非字符串值的自定义标记中时,这就会引起类型不匹配错误。而且还要注意,这很自然,你可以使用任意FTL表达式作为属性的值,比如变量,计算的结果值等。(比如:<@tiles.insert
page=layoutName flush=foo && bar/>)
servlet容器运行过程中,因为它实现了自身的轻量级JSP运行时环境,它用到JSP标签库,而FreeMarker并不依赖于JSP支持。这是一个很小但值得注意的地方:在它们的TLD文件中,开启FreeMarker的JSP运行时环境来分发事件到JSP标签库中注册时间监听器,你应该将下面的内容添加到Web应用下的WEB-INF/web.xml文件中:

<listener>
<listener-class>freemarker.ext.jsp.EventForwarding</listener-class>
</listener>

用就行。如果你的servlet容器只对JSP 1.1支持,那么你不得不将下面六个类(比如你可以从Tomcat 5.x或Tomcat 4.x的jar包中提取)复制到Web应用的WEB-INF/classes/...目录下:
javax.servlet.jsp.tagext.IterationTag,
 javax.servlet.jsp.tagext.TryCatchFinally,
 javax.servlet.ServletContextListener,
 javax.servlet.ServletContextAttributeListener,
 javax.servlet.http.HttpSessionAttributeListener,
 javax.servlet.http.HttpSessionListener.

但是要注意,因为容器只支持JSP 1.1,通常是使用较早的Servlet 2.3之前的版本,事件监听器可能就不支持,因此JSP 1.2标签库来注册事件监听器会正常工作。

四、为FreeMarker配置安全策略

当FreeMarker运行在装有安全管理器的Java虚拟机中时,你不得不再授与一些权限,确保运行良好。最值得注意的是,你需要为对freemarker.jar的安全策略文件添加这些条目:

grant codeBase "file:/path/to/freemarker.jar"
{
permission java.util.PropertyPermission "file.encoding", "read";
permission java.util.PropertyPermission "freemarker.*", "read";
}

另外,如果从一个目录中加载模板,你还需要给FreeMarker授权来从那个目录下读取文件,使用如下的授权:

grant codeBase "file:/path/to/freemarker.jar"
{ ...
permission java.io.FilePermission "/path/to/templates/-", "read";
}

最终,如果你使用默认的模板加载机制,也就是从当前文件夹下加载模板,那么需要指定这些授权内容:(主要表达式${user.dir}将会在运行时被策略解释器处理,几乎它就是一个FreeMarker模板)

grant codeBase "file:/path/to/freemarker.jar"
{
...
permission java.util.PropertyPermission "user.dir", "read";
permission java.io.FilePermission "${user.dir}/-", "read";
}

【上篇】
【下篇】

抱歉!评论已关闭.