(六)在servlet中使用FreeMarker

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模型。

(六)在servlet中使用FreeMarker_第1张图片

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

[html]  view plain  copy
 
  1.   <servlet>  
  2.     <servlet-name>freemarkerservlet-name>  
  3.     <servlet-class>freemarker.ext.servlet.FreemarkerServletservlet-class>  
  4.       
  5.     <init-param>  
  6.         <param-name>TemplatePathparam-name>  
  7.         <param-value>/param-value>  
  8.     init-param>  
  9.     <init-param>  
  10.         <param-name>NoCacheparam-name>  
  11.         <param-value>trueparam-value>  
  12.     init-param>  
  13.     <init-param>  
  14.         <param-name>ContentTypeparam-name>  
  15.         <param-value>text/html; charset=UTF-8param-value>  
  16.           
  17.     init-param>  
  18.       
  19.     <init-param>  
  20.         <param-name>template_update_delayparam-name>  
  21.         <param-value>0param-value>  
  22.           
  23.     init-param>  
  24.     <init-param>  
  25.         <param-name>default_encodingparam-name>  
  26.         <param-value>ISO-8859-1param-value>  
  27.           
  28.     init-param>  
  29.     <init-param>  
  30.         <param-name>number_formatparam-name>  
  31.         <param-value>0.##########param-value>  
  32.     init-param>  
  33.     <load-on-startup>1load-on-startup>  
  34.     servlet>  
  35. <servlet-mapping>  
  36.     <servlet-name>freemarkerservlet-name>  
  37.     <url-pattern>*.ftlurl-pattern>  
  38. servlet-mapping>  
  39. ...  
  40.   
  41. auth-constraint>  
  42. 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代码):

[html]  view plain  copy
 
  1. <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>  
  2. <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>  
  3. <html>  
  4. <head><title>Acmee Products Internationaltitle>  
  5. <body>  
  6. <h1>Hello <bean:write name="user"/>!h1>  
  7. <p>These are our latest offers:  
  8. <ul>  
  9. <logic:iterate name="latestProducts" id="prod">  
  10. <li><bean:write name="prod" property="name"/>  
  11. for <bean:write name="prod" property="price"/> Credits.  
  12. logic:iterate>  
  13. ul>  
  14. body>  
  15. html>  
你可以使用这个FTL文件(使用ftl扩展名而不是jsp):

[html]  view plain  copy
 
  1. <html>  
  2. <head><title>Acmee Products Internationaltitle>  
  3. <body>  
  4. <h1>Hello ${user}!h1>  
  5. <p>These are our latest offers:  
  6. <ul>  
  7. <#list latestProducts as prod>  
  8. <li>${prod.name} for ${prod.price} Credits.  
  9. #list>  
  10. ul>  
  11. body>  
  12. html>  
注意:在FreeMarker中,...仅仅被视为是静态文本,所以它会按照原本输出出来了,就像其他XML或HTML标记一样。JSP标签也仅仅是FreeMarker的指令,没有什么特殊之处,所以你可以使用FreeMarker语法形式来调用它们,而不是JSP语法:<@html.form action="/query">...。注意在FreeMarker语法中你不能像JSP那样在参数中使用${...},而且不能给参数值加引号。所以这样是不对的:

[html]  view plain  copy
 
  1. <#-- WRONG: -->  
  2. <@my.jspTag color="${aVariable}" name="aStringLiteral"  
  3. width="100" height=${a+b} />  
但下面这样是正确的:

[html]  view plain  copy
 
  1. <#-- Good: -->  
  2. <@my.jspTag color=aVariable name="aStringLiteral"  
  3. 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指令是相同的:

注意:
<@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文件:

[html]  view plain  copy
 
  1. <%@page contentType="text/html;charset=ISO-8859-2" language="java"%>  
  2. <%@taglib uri="/WEB-INF/struts-html.tld" prefix="html"%>  
  3. <%@taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>  
  4. <html>  
  5. <body>  
  6. <h1><bean:message key="welcome.title"/>h1>  
  7. <html:errors/>  
  8. <html:form action="/query">  
  9. Keyword: <html:text property="keyword"/><br>  
  10. Exclude: <html:text property="exclude"/><br>  
  11. <html:submit value="Send"/>  
  12. html:form>  
  13. body>  
  14. html>  
这是一个(近似)等价的FTL:

[html]  view plain  copy
 
  1. <#assign html=JspTaglibs["/WEB-INF/struts-html.tld"]>  
  2. <#assign bean=JspTaglibs["/WEB-INF/struts-bean.tld"]>  
  3. <html>  
  4. <body>  
  5. <h1><@bean.message key="welcome.title"/>h1>  
  6. <@html.errors/>  
  7. <@html.form action="/query">  
  8. Keyword: <@html.text property="keyword"/><br>  
  9. Exclude: <@html.text property="exclude"/><br>  
  10. <@html.submit value="Send"/>  
  11. @html.form>  
  12. body>  
  13. 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文件中:

[html]  view plain  copy
 
  1. <listener>  
  2. <listener-class>freemarker.ext.jsp.EventForwardinglistener-class>  
  3. 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的安全策略文件添加这些条目:

[html]  view plain  copy
 
  1. grant codeBase "file:/path/to/freemarker.jar"  
  2. {  
  3. permission java.util.PropertyPermission "file.encoding", "read";  
  4. permission java.util.PropertyPermission "freemarker.*", "read";  
  5. }  
另外,如果从一个目录中加载模板,你还需要给FreeMarker授权来从那个目录下读取文件,使用如下的授权:
[html]  view plain  copy
 
  1. grant codeBase "file:/path/to/freemarker.jar"  
  2. { ...  
  3. permission java.io.FilePermission "/path/to/templates/-", "read";  
  4. }  
最终,如果你使用默认的模板加载机制,也就是从当前文件夹下加载模板,那么需要指定这些授权内容:(主要表达式${user.dir}将会在运行时被策略解释器处理,几乎它就是一个FreeMarker模板)

[html]  view plain  copy
 
  1. grant codeBase "file:/path/to/freemarker.jar"  
  2. {  
  3. ...  
  4. permission java.util.PropertyPermission "user.dir", "read";  
  5. permission java.io.FilePermission "${user.dir}/-", "read";  
  6. }  

你可能感兴趣的:(freeMarker)