On this page
将 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 请求分派到用户定义的“操作”类,该类将数据作为属性放入ServletContext
,HttpSession
和HttpServletRequest
对象中,然后将该请求转发到 JSP 页面(视图),将使用随属性发送的数据生成 HTML 页面。这通常称为 Model 2.
使用这些框架,您可以简单地使用 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
对象,然后将调用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
文件 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 />
在这两个模板中,当您引用user
和latestProduct
时,它将首先尝试查找在模板中创建的具有该名称的变量(如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 个对象的属性。哈希变量为:Request
,Session
,Application
(对应于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 自定义标签
FreemarkerServlet
将JspTaglibs
哈希放入数据模型中,可用于访问 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.ServletContext
,HttpSession
和ServletRequest
对象的属性。从 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/**/*.tld
和WEB-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。