Appearance
Spring Web Servlet:MVC - Part4
Tip:基于 Spring Core 5.3.30 版本。
1. Web 安全
Spring Security 项目为保护 Web 应用程序免受恶意攻击提供了支持。请参阅 Spring Security 参考文档,包括:
HDIV 是另一个与 Spring MVC 集成的 Web 安全框架。
2. HTTP 缓存
HTTP 缓存可以显著提高 Web 应用程序的性能。HTTP 缓存围绕 Cache-Control 响应头以及随后的条件请求头(如 Last-Modified 和 ETag)进行。Cache-Control 向私有(例如,浏览器)和公共(例如,代理)缓存提供如何缓存和重用响应的建议。ETag 头被用来发出条件请求,如果内容没有改变,可能会导致 304(NOT_MODIFIED)而没有正文。ETag 可以被看作是 Last-Modified 头的一个更复杂的后继者。
本节描述了在 Spring Web MVC 中可用的与 HTTP 缓存相关的选项。
2.1. CacheControl
CacheControl 提供了对 Cache-Control 头相关设置的配置支持,并在许多地方被接受作为参数:
虽然 RFC 7234 描述了 Cache-Control 响应头的所有可能指令,但 CacheControl 类型采取了一种以用例为导向的方法,专注于常见的场景:
Java
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();
// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
WebContentGenerator 也接受一个更简单的 cachePeriod 属性(以秒为单位定义),其工作方式如下:
-1的值不会生成Cache-Control响应头;0的值通过使用Cache-Control: no-store指令阻止缓存;n > 0的值通过使用Cache-Control: max-age=n指令将给定的响应缓存n秒;
2.2. 控制器
控制器可以添加对 HTTP 缓存的显式支持。我们建议这样做,因为在可以与条件请求头进行比较之前,需要计算资源的 lastModified 或 ETag 值。控制器可以将 ETag 头和 Cache-Control 设置添加到 ResponseEntity 中,如下面的示例所示:
Java
@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {
Book book = findBook(id);
String version = book.getVersion();
return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
.eTag(version) // lastModified is also available
.body(book);
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
如果与条件请求头的比较表明内容没有改变,那么前面的示例会发送一个带有空正文的 304(NOT_MODIFIED)响应。否则,ETag 和 Cache-Control 头将被添加到响应中。
你也可以在控制器中进行对条件请求头的检查,如下面的示例所示:
Java
@RequestMapping
public String myHandleMethod(WebRequest request, Model model) {
long eTag = ...
if (request.checkNotModified(eTag)) {
return null;
}
model.addAttribute(...);
return "myViewName";
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
对于条件请求,有三种变体可以根据 eTag 值、lastModified 值或两者进行检查。对于条件 GET 和 HEAD 请求,你可以将响应设置为 304(NOT_MODIFIED)。对于条件 POST、PUT 和 DELETE,你可以将响应设置为 412(PRECONDITION_FAILED),以防止并发修改。
2.3. 静态资源
你应该使用 Cache-Control 和条件响应头来提供静态资源,以获得最佳性能。请参阅关于配置静态资源的部分。
2.4. ETag 过滤器
你可以使用 ShallowEtagHeaderFilter 来添加 “Shallow” eTag 值,这些值是从响应内容计算出来的,从而节省带宽,但不节省 CPU 时间。请参阅 Shallow ETag。
3. 视图技术
在 Spring MVC 中,视图技术的使用是可插拔的。你决定使用 Thymeleaf、Groovy Markup Templates、JSPs 还是其他技术,主要是配置更改的问题。本章介绍了与 Spring MVC 集成的视图技术。我们假设你已经熟悉了视图解析。
Note:Spring MVC 应用程序的视图存在于该应用程序的内部信任边界内。视图可以访问你的应用程序上下文中的所有 Bean。因此,不建议在模板可被外部源编辑的应用程序中使用 Spring MVC 的模板支持,因为这可能会有安全隐患。
3.1. Thymeleaf
Thymeleaf 是一种现代的服务器端 Java 模板引擎,强调可以通过双击在浏览器中预览的自然 HTML 模板,这对于独立工作在 UI 模板上(例如,由设计师)非常有帮助,无需运行服务器。如果你想替换 JSPs,Thymeleaf 提供了最广泛的功能集,使这样的过渡更容易。Thymeleaf 正在积极开发和维护。要获得更完整的介绍,请参阅 Thymeleaf 项目主页。
Thymeleaf 与 Spring MVC 的集成由 Thymeleaf 项目管理。配置涉及到一些 Bean 声明,如 ServletContextTemplateResolver、SpringTemplateEngine 和 ThymeleafViewResolver。请参阅 Thymeleaf+Spring 以获取更多详细信息。
3.2. FreeMarker
Apache FreeMarker 是一个模板引擎,用于生成从 HTML 到电子邮件等任何类型的文本输出。Spring 框架内置了与 FreeMarker 模板一起使用 Spring MVC 的集成。
3.2.1. 视图配置
以下示例展示了如何将 FreeMarker 配置为视图技术:
Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
}
// Configure FreeMarker...
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/freemarker");
return configurer;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
以下示例展示了如何在 XML 中进行相同的配置:
XML
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:freemarker/>
</mvc:view-resolvers>
<!-- Configure FreeMarker... -->
<mvc:freemarker-configurer>
<mvc:template-loader-path location="/WEB-INF/freemarker"/>
</mvc:freemarker-configurer>1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
或者,你也可以声明 FreeMarkerConfigurer Bean,以全面控制所有属性,如下例所示:
XML
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
</bean>1
2
3
2
3
你的模板需要存储在前面示例中 FreeMarkerConfigurer 指定的目录中。考虑到前面的配置,如果你的控制器返回一个名为 welcome 的视图,解析器会查找 /WEB-INF/freemarker/welcome.ftl 模板。
3.2.2. FreeMarker 配置
你可以直接将 FreeMarker 的 Settings 和 SharedVariables 传递给 FreeMarker Configuration 对象(由 Spring 管理),方法是在 FreeMarkerConfigurer Bean 上设置适当的 Bean 属性。freemarkerSettings 属性需要一个 java.util.Properties 对象,freemarkerVariables 属性需要一个 java.util.Map。以下示例展示了如何使用 FreeMarkerConfigurer:
XML
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
<property name="freemarkerVariables">
<map>
<entry key="xml_escape" value-ref="fmXmlEscape"/>
</map>
</property>
</bean>
<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
请参阅 FreeMarker 文档,了解有关设置和变量的详细信息,以及它们如何应用于 Configuration 对象。
3.2.3. 表单处理
Spring 为 JSPs 提供了一个标签库,其中包括一个 <spring:bind/> 元素。这个元素主要让表单显示来自表单支持对象的值,并显示来自 Web 或业务层的 Validator 的失败验证结果。Spring 也在 FreeMarker 中支持相同的功能,并提供了额外的便利宏,用于生成表单输入元素本身。
3.2.3.1. 绑定宏
在 spring-webmvc.jar 文件中维护了一套标准的宏,因此它们对于适当配置的应用程序来说始终是可用的。
在 Spring 模板库中定义的一些宏被认为是内部(私有)的,但在宏定义中并不存在这样的作用域,使得所有的宏对于调用代码和用户模板都是可见的。以下部分仅关注你需要直接从你的模板中调用的宏。如果你希望直接查看宏代码,该文件被称为 spring.ftl,位于 org.springframework.web.servlet.view.freemarker 包中。
3.2.3.2. 简单绑定
在你的基于 FreeMarker 模板的 HTML 表单中,如果它们作为 Spring MVC 控制器的表单视图,你可以使用类似于下一个示例的代码来绑定字段值,并以与 JSP 等效的方式为每个输入字段显示错误消息。以下示例显示了一个 personForm 视图:
XML
<!-- FreeMarker macros have to be imported into a namespace.
We strongly recommend sticking to 'spring'. -->
<#import "/spring.ftl" as spring/>
<html>
...
<form action="" method="POST">
Name:
<@spring.bind "personForm.name"/>
<input type="text"
name="${spring.status.expression}"
value="${spring.status.value?html}"/><br />
<#list spring.status.errorMessages as error> <b>${error}</b> <br /> </#list>
<br />
...
<input type="submit" value="submit"/>
</form>
...
</html>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<@spring.bind> 需要一个 path 参数,它由你的命令对象的名称(默认为 command,除非你在控制器配置中更改了它)加上一个点和你希望绑定的命令对象上的字段名称组成。你也可以使用嵌套字段,如 command.address.street。bind 宏假定默认的 HTML 转义行为由 web.xml 中 ServletContext 的参数 defaultHtmlEscape 指定。
宏的另一种形式叫做 <@spring.bindEscaped>,它需要一个第二个参数,明确指定是否应在状态错误消息或值中使用 HTML 转义。你可以根据需要将其设置为 true 或 false。额外的表单处理宏简化了 HTML 转义的使用,你应该尽可能地使用这些宏。它们将在下一节中进行解释。
3.2.3.3. 输入宏
FreeMarker 的附加便利宏简化了绑定和表单生成(包括验证错误显示)。使用这些宏来生成表单输入字段并不是必需的,你可以将它们与简单的 HTML 或直接调用我们之前强调的 Spring 绑定宏混合匹配。
下表显示了可用宏的 FreeMarker 模板(FTL)定义和每个宏接受的参数列表:
| 宏 | FTL 定义 |
|---|---|
message(基于 code 参数从资源包输出字符串) | <@spring.message code/> |
messageText(基于 code 参数从资源包输出字符串,如果找不到则回退到 default 参数的值) | <@spring.messageText code, text/> |
url(用应用的上下文根前缀一个相对 URL) | <@spring.url relativeUrl/> |
formInput(标准输入字段,用于收集用户输入) | <@spring.formInput path, attributes, fieldType/> |
formHiddenInput(隐藏输入字段,用于提交非用户输入) | <@spring.formHiddenInput path, attributes/> |
formPasswordInput(标准输入字段,用于收集密码。注意,这种类型的字段永远不会填充任何值) | <@spring.formPasswordInput path, attributes/> |
formTextarea(大型文本字段,用于收集长的、自由格式的文本输入) | <@spring.formTextarea path, attributes/> |
formSingleSelect(下拉框,让用户选择一个必需的值) | <@spring.formSingleSelect path, options, attributes/> |
formMultiSelect(列表框,让用户选择 0 个或多个值) | <@spring.formMultiSelect path, options, attributes/> |
formRadioButtons(一组单选按钮,让用户从可用选项中选择一个) | <@spring.formRadioButtons path, options, separator, attributes/> |
formCheckboxes(一组复选框,让用户选择 0 个或多个值) | <@spring.formCheckboxes path, options, separator, attributes/> |
formCheckbox(一个单独的复选框) | <@spring.formCheckbox path, attributes/> |
showErrors(简化绑定字段的验证错误显示) | <@spring.showErrors separator, classOrStyle/> |
Note:在 FreeMarker 模板中,
formHiddenInput和formPasswordInput实际上并不是必需的,你可以使用普通的formInput宏,并为fieldType参数指定hidden或password作为值。
以上所有宏的参数具有一致的含义:
path:要绑定的字段的名称(例如 "command.name")。options:输入字段中可以选择的所有可用值的Map。映射的键代表从表单 POST 回来并绑定到命令对象的值。存储在键对应的映射对象是显示在用户表单上的标签,可能与表单回传的对应值不同。通常,这样的映射是由控制器提供的参考数据。你可以使用任何Map实现,具体取决于所需的行为。对于严格排序的映射,你可以使用带有适当Comparator的SortedMap(如TreeMap),对于应按插入顺序返回值的任意映射,可以使用LinkedHashMap或来自commons-collections的LinkedMap。separator:在多个选项作为独立元素可用的地方(单选按钮或复选框),用于分隔列表中每一个的字符序列(如<br>)。attributes:要包含在 HTML 标签本身内的任意标签或文本的额外字符串。这个字符串会被宏直接输出。例如,在一个textarea字段中,你可能会提供属性(如rows="5" cols="60"),或者你可以传递样式信息,如style="border:1px solid silver"。classOrStyle:对于showErrors宏,包裹每个错误的span元素使用的 CSS 类的名称。如果没有提供信息(或值为空),错误将被包裹在<b></b>标签中。
以下部分概述了宏的示例。
Input 字段
formInput 宏接受 path 参数(command.name)和一个额外的 attributes 参数(在即将到来的示例中为空)。该宏与所有其他表单生成宏一样,对 path 参数执行隐式的 Spring 绑定。绑定保持有效,直到新的绑定发生,所以 showErrors 宏不需要再次传递 path 参数 —— 它操作的是最后创建绑定的字段。
showErrors 宏接受一个 separator 参数(用于在给定字段上分隔多个错误的字符)并且也接受第二个参数 —— 这次是类名或样式属性。注意,FreeMarker 可以为 attributes 参数指定默认值。以下示例显示了如何使用 formInput 和 showErrors 宏:
XML
<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>1
2
2
下一个示例显示了表单片段的输出,生成了名称字段,并在表单提交后显示了一个验证错误,该字段中没有任何值。验证是通过 Spring 的验证框架进行的。
生成的 HTML 类似于以下示例:
jsp
Name:
<input type="text" name="name" value="">
<br>
<b>required</b>
<br>
<br>1
2
3
4
5
6
2
3
4
5
6
formTextarea 宏的工作方式与 formInput 宏相同,并接受相同的参数列表。通常,第二个参数(attributes)用于传递样式信息或 textarea 的 rows 和 cols 属性。
Select 字段
你可以使用四个 Select 字段宏在 HTML 表单中生成常见的 UI 值 Select 输入:
formSingleSelectformMultiSelectformRadioButtonsformCheckboxes
四个宏中的每一个都接受一个选项 Map,其中包含表单字段的值和与该值相对应的标签。值和标签可以是相同的。
下一个示例是 FTL 中的单选按钮。表单支持对象为此字段指定了 London 的默认值,因此不需要验证。当渲染表单时,整个可供选择的城市列表在模型中以 cityMap 的名称作为参考数据提供。下面的列表显示了这个例子:
jsp
...
Town:
<@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>1
2
3
2
3
前面的列表渲染了一行单选按钮,每个 cityMap 的值对应一个按钮,并使用 "" 作为分隔符。没有提供额外的属性(宏的最后一个参数缺失)。cityMap 在映射中对每个键值对使用相同的字符串。映射的键是表单实际作为 POST 请求参数提交的内容。映射的值是用户看到的标签。在前面的示例中,给定一个包含三个知名城市的列表和表单背景对象中的默认值,HTML 如下所示:
jsp
Town:
<input type="radio" name="address.town" value="London">London</input>
<input type="radio" name="address.town" value="Paris" checked="checked">Paris</input>
<input type="radio" name="address.town" value="New York">New York</input>1
2
3
4
2
3
4
如果你的应用程序预期通过内部代码来处理城市,你可以创建具有适当键的代码映射,如下例所示:
Java
protected Map<String, ?> referenceData(HttpServletRequest request) throws Exception {
Map<String, String> cityMap = new LinkedHashMap<>();
cityMap.put("LDN", "London");
cityMap.put("PRS", "Paris");
cityMap.put("NYC", "New York");
Map<String, Object> model = new HashMap<>();
model.put("cityMap", cityMap);
return model;
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
现在的代码生成的输出中,单选值是相关的代码,但用户仍然可以看到更友好的城市名称,如下所示:
jsp
Town:
<input type="radio" name="address.town" value="LDN">London</input>
<input type="radio" name="address.town" value="PRS" checked="checked">Paris</input>
<input type="radio" name="address.town" value="NYC">New York</input>1
2
3
4
2
3
4
3.2.3.4. HTML 转义
前面描述的表单宏的默认使用结果是生成符合 HTML 4.01 的 HTML 元素,并使用你的 web.xml 文件中定义的 HTML 转义的默认值,这是 Spring 的绑定支持所使用的。为了使元素符合 XHTML 或者覆盖默认的 HTML 转义值,你可以在你的模板(或者在你的模型中,它们对你的模板是可见的)中指定两个变量。在模板中指定它们的优点是它们可以在模板处理的后期被改变为不同的值,以便为你的表单中的不同字段提供不同的行为。
要切换到你的标签的 XHTML 兼容性,为一个名为 xhtmlCompliant 的模型或上下文变量指定一个 true 值,如下例所示:
jsp
<#-- for FreeMarker -->
<#assign xhtmlCompliant = true>1
2
2
处理完这个指令后,由 Spring 宏生成的任何元素现在都符合 XHTML。
同样,你可以按字段指定 HTML 转义,如下例所示:
jsp
<#-- until this point, default HTML escaping is used -->
<#assign htmlEscape = true>
<#-- next field will use HTML escaping -->
<@spring.formInput "command.name"/>
<#assign htmlEscape = false in spring>
<#-- all future fields will be bound with HTML escaping off -->1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
3.3. Groovy Markup
Groovy 标记模板引擎主要用于生成类似 XML 的标记(如 XML、XHTML、HTML5 等),但你也可以使用它来生成任何基于文本的内容。Spring 框架内置了与 Groovy 标记一起使用 Spring MVC 的集成。
Note:Groovy 标记模板引擎需要 Groovy 2.3.1 或更高版本。
3.3.1. 配置
以下示例展示了如何配置 Groovy 标记模板引擎:
Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.groovy();
}
// Configure the Groovy Markup Template Engine...
@Bean
public GroovyMarkupConfigurer groovyMarkupConfigurer() {
GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
configurer.setResourceLoaderPath("/WEB-INF/");
return configurer;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
以下示例展示了如何在 XML 中进行相同的配置:
XML
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:groovy/>
</mvc:view-resolvers>
<!-- Configure the Groovy Markup Template Engine... -->
<mvc:groovy-configurer resource-loader-path="/WEB-INF/"/>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
3.3.2. 示例
与传统的模板引擎不同,Groovy 标记依赖于一个使用构建器语法的 DSL。以下示例展示了一个 HTML 页面的样本模板:
Groovy
yieldUnescaped '<!DOCTYPE html>'
html(lang:'en') {
head {
meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"')
title('My page')
}
body {
p('This is an example of HTML contents')
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
3.4. 脚本视图
Spring 框架内置了与任何可以在 JSR-223 Java 脚本引擎之上运行的模板库一起使用 Spring MVC 的集成。我们已经在不同的脚本引擎上测试了以下模板库:
| 脚本库 | 脚本引擎 |
|---|---|
| Handlebars | Nashorn |
| Mustache | Nashorn |
| React | Nashorn |
| EJS | Nashorn |
| ERB | JRuby |
| 字符串模板 | Jython |
| Kotlin 脚本模板 | Kotlin |
Note:集成任何其他脚本引擎的基本规则是,它必须实现
ScriptEngine和Invocable接口。
3.4.1. 要求
你需要在你的类路径中有脚本引擎,具体的细节会因脚本引擎的不同而不同:
Nashorn JavaScript 引擎随 Java 8+ 提供。强烈推荐使用最新的更新版本。
应该添加 JRuby 作为对 Ruby 的支持的依赖。
应该添加 Jython 作为对 Python 的支持的依赖。
对于 Kotlin 脚本的支持,应该添加
org.jetbrains.kotlin:kotlin-script-util依赖和一个包含org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory行的META-INF/services/javax.script.ScriptEngineFactory文件。查看此示例以获取更多详细信息。
你需要有脚本模板库。对于 JavaScript,通过 WebJars 是一种方法。
3.4.2. 脚本模板
你可以声明一个 ScriptTemplateConfigurer Bean 来指定要使用的脚本引擎,要加载的脚本文件,要调用的渲染模板的函数等。以下示例使用了 Mustache 模板和 Nashorn JavaScript 引擎:
Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("mustache.js");
configurer.setRenderObject("Mustache");
configurer.setRenderFunction("render");
return configurer;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
以下示例展示了在 XML 中的相同配置:
XML
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:script-template/>
</mvc:view-resolvers>
<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
<mvc:script location="mustache.js"/>
</mvc:script-template-configurer>1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
对于 Java 和 XML 的配置,控制器看起来不会有任何不同,如下例所示:
Java
@Controller
public class SampleController {
@GetMapping("/sample")
public String test(Model model) {
model.addAttribute("title", "Sample title");
model.addAttribute("body", "Sample body");
return "template";
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
以下示例展示了 Mustache 模板:
HTML
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<p>{{body}}</p>
</body>
</html>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
render 函数会被调用,并带有以下参数:
String template:模板内容。Map model:视图模型。RenderingContext renderingContext:RenderingContext提供了对应用上下文、区域设置、模板加载器和 URL(自 5.0 版本开始)的访问。
Mustache.render() 与这个签名原生兼容,所以你可以直接调用它。
如果你的模板技术需要一些定制,你可以提供一个实现自定义渲染功能的脚本。例如,Handlerbars 需要在使用模板之前编译它们,并需要一个 polyfill 来模拟在服务器端脚本引擎中不可用的一些浏览器设施。
以下示例展示了如何做到这一点:
Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
configurer.setRenderFunction("render");
configurer.setSharedEngine(false);
return configurer;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Note:当使用与并发设计不兼容的模板库(如在 Nashorn 上运行的 Handlebars 或 React)的非线程安全脚本引擎时,需要将
sharedEngine属性设置为false。在这种情况下,由于这个 BUG,需要 Java SE 8 update 60,但一般情况下,建议使用最新的 Java SE 补丁版本。
polyfill.js 只定义了 Handlebars 正常运行所需的 window 对象,如下所示:
JavaScript
var window = {};这个基本的 render.js 实现在使用模板之前会先编译它。一个生产就绪的实现还应该存储任何重用的缓存模板或预编译的模板。你可以在脚本端做到这一点(并处理你需要的任何定制 —— 例如,管理模板引擎配置)。以下示例展示了如何做到这一点:
JavaScript
function render(template, model) {
var compiledTemplate = Handlebars.compile(template);
return compiledTemplate(model);
}1
2
3
4
2
3
4
查看 Spring 框架的单元测试、Java 和资源,以获取更多的配置示例。
3.5. JSP 和 JSTL
Spring 框架内置了与 JSP 和 JSTL 一起使用 Spring MVC 的集成。
3.5.1. 视图解析器
在使用 JSP 进行开发时,你通常会声明一个 InternalResourceViewResolver Bean。
InternalResourceViewResolver 可以用于调度到任何 Servlet 资源,但特别适用于 JSP。作为一种最佳实践,我们强烈建议将你的 JSP 文件放在 'WEB-INF' 目录下的一个目录中,这样客户端就无法直接访问。
XML
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>1
2
3
4
5
2
3
4
5
3.5.2. JSP 与 JSTL 的对比
在使用 JSP 标准标签库(JSTL)时,你必须使用一个特殊的视图类,即 JstlView,因为在诸如 I18N 功能等可以工作之前,JSTL 需要一些准备工作。
3.5.3. Spring 的 JSP 标签库
Spring 提供了请求参数到命令对象的数据绑定,如前面的章节所述。为了方便 JSP 页面的开发,并结合这些数据绑定特性,Spring 提供了一些标签,使事情变得更加容易。所有的 Spring 标签都有 HTML 转义特性,可以启用或禁用字符的转义。
spring.tld 标签库描述符(TLD,Tag Library Descriptor)包含在 spring-webmvc.jar 中。要获取关于各个标签的全面参考,可以浏览 API 参考或查看标签库描述。
3.5.4. Spring 的表单标签库
从 2.0 版本开始,Spring 提供了一套全面的数据绑定感知标签,用于处理使用 JSP 和 Spring Web MVC 时的表单元素。每个标签都支持其对应的 HTML 标签的属性集,使得标签的使用既熟悉又直观。标签生成的 HTML 符合 HTML 4.01/XHTML 1.0。
与其他表单/输入标签库不同,Spring 的表单标签库与 Spring Web MVC 集成,使标签可以访问你的控制器处理的命令对象和参考数据。如我们在以下示例中所示,表单标签使 JSP 更容易开发、阅读和维护。
我们将逐一介绍表单标签,并查看每个标签的使用示例。在某些标签需要进一步注释的地方,我们已经包含了生成的 HTML 片段。
3.5.4.1. 配置
表单标签库捆绑在 spring-webmvc.jar 中。库描述符被称为 spring-form.tld。
要使用此库中的标签,请在你的 JSP 页面顶部添加以下指令:
XML
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>在这里,form 是你想要用于此库中标签的标签名前缀。
3.5.4.2. form 标签
这个标签会渲染一个 HTML 的 form 元素,并向内部标签暴露一个绑定路径以进行绑定。它将命令对象放在 PageContext 中,以便命令对象可以被内部标签访问。这个库中的所有其他标签都是 form 标签的嵌套标签。
假设我们有一个叫做 User 的领域对象。它是一个具有 firstName 和 lastName 等属性的 JavaBean。我们可以将它用作我们的表单控制器的表单支持对象,该控制器返回 form.jsp。以下示例显示了 form.jsp 可能的样子:
XML
<form:form>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
firstName 和 lastName 的值是由页面控制器从放置在 PageContext 的命令对象中获取的。继续阅读以查看更复杂的示例,了解如何在 form 标签中使用内部标签。
以下列表显示了生成的 HTML,它看起来像一个标准的表单:
XML
<form method="POST">
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value="Harry"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value="Potter"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
前面的 JSP 假设表单支持对象的变量名为 command。如果你已经将表单支持对象以另一个名称放入模型中(这绝对是一个最佳实践),你可以将表单绑定到命名变量,如下面的示例所示:
XML
<form:form modelAttribute="user">
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3.5.4.3. input 标签
这个标签默认渲染一个带有绑定值和 type='text' 的 HTML input 元素。关于这个标签的示例,可以参见 form 标签。你也可以使用特定于 HTML5 的类型,如 email、tel、date 等。
3.5.4.4. checkbox 标签
这个标签会渲染一个 HTML input 标签,其 type 设置为 checkbox。
假设我们的 User 有一些偏好,如订阅新闻通讯和一系列的爱好。下面的示例展示了 Preferences 类:
Java
public class Preferences {
private boolean receiveNewsletter;
private String[] interests;
private String favouriteWord;
public boolean isReceiveNewsletter() {
return receiveNewsletter;
}
public void setReceiveNewsletter(boolean receiveNewsletter) {
this.receiveNewsletter = receiveNewsletter;
}
public String[] getInterests() {
return interests;
}
public void setInterests(String[] interests) {
this.interests = interests;
}
public String getFavouriteWord() {
return favouriteWord;
}
public void setFavouriteWord(String favouriteWord) {
this.favouriteWord = favouriteWord;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
对应的 form.jsp 可能如下所示:
XML
<form:form>
<table>
<tr>
<td>Subscribe to newsletter?:</td>
<%-- Approach 1: Property is of type java.lang.Boolean --%>
<td><form:checkbox path="preferences.receiveNewsletter"/></td>
</tr>
<tr>
<td>Interests:</td>
<%-- Approach 2: Property is of an array or of type java.util.Collection --%>
<td>
Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/>
Herbology: <form:checkbox path="preferences.interests" value="Herbology"/>
Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>
</td>
</tr>
<tr>
<td>Favourite Word:</td>
<%-- Approach 3: Property is of type java.lang.Object --%>
<td>
Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/>
</td>
</tr>
</table>
</form:form>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
对于 checkbox 标签,有三种方法可以满足你所有的复选框需求。
方法一:当绑定值的类型为
java.lang.Boolean时,如果绑定值为true,则input(checkbox)被标记为checked。value属性对应于setValue(Object)值属性的解析值。方法二:当绑定值的类型为
array或java.util.Collection时,如果配置的setValue(Object)值存在于绑定的Collection中,则input(checkbox)被标记为checked。方法三:对于任何其他类型的绑定值,如果配置的
setValue(Object)等于绑定值,则input(checkbox)被标记为checked。
请注意,无论采用哪种方法,生成的 HTML 结构都是相同的。以下 HTML 片段定义了一些复选框:
XML
<tr>
<td>Interests:</td>
<td>
Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/>
<input type="hidden" value="1" name="_preferences.interests"/>
Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/>
<input type="hidden" value="1" name="_preferences.interests"/>
Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/>
<input type="hidden" value="1" name="_preferences.interests"/>
</td>
</tr>1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
你可能没有预料到会在每个复选框后看到额外的隐藏字段。当 HTML 页面中的复选框未被选中时,一旦提交表单,其值不会作为 HTTP 请求参数发送到服务器,因此我们需要一种解决方案来解决这个 HTML 的怪癖,以使 Spring 表单数据绑定能够工作。checkbox 标签遵循现有的 Spring 约定,为每个复选框包含一个以下划线(_)为前缀的隐藏参数。通过这样做,你实际上是在告诉 Spring,“复选框在表单中是可见的,无论如何,我希望我的对象反映复选框的状态,该对象将表单数据绑定到其中。”
3.5.4.5. checkboxes 标签
这个标签会渲染多个 HTML input 标签,其 type 设置为 checkbox。
本节在前一节 checkbox 标签的示例基础上进行构建。有时,你可能不希望在你的 JSP 页面中列出所有可能的爱好。你更愿意在运行时提供一个可用选项的列表,并将其传递给标签。这就是 checkboxes 标签的目的。你可以在 items 属性中传入一个包含可用选项的 Array、List 或 Map。通常,绑定的属性是一个集合,以便它可以容纳用户选择的多个值。以下示例显示了使用此标签的 JSP:
XML
<form:form>
<table>
<tr>
<td>Interests:</td>
<td>
<%-- Property is of an array or of type java.util.Collection --%>
<form:checkboxes path="preferences.interests" items="${interestList}"/>
</td>
</tr>
</table>
</form:form>1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
这个示例假设 interestList 是一个作为模型属性可用的 List,其中包含要从中选择的值的字符串。如果你使用 Map,映射条目键被用作值,映射条目的值被用作要显示的标签。你也可以使用自定义对象,其中你可以通过使用 itemValue 提供值的属性名,通过使用 itemLabel 提供标签的属性名。
3.5.4.6. radiobutton 标签
这个标签会渲染一个 HTML input 元素,其 type 设置为 radio。
典型的使用模式涉及到多个标签实例绑定到同一个属性,但具有不同的值,如下面的示例所示:
XML
<tr>
<td>Sex:</td>
<td>
Male: <form:radiobutton path="sex" value="M"/> <br/>
Female: <form:radiobutton path="sex" value="F"/>
</td>
</tr>1
2
3
4
5
6
7
2
3
4
5
6
7
3.5.4.7. radiobuttons 标签
这个标签会渲染多个 HTML input 元素,其 type 设置为 radio。
与 checkboxes 标签一样,你可能希望将可用选项作为运行时变量传入。对于这种用法,你可以使用 radiobuttons 标签。你可以在 items 属性中传入一个包含可用选项的 Array、List 或 Map。如果你使用 Map,映射条目键被用作值,映射条目的值被用作要显示的标签。你也可以使用自定义对象,其中你可以通过使用 itemValue 提供值的属性名,通过使用 itemLabel 提供标签的属性名,如下面的示例所示:
XML
<tr>
<td>Sex:</td>
<td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
</tr>1
2
3
4
2
3
4
3.5.4.8. password 标签
这个标签会渲染一个 HTML input 标签,其 type 设置为 password,并带有绑定值。
XML
<tr>
<td>Password:</td>
<td>
<form:password path="password"/>
</td>
</tr>1
2
3
4
5
6
2
3
4
5
6
请注意,默认情况下,密码值是不显示的。如果你确实希望显示密码值,你可以将 showPassword 属性的值设置为 true,如下面的示例所示:
XML
<tr>
<td>Password:</td>
<td>
<form:password path="password" value="^76525bvHGq" showPassword="true"/>
</td>
</tr>1
2
3
4
5
6
2
3
4
5
6
3.5.4.9. select 标签
这个标签会渲染一个 HTML select 元素。它支持将数据绑定到所选选项,以及使用嵌套的 option 和 options 标签。
假设一个 User 有一份技能列表。相应的 HTML 可能如下所示:
XML
<tr>
<td>Skills:</td>
<td><form:select path="skills" items="${skills}"/></td>
</tr>1
2
3
4
2
3
4
如果 User 的技能是草药学(Herbology),那么 “Skills” 行的 HTML 源代码可能如下所示:
XML
<tr>
<td>Skills:</td>
<td>
<select name="skills" multiple="true">
<option value="Potions">Potions</option>
<option value="Herbology" selected="selected">Herbology</option>
<option value="Quidditch">Quidditch</option>
</select>
</td>
</tr>1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
3.5.4.10. option 标签
这个标签会渲染一个 HTML option 元素。它根据绑定的值设置 selected。以下的 HTML 显示了它的典型输出:
XML
<tr>
<td>House:</td>
<td>
<form:select path="house">
<form:option value="Gryffindor"/>
<form:option value="Hufflepuff"/>
<form:option value="Ravenclaw"/>
<form:option value="Slytherin"/>
</form:select>
</td>
</tr>1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
如果 User 的学院是 Gryffindor,那么 “House” 行的 HTML 源代码可能如下所示:
XML
<tr>
<td>House:</td>
<td>
<select name="house">
<option value="Gryffindor" selected="selected">Gryffindor</option>
<option value="Hufflepuff">Hufflepuff</option>
<option value="Ravenclaw">Ravenclaw</option>
<option value="Slytherin">Slytherin</option>
</select>
</td>
</tr>1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
3.5.4.11. options 标签
这个标签渲染一个 HTML option 元素列表。它根据绑定的值设置 selected 属性。以下的 HTML 显示了它的典型输出:
XML
<tr>
<td>Country:</td>
<td>
<form:select path="country">
<form:option value="-" label="-- Please Select"/>
<form:options items="${countryList}" itemValue="code" itemLabel="name"/>
</form:select>
</td>
</tr>1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
如果 User 居住在 UK,那么 “Country” 行的 HTML 源代码可能如下所示:
XML
<tr>
<td>Country:</td>
<td>
<select name="country">
<option value="-">-- Please Select</option>
<option value="AT">Austria</option>
<option value="UK" selected="selected">United Kingdom</option>
<option value="US">United States</option>
</select>
</td>
</tr>1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
如前面的示例所示,option 标签与 options 标签的组合使用生成了相同的标准 HTML,但是它允许你在 JSP 中明确指定一个仅用于显示(放在它应该在的地方)的值,比如示例中的默认字符串:“-- Please Select”。
items 属性通常填充的是一个集合或者数组的 item 对象。如果指定了 itemValue 和 itemLabel,它们会引用这些 item 对象的 Bean 属性。否则,item 对象本身会被转换成字符串。或者,你可以指定一个 items 的 Map,此时映射的键会被解释为选项值,映射的值对应于选项标签。如果 itemValue 或 itemLabel(或者两者都)恰好也被指定了,那么 item 值属性就应用于映射的键,item 标签属性就应用于映射的值。
3.5.4.12. textarea 标签
这个标签会渲染一个 HTML 的 textarea 元素。以下的 HTML 展示了它的典型输出:
XML
<tr>
<td>Notes:</td>
<td><form:textarea path="notes" rows="3" cols="20"/></td>
<td><form:errors path="notes"/></td>
</tr>1
2
3
4
5
2
3
4
5
3.5.4.13. hidden 标签
这个标签会渲染一个 HTML 的 input 标签,并将 type 设置为 hidden,并带有绑定值。要提交一个未绑定的隐藏值,请使用 HTML 的 input 标签,并将 type 设置为 hidden。以下的 HTML 展示了它的典型输出:
XML
<form:hidden path="house"/>如果我们选择将 house 值作为一个隐藏值提交,那么 HTML 将如下所示:
XML
<input name="house" type="hidden" value="Gryffindor"/>3.5.4.14. errors 标签
这个标签在 HTML 的 span 元素中渲染字段错误。它提供了访问你的控制器中创建的错误,或者与你的控制器关联的任何验证器创建的错误的能力。
假设我们想要在提交表单后显示 firstName 和 lastName 字段的所有错误消息。我们有一个针对 User 类实例的验证器,叫做 UserValidator,如下面的示例所示:
Java
public class UserValidator implements Validator {
public boolean supports(Class candidate) {
return User.class.isAssignableFrom(candidate);
}
public void validate(Object obj, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.");
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
form.jsp 可能如下所示:
XML
<form:form>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
<%-- Show errors for firstName field --%>
<td><form:errors path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
<%-- Show errors for lastName field --%>
<td><form:errors path="lastName"/></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
如果我们在 firstName 和 lastName 字段中提交一个带有空值的表单,那么 HTML 会如下所示:
XML
<form method="POST">
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value=""/></td>
<%-- Associated errors to firstName field displayed --%>
<td><span name="firstName.errors">Field is required.</span></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value=""/></td>
<%-- Associated errors to lastName field displayed --%>
<td><span name="lastName.errors">Field is required.</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
如果我们想要在给定页面显示所有的错误列表呢?下一个示例显示了 errors 标签也支持一些基本的通配符功能。
path="*":显示所有错误;path="lastName":显示与lastName字段相关的所有错误;- 如果省略了
path,只会显示对象错误;
下面的示例在页面顶部显示了一个错误列表,然后是字段旁边的特定字段错误:
XML
<form:form>
<form:errors path="*" cssClass="errorBox"/>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
<td><form:errors path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
<td><form:errors path="lastName"/></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
HTML 会如下所示:
XML
<form method="POST">
<span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value=""/></td>
<td><span name="firstName.errors">Field is required.</span></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value=""/></td>
<td><span name="lastName.errors">Field is required.</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spring-form.tld 标签库描述符(TLD,Tag Library Descriptor)包含在 spring-webmvc.jar 中。要获取关于各个标签的全面参考,可以浏览 API 参考或查看标签库描述。
3.5.4.15. HTTP 方法转换
REST 的一个关键原则是使用 “统一接口”。这意味着所有的资源(URLs)都可以通过使用相同的四种 HTTP 方法进行操作:GET、PUT、POST 和 DELETE。对于每种方法,HTTP 规范都定义了确切的语义。例如,GET 应该始终是一个安全的操作,意味着它没有副作用,而 PUT 或 DELETE 应该是幂等的,意味着你可以反复进行这些操作,但最终结果应该是相同的。虽然 HTTP 定义了这四种方法,但 HTML 只支持两种:GET 和 POST。幸运的是,有两种可能的解决方案:你可以使用 JavaScript 来执行你的 PUT 或 DELETE,或者你可以使用 “real” 方法作为一个额外的参数(作为 HTML 表单中的一个隐藏输入字段)来执行 POST。Spring 的 HiddenHttpMethodFilter 使用了这个后一种技巧。这个过滤器是一个普通的 Servlet 过滤器,因此,它可以与任何 Web 框架(不仅仅是 Spring MVC)结合使用。将此过滤器添加到你的 web.xml 中,带有隐藏 method 参数的 POST 将被转换为相应的 HTTP 方法请求。
为了支持 HTTP 方法转换,Spring MVC 的 form 标签已经更新,以支持设置 HTTP 方法。例如,下面的片段来自 Pet Clinic 示例:
XML
<form:form method="delete">
<p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>1
2
3
2
3
前面的示例执行了一个 HTTP POST,其中 “real” 的 DELETE 方法隐藏在请求参数后面。它被定义在 web.xml 中的 HiddenHttpMethodFilter 捕获,如下面的示例所示:
XML
<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<servlet-name>petclinic</servlet-name>
</filter-mapping>1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
下面的示例显示了相应的 @Controller 方法:
Java
@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
this.clinic.deletePet(petId);
return "redirect:/owners/" + ownerId;
}1
2
3
4
5
2
3
4
5
3.5.4.16. HTML5 标签
Spring 的 form 标签库允许输入动态属性,这意味着你可以输入任何 HTML5 特定的属性。
Form input 标签支持输入除 text 之外的 type 属性。这是为了允许渲染新的 HTML5 特定的输入类型,如 email、date、range 等。请注意,输入 type='text' 不是必需的,因为 text 是默认类型。
3.6. Tiles
你可以将 Tiles(就像任何其他视图技术一样)集成到使用 Spring 的 Web 应用程序中。本节以宽泛的方式描述了如何做到这一点。
Note:本节主要关注 Spring 对
org.springframework.web.servlet.view.tiles3包中 Tiles 版本 3 的支持。
3.6.1. 依赖
要使用 Tiles,你必须在你的项目中添加对 Tiles 版本 3.0.1 或更高版本及其传递依赖的依赖。
3.6.2. 配置
要使用 Tiles,你必须通过使用包含定义的文件来配置它(有关定义和其他 Tiles 概念的基本信息,请参见 https://tiles.apache.org)。在 Spring 中,这是通过使用 TilesConfigurer 来完成的。以下示例 ApplicationContext 配置展示了如何做到这一点:
XML
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/general.xml</value>
<value>/WEB-INF/defs/widgets.xml</value>
<value>/WEB-INF/defs/administrator.xml</value>
<value>/WEB-INF/defs/customer.xml</value>
<value>/WEB-INF/defs/templates.xml</value>
</list>
</property>
</bean>1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
前述示例定义了包含定义的五个文件。这些文件都位于 WEB-INF/defs 目录中。在 WebApplicationContext 初始化时,这些文件被加载,定义工厂被初始化。完成这些操作后,定义文件中包含的 Tiles 可以在你的 Spring Web 应用程序中用作视图。要能够使用这些视图,你必须有一个 ViewResolver,就像 Spring 中的任何其他视图技术一样:通常是一个方便的 TilesViewResolver。
你可以通过添加下划线和地区来指定特定于地区的 Tiles 定义,如下例所示:
XML
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/tiles.xml</value>
<value>/WEB-INF/defs/tiles_fr_FR.xml</value>
</list>
</property>
</bean>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
在前述配置中,tiles_fr_FR.xml 用于 fr_FR 地区的请求,而 tiles.xml 是默认使用的。
Note:由于下划线被用来表示地区,我们建议在 Tiles 定义的文件名中不要使用下划线。
3.6.2.1. UrlBasedViewResolver
UrlBasedViewResolver 会为每个需要解析的视图实例化给定的 viewClass。以下的 Bean 定义了一个 UrlBasedViewResolver:
XML
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
</bean>1
2
3
2
3
3.6.2.2. SimpleSpringPreparerFactory 和 SpringBeanPreparerFactory
作为一种高级特性,Spring 也支持两种特殊的 Tiles PreparerFactory 实现。请参阅 Tiles 文档以了解如何在你的 Tiles 定义文件中使用 ViewPreparer 引用。
你可以指定 SimpleSpringPreparerFactory 来根据指定的 preparer 类自动装配 ViewPreparer 实例,应用 Spring 的容器回调以及应用配置的 Spring BeanPostProcessors。如果已激活 Spring 的上下文范围内的注解配置,则会自动检测并应用 ViewPreparer 类中的注解。请注意,这期望在 Tiles 定义文件中有 preparer 类,就像默认的 PreparerFactory 一样。
你可以指定 SpringBeanPreparerFactory 来操作指定的 preparer 名称(而不是类),从 DispatcherServlet 的应用上下文中获取相应的 Spring Bean。在这种情况下,完整的 Bean 创建过程由 Spring 应用上下文控制,允许使用显式依赖注入配置、作用域 Bean 等。请注意,你需要为每个 preparer 名称(如在你的 Tiles 定义中使用)定义一个 Spring Bean 定义。以下示例显示了如何在 TilesConfigurer Bean 上定义一个 SpringBeanPreparerFactory 属性:
XML
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/general.xml</value>
<value>/WEB-INF/defs/widgets.xml</value>
<value>/WEB-INF/defs/administrator.xml</value>
<value>/WEB-INF/defs/customer.xml</value>
<value>/WEB-INF/defs/templates.xml</value>
</list>
</property>
<!-- resolving preparer names as Spring bean definition names -->
<property name="preparerFactoryClass"
value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/>
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
3.7. RSS 和 Atom
AbstractAtomFeedView 和 AbstractRssFeedView 都继承自 AbstractFeedView 基类,分别用于提供 Atom 和 RSS Feed 视图。它们基于 ROME 项目,并位于 org.springframework.web.servlet.view.feed 包中。
AbstractAtomFeedView 要求你实现 buildFeedEntries() 方法,并可选择覆盖 buildFeedMetadata() 方法(默认实现为空)。以下示例显示了如何操作:
Java
public class SampleContentAtomView extends AbstractAtomFeedView {
@Override
protected void buildFeedMetadata(Map<String, Object> model,
Feed feed, HttpServletRequest request) {
// implementation omitted
}
@Override
protected List<Entry> buildFeedEntries(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
// implementation omitted
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
实现 AbstractRssFeedView 的要求与此类似,如下例所示:
Java
public class SampleContentRssView extends AbstractRssFeedView {
@Override
protected void buildFeedMetadata(Map<String, Object> model,
Channel feed, HttpServletRequest request) {
// implementation omitted
}
@Override
protected List<Item> buildFeedItems(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
// implementation omitted
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
buildFeedItems() 和 buildFeedEntries() 方法传入 HTTP 请求,以防你需要访问 Locale。HTTP 响应仅用于设置 Cookies 或其他 HTTP 头。方法返回后,Feed 会自动写入到响应对象中。
有关创建 Atom 视图的示例,请参见 Alef Arendsen 的 Spring 团队博客条目。
3.8. PDF 和 Excel
Spring 提供了除 HTML 之外的其他输出方式,包括 PDF 和 Excel 电子表格。本节将描述如何使用这些功能。
3.8.1. 文档视图介绍
HTML 页面并不总是用户查看模型输出的最佳方式,Spring 简化了从模型数据动态生成 PDF 文档或 Excel 电子表格的过程。这个文档就是视图,它会以正确的内容类型从服务器 “Stream” 出来,以便(希望)使客户端 PC 运行他们的电子表格或 PDF 查看器应用程序。
为了使用 Excel 视图,你需要将 Apache POI 库添加到你的类路径中。对于 PDF 生成,你需要添加(最好是)OpenPDF 库。
Note:如果可能的话,你应该使用底层文档生成库的最新版本。特别是,我们强烈推荐 OpenPDF(例如,OpenPDF 1.2.12)而不是过时的原始 iText 2.1.7,因为 OpenPDF 正在积极维护,并修复了对不受信任的 PDF 内容的重要漏洞。
3.8.2. PDF 视图
一个简单的单词列表的 PDF 视图可以扩展 org.springframework.web.servlet.view.document.AbstractPdfView 并实现 buildPdfDocument() 方法,如下面的示例所示:
Java
public class PdfWordList extends AbstractPdfView {
protected void buildPdfDocument(Map<String, Object> model, Document doc, PdfWriter writer,
HttpServletRequest request, HttpServletResponse response) throws Exception {
List<String> words = (List<String>) model.get("wordList");
for (String word : words) {
doc.add(new Paragraph(word));
}
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
控制器可以从外部视图定义(通过名称引用)返回这样的视图,或者从处理器方法返回一个 View 实例。
3.8.3. Excel 视图
自 Spring Framework 4.2 版本以来,org.springframework.web.servlet.view.document.AbstractXlsView 被提供为 Excel 视图的基类。它基于 Apache POI,有专门的子类(AbstractXlsxView 和 AbstractXlsxStreamingView)取代了过时的 AbstractExcelView 类。
编程模型与 AbstractPdfView 类似,以 buildExcelDocument() 作为中心模板方法,控制器可以从外部定义(通过名称)返回这样的视图,或者从处理器方法返回一个 View 实例。
3.9. Jackson
Spring 提供了对 Jackson JSON 库的支持。
3.9.1. 基于 Jackson 的 JSON MVC 视图
MappingJackson2JsonView 使用 Jackson 库的 ObjectMapper 来将响应内容渲染为 JSON。默认情况下,模型映射的全部内容(除了框架特定的类)都会被编码为 JSON。对于需要过滤映射内容的情况,你可以使用 modelKeys 属性来指定一组特定的模型属性进行编码。你还可以使用 extractValueFromSingleKeyModel 属性来将单键模型中的值直接提取并序列化,而不是作为模型属性的映射。
你可以根据需要使用 Jackson 提供的注解来自定义 JSON 映射。当你需要进一步控制时,你可以通过 ObjectMapper 属性注入一个自定义的 ObjectMapper,用于提供特定类型的自定义 JSON 序列化器和反序列化器。
3.9.2. 基于 Jackson 的 XML 视图
MappingJackson2XmlView 使用 Jackson XML 扩展的 XmlMapper 来将响应内容渲染为 XML。如果模型包含多个条目,你应该通过使用 modelKey Bean 属性明确设置要序列化的对象。如果模型只包含一个条目,它会自动被序列化。
你可以根据需要使用 JAXB 或 Jackson 提供的注解来自定义 XML 映射。当你需要进一步控制时,你可以通过 ObjectMapper 属性注入一个自定义的 XmlMapper,用于提供特定类型的自定义 XML 序列化器和反序列化器。
3.10. XML Marshalling
MarshallingView 使用 XML Marshaller(在 org.springframework.oxm 包中定义)来将响应内容渲染为 XML。你可以通过使用 MarshallingView 实例的 modelKey Bean 属性明确设置要 Marshal 的对象。或者,视图会遍历所有模型属性,并 Marshal Marshaller 支持的第一种类型。有关 org.springframework.oxm 包中功能的更多信息,请参阅使用 O/X Mappers Marshal XML。
3.11. XSLT 视图
XSLT 是一种 XML 转换语言,在 Web 应用程序中作为视图技术非常流行。如果你的应用程序自然处理 XML,或者你的模型可以轻易地转换为 XML,那么 XSLT 可以是一个很好的视图技术选择。下面的部分将展示如何在 Spring Web MVC 应用程序中生成一个作为模型数据的 XML 文档,并使用 XSLT 进行转换。
这个示例是一个简单的 Spring 应用程序,它在控制器中创建一个单词列表并将它们添加到模型映射中。映射和我们的 XSLT 视图的视图名称一起返回。请参阅注解控制器以获取 Spring Web MVC 的控制器接口的详细信息。XSLT 控制器将单词列表转换为一个准备好进行转换的简单 XML 文档。
3.11.1. Beans
对于一个简单的 Spring Web 应用程序,配置是标准的:MVC 配置必须定义一个 XsltViewResolver Bean 和常规的 MVC 注解配置。下面的示例展示了如何做到这一点:
Java
@EnableWebMvc
@ComponentScan
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public XsltViewResolver xsltViewResolver() {
XsltViewResolver viewResolver = new XsltViewResolver();
viewResolver.setPrefix("/WEB-INF/xsl/");
viewResolver.setSuffix(".xslt");
return viewResolver;
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
3.11.2. 控制器
我们还需要一个封装了我们的单词生成逻辑的控制器。
控制器逻辑被封装在一个 @Controller 类中,处理器方法定义如下:
Java
@Controller
public class XsltController {
@RequestMapping("/")
public String home(Model model) throws Exception {
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element root = document.createElement("wordList");
List<String> words = Arrays.asList("Hello", "Spring", "Framework");
for (String word : words) {
Element wordNode = document.createElement("word");
Text textNode = document.createTextNode(word);
wordNode.appendChild(textNode);
root.appendChild(wordNode);
}
model.addAttribute("wordList", root);
return "home";
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
到目前为止,我们只创建了一个 DOM 文档并将其添加到模型映射中。请注意,你也可以将 XML 文件作为 Resource 加载,并用它代替自定义的 DOM 文档。
有一些软件包可以自动 “domify” 对象图,但在 Spring 中,你可以完全自由地以任何你选择的方式从模型创建 DOM。这防止了 XML 转换在你的模型数据结构中扮演太重要的角色,这在使用工具来管理 DOM 化过程时是很危险的。
3.11.3. 转换
最后,XsltViewResolver 解析 “home” XSLT 模板文件,并将 DOM 文档合并到其中以生成我们的视图。如 XsltViewResolver 配置中所示,XSLT 模板位于 war 文件的 WEB-INF/xsl 目录中,并以 xslt 文件扩展名结束。
下面的示例展示了一个 XSLT 转换:
XML
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" omit-xml-declaration="yes"/>
<xsl:template match="/">
<html>
<head><title>Hello!</title></head>
<body>
<h1>My First Words</h1>
<ul>
<xsl:apply-templates/>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="word">
<li><xsl:value-of select="."/></li>
</xsl:template>
</xsl:stylesheet>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
前面的转换将被渲染为以下 HTML:
HTML
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello!</title>
</head>
<body>
<h1>My First Words</h1>
<ul>
<li>Hello</li>
<li>Spring</li>
<li>Framework</li>
</ul>
</body>
</html>1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14