将 FreeMarker 与 Servlet 一起使用

Page Contents

从根本上讲,在 Web 应用程序空间中使用 FreeMarker 与其他任何地方都没有什么不同。 FreeMarker 将其输出写入传递给Template.process方法的Writer,它并不关心Writer是否输出到控制台,文件或HttpServletResponse的输出流。 FreeMarker 对 servlet 和 Web 一无所知。它只是将 Java 对象与模板文件合并,并从模板文件生成文本输出。从这里开始,您将如何围绕此构建 Web 应用程序。

但是,可能您想将 FreeMarker 与一些已经存在的 Web 应用程序框架一起使用。许多框架都依赖于“模型 2”架构,其中 JSP 页面负责处理表示。如果使用这样的框架(例如Apache Struts),请 continue 阅读。对于其他框架,请参阅该框架的文档。

将 FreeMarker 用于“模型 2”

许多框架遵循以下策略:将 HTTP 请求分派到用户定义的“操作”类,该类将数据作为属性放入ServletContextHttpSessionHttpServletRequest对象中,然后将该请求转发到 JSP 页面(视图),将使用随属性发送的数据生成 HTML 页面。这通常称为 Model 2.

Figure

使用这些框架,您可以简单地使用 FTL 文件而不是 JSP 文件。但是,由于与 JSP 文件不同,您的 servlet 容器(Web 应用程序服务器)不了解如何立即使用 FTL 文件,因此需要对 Web 应用程序进行一些额外的配置:

  • freemarker.jar(从 FreeMarker 发行版的lib目录中)复制到 Web 应用程序的WEB-INF/lib目录中。

  • 将以下部分插入 Web 应用程序的WEB-INF/web.xml文件(并根据需要进行调整):

<servlet>
  <servlet-name>freemarker</servlet-name>
  <servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>

  <!--
    Init-param documentation:
    https://freemarker.apache.org/docs/api/freemarker/ext/servlet/FreemarkerServlet.html
  -->

  <!-- FreemarkerServlet settings: -->
  <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>ResponseCharacterEncoding</param-name>
    <!-- Use the output_encoding setting of FreeMarker: -->
    <param-value>fromTemplate</param-value>
  </init-param>
  <init-param>
    <param-name>ExceptionOnMissingTemplate</param-name>
    <!-- true => HTTP 500 on missing template, instead of HTTP 404. -->
    <param-value>true</param-value>
  </init-param>

  <!-- FreeMarker engine settings: -->
  <init-param>
    <param-name>incompatible_improvements</param-name>
    <param-value>2.3.27</param-value>
    <!--
      Recommended to set to a high value.
      See: https://freemarker.apache.org/docs/pgui_config_incompatible_improvements.html
    -->
  </init-param>
  <init-param>
    <param-name>template_exception_handler</param-name>
    <!-- Use "html_debug" during development! -->
    <param-value>rethrow</param-value>
  </init-param>
  <init-param>
    <param-name>template_update_delay</param-name>
    <!-- Use 0 during development! Consider what value you need otherwise. -->
    <param-value>30 s</param-value>
  </init-param>
  <init-param>
    <param-name>default_encoding</param-name>
    <!-- The encoding of the template files: -->
    <param-value>UTF-8</param-value>
  </init-param>
  <init-param>
    <param-name>output_encoding</param-name>
    <!-- The encoding of the template output; Note that you must set
         "ResponseCharacterEncodring" to "fromTemplate" for this to work! -->
    <param-value>UTF-8</param-value>
  </init-param>
  <init-param>
    <param-name>locale</param-name>
    <!-- Influences number and date/time formatting, etc. -->
    <param-value>en_US</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>
  <!-- HTML and XML auto-escaped if incompatible_improvements >= 2.3.24: -->
  <url-pattern>*.ftlh</url-pattern>
  <url-pattern>*.ftlx</url-pattern>
</servlet-mapping>

...

<!--
  Prevent the visiting of MVC Views from outside the servlet container.
  RequestDispatcher.forward/include should, and will still work.
  Removing this may open security holes!
-->
<security-constraint>
  <web-resource-collection>
    <web-resource-name>FreeMarker MVC Views</web-resource-name>
    <url-pattern>*.ftl</url-pattern>
    <url-pattern>*.ftlh</url-pattern>
    <url-pattern>*.ftlx</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <!-- Nobody is allowed to visit these directly. -->
  </auth-constraint>
</security-constraint>

之后,您可以以与 JSP(*.jsp)文件相同的方式使用 FTL 文件(*.ftl)。 (当然,您可以选择ftl以外的其他 extensions;这只是约定)

Note:

它是如何工作的?让我们研究一下 JSP 是如何工作的。许多 servlet 容器使用 Map 到*.jsp请求 URL 模式的 servlet 来处理 JSP。该 servlet 将接收所有请求 URL 以.jsp结尾的请求,根据请求 URL 查找 JSP 文件,并在内部将其编译为Servlet,然后调用生成的 servlet 生成页面。此处 Map 到*.ftl URL 模式的FreemarkerServlet的功能相同,除了 FTL 文件不是编译为Servlet -s 而是Template对象,然后将调用Templateprocess方法生成页面。

例如,而不是此 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文件 extensions 而不是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>

Warning!

在 FreeMarker <html:form action="/query">...</html:form>中只是静态文本,因此它像任何其他 XML 或 HTML 标记一样按原样打印到输出中。 JSP 标记只是 FreeMarker 指令,没有什么特别的,因此您使用 FreeMarker 语法来调用它们,而不是 JSP 语法:<@html.form action="/query">...</@html.form>。请注意,在 FreeMarker 语法中,您不能像在 JSP 中那样在参数中使用${...},也不要对参数值使用引号。所以这是* WRONG *:

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

这很好:

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

在这两个模板中,当您引用userlatestProduct时,它将首先尝试查找在模板中创建的具有该名称的变量(如prod;如果您掌握 JSP:页面范围属性)。如果失败,它将尝试在HttpServletRequest中查找具有该名称的属性,如果不存在,则在HttpSession中查找,如果仍然找不到,则在ServletContext中查找。对于 FTL,这是可行的,因为FreemarkerServlet从提到的 3 个对象的属性构建数据模型。也就是说,在这种情况下,根哈希不是java.util.Map(在本手册的某些示例代码中是),而是ServletContext HttpSession HttpServletRequest; FreeMarker 对于数据模型是非常灵活的。因此,如果要将变量"name"放入数据模型,则调用servletRequest.setAttribute("name", "Fred");这是 Model 2 的逻辑,FreeMarker 适应了它。

FreemarkerServlet还将 3 个散列放入数据模型,您可以通过它们直接访问 3 个对象的属性。哈希变量为:RequestSessionApplication(对应于ServletContext)。它还公开了另一个名为RequestParameters的哈希,该哈希提供对 HTTP 请求参数的访问。

FreemarkerServlet具有各种初始化参数。可以将其设置为从任意目录,Classpath 或相对于 Web 应用程序目录加载模板。您可以设置用于模板的字符集,模板使用的默认语言环境,要使用的对象包装器等。

FreemarkerServlet通过子类化可以轻松地满足特殊需求。说,如果您需要在数据模型中为所有模板使用其他变量,请对 Servlet 进行子类化并重写preTemplateProcess()方法,以在处理模板之前将所需的任何其他数据填充到模型中。或子类化 servlet,然后在Configuration中将这些全局可用变量设置为shared variables

有关更多信息,请阅读该类的 Java API 文档。

包含来自其他 Web 应用程序资源的内容

您可以使用FreemarkerServlet提供的<@include_page path="..."/>自定义指令(自 2.3.15 开始),将另一个 Web 应用程序资源的内容包括到输出中。这对于将 JSP 页面的输出(与同一 Web 服务器中的 FreeMarker 模板并存)集成到 FreeMarker 模板输出中通常很有用。使用:

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

与在 JSP 中使用此标记相同:

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

Note:

<@include_page ...>不要与<#include ...>混淆,因为最后一个是包含 FreeMarker 模板而不涉及 Servlet 容器。 <#include ...>ed 模板与包含的模板(例如数据模型和模板语言变量)共享模板处理状态,而<@include_page ...>开始独立的 HTTP 请求处理。

Note:

一些 Web 应用程序框架为此提供了自己的解决方案,在这种情况下,您可能应该使用它。另外,某些 Web 应用程序框架不使用FreemarkerServlet,因此include_page不可用。

路径可以是相对的或绝对的。相对路径是相对于当前 HTTP 请求(触发模板处理的 URL)的 URL 解释的,而绝对路径在当前 Servlet 上下文(当前 Web 应用程序)中是绝对的。您不能包含来自当前 Web 应用程序外部的页面。注意,您可以包括任何页面,而不仅仅是 JSP 页面。我们仅以路径结尾为.jsp的页面为例。

除了path参数之外,您还可以指定一个名为inherit_params的可选参数,该参数带有一个布尔值(未指定时默认为 true),该布尔值指定所包含的页面是否将看到当前请求的 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 对象(即 Number,Boolean,Date 等),然后将使用其 Java toString()方法获取字符串值。不过,最好不要依赖于此机制,而应明确确保将不是字符串的参数值转换为模板级别的字符串,在模板级别上,您可以使用?string?c内置函数控制格式。

在 FTL 中使用 JSP 自定义标签

FreemarkerServletJspTaglibs哈希放入数据模型中,可用于访问 JSP taglib。 JSP 定制标记可以作为普通的用户定义指令来访问,定制 EL 功能(自 FreeMarker 2.3.22 起)可以作为方法来访问。例如,对于此 JSP 文件:

<%@ page contentType="text/html;charset=ISO-8859-2" language="java"%>
<%@ taglib prefix="e" uri="/WEB-INF/example.tld" %>
<%@ taglib prefix="oe" uri="/WEB-INF/other-example.tld" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<%-- Custom JSP tags and functions: --%>

<e:someTag numParam="123" boolParam="true" strParam="Example" anotherParam="${someVar}">
  ...
</e:someTag>

<oe:otherTag />

${e:someELFunction(1, 2)}

<%-- JSTL: --%>

<c:if test="${foo}">
  Do this
</c:if>

<c:choose>
  <c:when test="${x == 1}">
      Do this
  </c:when>
  <c:otherwise>
      Do that
  </c:otherwise>
</c:choose>

<c:forEach var="person" items="${persons}">
  ${person.name}
</c:forEach>

${fn:trim(bar)}

大约等效的 FTL 为:

<#assign e=JspTaglibs["/WEB-INF/example.tld"]>
<#assign oe=JspTaglibs["/WEB-INF/other-example.tld"]>

<#-- Custom JSP tags and functions: --#>

<@e.someTag numParam=123 boolParam=true strParam="Example" anotherParam=someVar>
  ...
</@e.someTag>

<@oe.otherTag />

${e.someELFunction(1, 2)}

<#-- JSTL - Instead, use native FTL constructs: -->

<#if foo>
  Do this
</#if>

<#if x == 1>
  Do this
<#else>
  Do that
</#if>

<#list persons as person>
  ${person.name}
</#list>

${bar?trim}

Note:

参数值不像 JSP 中那样使用引号和"${...}"。稍后查看更多说明。

Note:

JspTaglibs不是 FreeMarker 的核心功能;它仅在通过FreemarkerServlet调用模板时存在。这是因为 JSP 标记/功能假定使用 Servlet 环境(而 FreeMarker 则没有),并且必须在FreemarkerServlet构建的特殊 FreeMarker 数据模型中模拟某些 Servlet 概念。许多现代框架都以纯方式(而不是通过FreemarkerServlet)使用 FreeMarker。

由于 JSP 自定义标记是为在 JSP 环境中运行而编写的,因此它们假定变量(在 JSP 世界中通常称为“ beans”)存储在 4 个范围中:页面范围,请求范围,会话范围和应用程序范围。 FTL 没有这种表示法(这 4 个作用域),但是FreemarkerServlet为自定义 JSP 标记提供了模拟的 JSP 环境,该环境维护 JSP 作用域的“ bean”与 FTL 变量之间的对应关系。对于定制 JSP 标记,请求,会话和应用程序作用域与真实 JSP 完全相同:javax.servlet.ServletContextHttpSessionServletRequest对象的属性。从 FTL 方面,您可以将这三个范围一起视为数据模型,如前所述。页面范围对应于 FTL 全局变量(请参见global directive)。也就是说,如果您使用global指令创建变量,那么对于自定义标签,该变量将在模拟的 JSP 环境中作为页面范围变量可见。另外,如果 JSP 标签创建了新的页面范围变量,则结果将与使用global指令创建变量的结果相同。请注意,尽管数据模型中的变量是全局可见的,但它们并不作为 JSP 标记的页面范围属性可见,因为数据模型对应于请求,会话和应用程序范围,而不是页面范围。

在 JSP 页面上,您引用了所有属性值,如果参数的类型为字符串,布尔值或数字,则该属性不起作用。但是由于自定义标记可以作为用户定义的 FTL 指令在 FTL 模板中访问,因此您必须在自定义标记中使用 FTL 语法规则,而不是 JSP 规则。因此,当您指定“属性”的值时,在=的右侧将有一个FTL expression。因此,您不得引用布尔值和数值参数值(例如<@tiles.insert page="/layout.ftl" flush=true/>),否则它们将被解释为字符串值,并且当 FreeMarker 尝试将值传递给期望非字符串的自定义标签时,这将导致类型不匹配错误值。还请注意,自然地,您可以将任何 FTL 表达式用作属性值,例如变量,计算值等(例如<@tiles.insert page=layoutName flush=foo && bar/>)。

FreeMarker 使用 JSP 标记库时,不依赖运行它的 servlet 容器的 JSP 支持,因为它实现了自己的轻量级 JSP 运行时环境。仅需注意一个小细节:要使 FreeMarker JSP 运行时环境能够将事件调度到在其 TLD 文件中注册了事件侦听器的 JSP 标签库,您应该将其添加到 Web 应用程序的WEB-INF/web.xml

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

请注意,即使 servlet 容器不提供本地 JSP 支持,您也可以将 JSP 标记库与 FreeMarker 一起使用,只需确保 Web 应用程序可以使用 JSP 2.0(或更高版本)的javax.servlet.jsp.*软件包即可。

撰写本文时,除了 JSP 2 的“标记文件”功能(即,自定义的 JSP 标记在 JSP 语言中已实现)外,还实现了 JSP 2.1 之前的 JSP 功能。标记文件必须编译为 Java 类才能在 FreeMarker 下使用。

就像 JSP 的@taglib指令一样,JspTaglibs[uri]必须找到指定 URI 的 TLD。为此,它实现了 JSP 规范中描述的 TLD 发现机制。在那里查看更多内容,但总而言之,它将在WEB-INF/web.xml taglib元素,WEB-INF/**/*.tldWEB-INF/lib/*.{jar,zip}/META-INF/**/*.tld中搜索 TLD-s。此外,当您使用MetaInfTldSources和/或ClasspathTlds FreemarkerServlet init-params 进行设置时(从 2.3.22 开始),它可以发现类加载器可见的 TLD,即使它们在 WAR 结构之外。有关这些说明,请参见FreemarkerServlet的 Java API 文档。也可以从 Java 系统属性设置这些属性,当您想在 Eclipse 运行配置中更改这些属性而不修改web.xml时,这将很方便。再次,请参见FreemarkerServlet API 文档。 FreemarkerServlet还识别org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern servlet 上下文属性,并将其条目添加到MetaInfTldSources

将 FTL 嵌入到 JSP 页面中

有一个 taglib,它允许您将 FTL 片段放入 JSP 页面。嵌入式 FTL 片段可以访问 4 个 JSP 范围的属性(Bean)。您可以在 FreeMarker 发行版中找到一个有效的示例和 taglib。