Spring Web MVC:视图技术

https://docs.spring.io/spring-framework/reference/web/webmvc-view.html

在Spring MVC中使用视图技术是可插拔的。无论你决定使用Thymeleaf、Groovy Markup Templates、JSPs还是其他技术,主要是通过配置变更来实现的。本篇将介绍与Spring MVC集成的视图技术。

Spring MVC应用程序的视图存在于该应用程序的内部信任边界中。视图可以访问应用程序上下文的所有bean。因此,在模板可以被外部来源编辑的应用程序中使用Spring MVC的模板支持是不推荐的,因为这可能会带来安全隐患。

Thymeleaf

Thymeleaf是一个现代的服务器端Java模板引擎,它强调可以双击在浏览器中预览的自然HTML模板,这对于独立工作于UI模板(例如,由设计师)非常有用,无需运行服务器。如果你想替换JSPs,Thymeleaf提供了最广泛的特性集之一,以便更容易地进行这种过渡。Thymeleaf正在积极开发和维护中。

Thymeleaf与Spring MVC的集成由Thymeleaf项目管理。配置涉及到一些bean声明,如ServletContextTemplateResolver、SpringTemplateEngine和ThymeleafViewResolver。

FreeMarker

Apache FreeMarker是一个模板引擎,用于生成从HTML到电子邮件等各种文本输出。Spring框架具有内置的集成功能,可以使用Spring MVC和FreeMarker模板。

视图配置

以下示例演示了如何将FreeMarker配置为视图技术:

@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;
	}
}

以下示例演示了如何在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>

或者,也可以声明FreeMarkerConfigurer bean以完全控制所有属性,如下例所示:

<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
	<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
</bean>

模板需要存储在由FreeMarkerConfigurer指定的目录中,如前面示例所示。根据前面的配置,如果控制器返回welcome的视图名称,解析器将查找/WEB-INF/freemarker/welcome.ftl模板。

FreeMarker 配置

可以通过在FreeMarkerConfigurer bean上设置适当的bean属性,直接将FreeMarker的“Settings”和“SharedVariables”传递给由Spring管理的FreeMarker Configuration对象。freemarkerSettings属性需要一个java.util.Properties对象,而freemarkerVariables属性需要一个java.util.Map。以下示例展示了如何使用FreeMarkerConfigurer:

<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"/>

表单处理

Spring提供了一个用于JSP的标签库,其中包含<spring:bind/>元素。该元素主要用于让表单显示来自表单绑定对象的值,并展示Web层或业务层中验证器失败的验证结果。Spring还支持在FreeMarker中实现相同的功能,并提供了一些方便的宏来生成表单输入元素本身。

绑定宏

在spring-webmvc.jar文件中维护了一组标准的FreeMarker宏,因此它们始终可用于适当配置的应用程序。

在Spring模板库中定义的一些宏被认为是内部的(私有的),但在宏定义中没有这样的范围限制,这使得所有宏都对调用代码和用户模板可见。以下部分仅关注你需要在模板内直接调用的宏。如果希望直接查看宏代码,该文件名为spring.ftl,位于org.springframework.web.servlet.view.freemarker包中。

简单绑定

在基于FreeMarker模板的HTML表单中,作为Spring MVC控制器的表单视图,可以使用类似于以下示例的代码来绑定字段值并显示每个输入字段的错误消息,与JSP等效项类似。以下示例显示了一个personForm视图:

<!-- 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>

<@spring.bind>需要一个’path’参数,该参数由你的命令对象的名称(除非你在控制器配置中更改了它,否则为’command’)后跟一个句点和你希望绑定的命令对象上的字段名组成。还可以使用嵌套字段,例如command.address.street。绑定宏假设由web.xml中的ServletContext参数defaultHtmlEscape指定的默认HTML转义行为。

宏的另一种形式<@spring.bindEscaped>接受第二个参数,该参数明确指定是否应在状态错误消息或值中使用HTML转义。可以根据需要将其设置为true或false。其他表单处理宏简化了HTML转义的使用,应尽可能使用这些宏。

输入宏

FreeMarker的其他便利宏简化了绑定和表单生成(包括验证错误显示)。使用这些宏生成表单输入字段从来都不是必需的,可以将它们与简单的HTML或直接调用我们之前强调的Spring绑定宏混合使用。

以下表格显示了可用的宏,以及每个宏所采用的FreeMarker模板(FTL)定义和参数列表:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在FreeMarker模板中,实际上不需要formHiddenInput和formPasswordInput,因为可以使用正常的formInput宏,将hidden或password指定为fieldType参数的值。

上述任何宏的参数都具有一致的含义:

  • path:要绑定的字段的名称(例如,“command.name”)
  • options:输入字段中可以选择的所有可用值的Map。Map中的键表示从表单POST回来并绑定到命令对象的值。针对键存储的Map对象是在表单上显示给用户的标签,可能与表单POST回来的相应值不同。通常,这样的Map由控制器作为引用数据提供。可以根据所需的行为使用任何Map实现。对于严格排序的Map,可以使用带有合适Comparator的SortedMap(如TreeMap),对于应返回插入顺序值的任意Map,可以使用LinkedHashMap或来自commons-collections的LinkedMap。
  • separator:在多个选项作为离散元素(单选按钮或复选框)提供时,用于分隔列表中每个元素的字符序列(如<br> )。
  • attributes:要在HTML标签本身内包含的任意标签或文本的额外字符串。该字符串由宏直接输出。例如,在textarea字段中,可以提供属性(如’rows=“5” cols=“60”‘),或者可以传递样式信息,如’style=“border:1px solid silver”’。
  • classOrStyle:对于showErrors宏,包装每个错误的span元素使用的CSS类的名称。如果没有提供信息(或者值为空),则错误被包装在<b></b>标签中。
输入字段

formInput宏接受path参数(command.name)和额外的attributes参数(在即将到来的示例中为空)。该宏以及所有其他表单生成宏都会对path参数执行隐式的Spring绑定。绑定保持有效,直到发生新的绑定,因此showErrors宏不需要再次传递path参数——它作用于最近创建绑定的字段。

showErrors宏接受一个separator参数(用于分隔给定字段上的多个错误的字符)并且还接受第二个参数——这次是一个类名或样式属性。请注意,FreeMarker可以为attributes参数指定默认值。以下示例展示了如何使用formInput和showErrors宏:

<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>

下一个示例显示了表单片段的输出,生成了name字段并在表单提交时该字段没有值的情况下显示验证错误。验证通过Spring的Validation框架进行。

生成的HTML类似于以下示例:

Name:
<input type="text" name="name" value="">
<br>
	<b>required</b>
<br>
<br>

formTextarea宏的工作方式与formInput宏相同,并接受相同的参数列表。通常,第二个参数(attributes)用于传递样式信息或textarea的rows和cols属性。

选择字段

可以使用四个选择字段宏在HTML表单中生成常见的UI值选择输入:

  • formSingleSelect
  • formMultiSelect
  • formRadioButtons
  • formCheckboxes

这四个宏中的每个都接受一个包含表单字段值及其对应标签的Map。值和标签可以是相同的。

下一个示例是FTL中的单选按钮。表单支持对象为该字段指定了默认值’London’,因此不需要验证。当呈现表单时,整个可供选择的城市列表在模型中作为参考数据提供,名称为’cityMap’。以下清单显示了示例:

...
Town:
<@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>

前面的清单呈现了一行单选按钮,每个按钮对应cityMap中的一个值,并使用空字符串作为分隔符。没有提供额外的属性(宏的最后一个参数缺失)。cityMap在映射中为每个键值对使用相同的字符串。映射的键是表单实际作为POST请求参数提交的内容。映射值是用户看到的标签。在前面的示例中,给定三个著名城市的列表和表单支持对象中的默认值,HTML类似于以下内容:

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>

如果你的应用程序期望通过内部代码处理城市(例如),可以创建带有适当键的代码映射,如下所示:

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;
}

代码现在生成的输出中,单选按钮的值是相关代码,但用户仍然看到的是更友好的城市名称,如下所示:

HTML转义

默认情况下,使用前面描述的表单宏会生成符合HTML 4.01标准的HTML元素,并使用web.xml文件中定义的HTML转义的默认值,这是Spring绑定支持所使用的。为了使元素符合XHTML标准或覆盖默认的HTML转义值,可以在模板中(或在模型中,如果它们对你的模板可见)指定两个变量。在模板中指定它们的优势在于,它们可以在模板处理过程中被更改为不同的值,以为表单中的不同字段提供不同的行为。

要切换到符合XHTML标准的标签,请为名为xhtmlCompliant的模型或上下文变量指定true值,如下所示:

<#-- for FreeMarker -->
<#assign xhtmlCompliant = true>

处理此指令后,由Spring宏生成的任何元素现在都符合XHTML标准。

类似地,可以为每个字段指定HTML转义,如下所示:

<#-- 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 -->

Groovy Markup

Groovy Markup模板引擎主要旨在生成类似XML的标记(XML、XHTML、HTML5等),但也可以使用它来生成任何基于文本的内容。Spring框架内置了将Spring MVC与Groovy Markup结合使用的功能。

Groovy Markup模板引擎需要Groovy 2.3.1+。

配置

以下示例显示了如何配置Groovy Markup模板引擎:

@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;
	}
}

以下示例显示了如何在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/"/>

示例

与传统模板引擎不同,Groovy Markup依赖于使用构建器语法的DSL。以下示例显示了一个HTML页面的样本模板:

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')
	}
}

脚本视图

Spring框架内置了Spring MVC与任何能在JSR-223 Java脚本引擎之上运行的模板库进行集成的功能。在不同的脚本引擎上测试了以下模板库:
在这里插入图片描述

集成任何其他脚本引擎的基本规则是,它必须实现ScriptEngine和Invocable接口。

要求

需要在类路径上拥有脚本引擎,不同脚本引擎的具体细节会有所不同:

  • Nashorn JavaScript引擎随Java 8+提供。建议使用最新的更新版本。
  • 要支持Ruby,应将JRuby添加为依赖项。
  • 要支持Python,应将Jython添加为依赖项。
  • 要支持Kotlin脚本,应添加org.jetbrains.kotlin:kotlin-script-util依赖项,并在META-INF/services/javax.script.ScriptEngineFactory文件中添加一行org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory。

你需要有脚本模板库。对于JavaScript来说,一种方法是通过WebJars来实现。

脚本模板

可以声明一个ScriptTemplateConfigurer bean来指定要使用的脚本引擎、要加载的脚本文件、用于渲染模板的函数等。以下示例使用Mustache模板和Nashorn JavaScript引擎:

@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;
	}
}

以下示例显示了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>

控制器对于Java和XML配置看起来没有区别,如下所示:

@Controller
public class SampleController {

	@GetMapping("/sample")
	public String test(Model model) {
		model.addAttribute("title", "Sample title");
		model.addAttribute("body", "Sample body");
		return "template";
	}
}

以下示例显示了Mustache模板:

<html>
	<head>
		<title>{{title}}</title>
	</head>
	<body>
		<p>{{body}}</p>
	</body>
</html>

渲染函数使用以下参数调用:

  • String template:模板内容
  • Map model:视图模型
  • RenderingContext renderingContext:提供对应用程序上下文、区域设置、模板加载器和URL(自5.0起)访问的RenderingContext

Mustache.render()与这个签名天然兼容,因此可以直接调用它。

如果你的模板技术需要一些定制,可以提供一个实现自定义渲染函数的脚本。例如,Handlebars需要在使用模板之前编译它们,并需要一个polyfill来模拟服务器端脚本引擎中不可用的浏览器功能。

以下示例显示了如何做到这一点:

@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;
	}
}

在使用非线程安全的脚本引擎与未针对并发设计的模板库(如在Nashorn上运行的Handlebars或React)时,需要将sharedEngine属性设置为false。在这种情况下,由于这个bug,需要Java SE 8 update 60,但通常建议使用最新的Java SE补丁版本。

polyfill.js定义了Handlebars正确运行所需的window对象,如下所示:

var window = {};

这个基本的render.js实现在使用模板之前编译它。一个生产就绪的实现还应该存储任何重复使用的缓存模板或预编译模板。可以在脚本端执行此操作(并处理需要的自定义内容,例如管理模板引擎配置)。以下示例显示了如何做到这一点:

function render(template, model) {
	var compiledTemplate = Handlebars.compile(template);
	return compiledTemplate(model);
}

JSP 和 JSTL

Spring框架内置了将Spring MVC与JSP和JSTL结合使用的功能。

视图解析器

在使用JSP进行开发时,通常声明一个InternalResourceViewResolver bean。

InternalResourceViewResolver可以用于分派到任何Servlet资源,特别是JSP。作为一个最佳实践,建议您将JSP文件放置在’WEB-INF’目录下的某个目录中,这样客户端就无法直接访问它们。

<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>

JSP与JSTL

使用JSP标准标签库(JSTL)时,必须使用特殊的视图类JstlView,因为在使用诸如I18N特性等功能之前,JSTL需要进行一些准备工作。

Spring的JSP标签库

Spring提供了请求参数到命令对象的数据绑定。为了便于结合这些数据绑定特性开发JSP页面,Spring提供了一些标签,使事情变得更加简单。所有Spring标签都具有HTML转义功能,可以启用或禁用字符转义。

spring.tld标签库描述符(TLD)包含在spring-webmvc.jar中。

Spring的表单标签库

从2.0版本开始,Spring提供了一套全面的数据绑定感知标签,用于在使用JSP和Spring Web MVC时处理表单元素。每个标签都支持其对应的HTML标签的属性集,使得这些标签使用起来熟悉且直观。标签生成的HTML符合HTML 4.01/XHTML 1.0标准。

与其他表单/输入标签库不同,Spring的表单标签库与Spring Web MVC集成,使标签能够访问控制器处理的命令对象和引用数据。表单标签使得JSP的开发、阅读和维护变得更加简单。

配置

表单标签库打包在spring-webmvc.jar中。库描述符称为spring-form.tld。

要使用这个库中的标签,在JSP页面顶部添加以下指令:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

其中form是你希望用于该库标签的标签名前缀。

表单标签

这个标签渲染一个HTML 'form’元素,并为内部标签暴露一个绑定路径以进行绑定。它将命令对象放置在PageContext中,以便内部标签可以访问命令对象。库中的所有其他标签都是form 标签的嵌套标签。

假设我们有一个名为User的领域对象。它是一个带有firstName和lastName属性的JavaBean。我们可以将其作为表单控制器的表单后端对象,该控制器返回form.jsp。以下示例显示了form.jsp可能的样子:

<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>

firstName和lastName值是从页面控制器放置在PageContext中的命令对象中检索的。

以下清单显示了生成的HTML,看起来像一个标准的表单:

<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>

前面的JSP假设表单后端对象的变量名是command。如果你已经将表单后端对象以另一个名称放入模型中(这绝对是一个最佳实践),可以将表单绑定到命名变量,如下例所示:

<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>

input 标签

这个标签渲染一个带有绑定值的HTML input 元素,默认类型为’text’。还可以使用HTML5特定的类型,如email、tel、date等。

checkbox 标签

这个标签渲染一个类型设置为checkbox的HTML input 标签。

假设我们的User有偏好,比如订阅新闻通讯和一系列爱好。以下示例显示了Preferences类:

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;
	}
}

相应的form.jsp可能如下所示:

<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>

复选框标签有三种方法,应该可以满足你所有的复选框需求。

  • 方法一:当绑定值为java.lang.Boolean类型时,如果绑定值为true,则将input(checkbox)标记为选中。value属性对应于setValue(Object) value属性的解析值。
  • 方法二:当绑定值为数组或java.util.Collection类型时,如果配置的setValue(Object)值出现在绑定的集合中,则将input(checkbox)标记为选中。
  • 方法三:对于任何其他绑定值类型,如果配置的setValue(Object)等于绑定值,则将input(checkbox)标记为选中。

注意,无论采用哪种方法,都会生成相同的HTML结构。以下HTML片段定义了一些复选框:

<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>

你可能不希望在每个复选框之后看到额外的隐藏字段。当HTML页面中的复选框未选中时,一旦提交表单,其值不会作为HTTP请求参数发送到服务器,因此我们需要一种变通方法来处理HTML的这一特性,以便Spring表单数据绑定能够正常工作。复选框标签遵循现有的Spring约定,为每个复选框包含一个以前缀下划线(_)开头的隐藏参数。通过这样做,实际上是在告诉Spring“复选框在表单中是可见的,我希望将表单数据绑定到的对象反映出复选框的状态。”

checkboxes 标签

这个标签渲染多个类型设置为checkbox的HTML input 标签。

本节基于前一个复选框标签部分的示例。有时,可能不希望在JSP页面中列出所有可能的爱好。更希望在运行时提供可用选项的列表并将其传递给标签。这就是checkboxes 标签的目的。可以在items属性中传入一个数组、列表或包含可用选项的映射。通常,绑定属性是一个集合,以便它可以保存用户选择的多个值。以下示例显示了使用此标签的JSP:

<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>

这个例子假设interestList是一个模型属性中可用的列表,其中包含要从中选择的值的字符串。如果使用映射,映射条目键将用作值,映射条目的值将用作要显示的标签。还可以使用自定义对象,通过使用itemValue提供值的属性名称,通过使用itemLabel提供标签。

radiobutton 标签

这个标签渲染一个类型设置为radio的HTML input 元素。

典型的使用模式涉及多个绑定到相同属性但具有不同值的标签实例,如下例所示:

<tr>
	<td>Sex:</td>
	<td>
		Male: <form:radiobutton path="sex" value="M"/> <br/>
		Female: <form:radiobutton path="sex" value="F"/>
	</td>
</tr>

radiobuttons 标签

这个标签渲染多个类型设置为radio的HTML input 元素。

与checkboxes标签一样,你可能希望将可用选项作为运行时变量传递。对于这种用法,可以使用radiobuttons 标签。可以在items属性中传入一个数组、列表或包含可用选项的映射。如果使用映射,映射条目键将用作值,映射条目的值将用作要显示的标签。还可以使用自定义对象,通过使用itemValue提供值的属性名称,通过使用itemLabel提供标签,如下例所示:

<tr>
	<td>Sex:</td>
	<td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
</tr>

password 标签

这个标签渲染一个类型设置为password 的HTML input 标签,并带有绑定值。

<tr>
	<td>Password:</td>
	<td>
		<form:password path="password"/>
	</td>
</tr>

默认情况下,密码值不会显示。如果确实希望显示密码值,可以将showPassword属性的值设置为true,如下例所示:

<tr>
	<td>Password:</td>
	<td>
		<form:password path="password" value="^76525bvHGq" showPassword="true"/>
	</td>
</tr>

select 标签

这个标签渲染一个HTML 'select’元素。它支持绑定到选定选项的数据,以及使用嵌套的option和options标签。

假设User有一系列技能。相应的HTML可能如下:

<tr>
	<td>Skills:</td>
	<td><form:select path="skills" items="${skills}"/></td>
</tr>

如果User的技能在草药学方面,'Skills’行的HTML源代码可能如下:

<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>

option 标签

这个标签渲染一个HTML option元素。它根据绑定值设置selected。以下HTML显示了它的典型输出:

<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>

如果User的学院在格兰芬多,'House’行的HTML源代码可能如下:

<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>

options 标签

这个标签渲染一系列HTML option元素。它根据绑定值设置selected属性。以下HTML显示了它的典型输出:

<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>

如果User住在英国,'Country’行的HTML源代码可能如下:

<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>

如前面的示例所示,option标签与options标签的结合使用生成了相同的标准HTML,但允许在JSP中显式指定仅用于显示的值(例如示例中的默认字符串:“-- 请选择”)。

items属性通常使用项目对象的集合或数组填充。如果指定了itemValue和itemLabel,它们将引用这些项目对象的bean属性。否则,项目对象本身将被转换为字符串。或者,可以指定一个项目的映射,在这种情况下,映射键被解释为选项值,映射值对应于选项标签。如果同时指定了itemValue或itemLabel(或两者),则项目值属性适用于映射键,项目标签属性适用于映射值。

textarea 标签

这个标签渲染一个HTML textarea元素。以下HTML显示了它的典型输出:

<tr>
	<td>Notes:</td>
	<td><form:textarea path="notes" rows="3" cols="20"/></td>
	<td><form:errors path="notes"/></td>
</tr>

hidden 标签

这个标签渲染一个类型设置为hidden的HTML input 标签,并带有绑定值。要提交未绑定的隐藏值,请使用类型设置为hidden的HTML input 标签。以下HTML显示了它的典型输出:

<form:hidden path="house"/>

如果我们选择将house值作为隐藏值提交,HTML可能如下:

<input name="house" type="hidden" value="Gryffindor"/>

errors 标签

这个标签在HTML span元素中渲染字段错误。它提供了对控制器中创建的错误或与控制器关联的任何验证器创建的错误的访问。

假设我们想在提交表单后显示firstName和lastName字段的所有错误消息。我们有一个针对User类实例的验证器,名为UserValidator,如下所示:

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.");
	}
}

form.jsp可能如下:

<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>

如果我们提交一个表单,firstName和lastName字段的值为空,HTML可能如下:

<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>

如果我们想显示给定页面的完整错误列表怎么办?下一个示例表明,errors标签还支持一些基本的通配符功能。

  • path=“*”:显示所有错误。
  • path=“lastName”:显示与lastName字段相关的所有错误。
  • 如果省略了path,只显示对象错误。

以下示例在页面顶部显示错误列表,然后在字段旁边显示特定于字段的错误:

<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>

HTML可能如下:

<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>

spring-form.tld标签库描述符(TLD)包含在spring-webmvc.jar中。

HTTP方法转换

REST的一个关键原则是使用“统一接口”。这意味着所有资源(URL)都可以通过使用相同的四个HTTP方法来操作:GET、PUT、POST和DELETE。对于每个方法,HTTP规范定义了确切的语义。例如,GET应该始终是安全的操作,意味着它没有副作用,而PUT或DELETE应该是幂等的,意味着你可以一次又一次地重复这些操作,但最终结果应该是一样的。虽然HTTP定义了这四种方法,但HTML只支持两种:GET和POST。幸运的是,有两种可能的解决方法:你可以使用JavaScript执行你的PUT或DELETE,或者你可以使用带有“真实”方法作为额外参数的POST(在HTML表单中模拟为隐藏的输入字段)。Spring的HiddenHttpMethodFilter使用了后一种技巧。这个过滤器是一个普通的Servlet过滤器,因此,它可以与任何Web框架(不仅仅是Spring MVC)结合使用。将此过滤器添加到你的web.xml中,带有隐藏方法参数的POST将被转换为相应的HTTP方法请求。

为了支持HTTP方法转换,Spring MVC的表单标签已经更新,以支持设置HTTP方法。例如,以下代码片段来自宠物诊所(Pet Clinic)示例:

<form:form method="delete">
	<p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>

前面的例子执行了一个HTTP POST,其中“真实”的DELETE方法隐藏在请求参数后面。它被web.xml中定义的HiddenHttpMethodFilter捕获,如下例所示:

<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>

以下示例显示了相应的@Controller方法:

@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
	this.clinic.deletePet(petId);
	return "redirect:/owners/" + ownerId;
}

HTML5 标签

Spring表单标签库允许输入动态属性,这意味着你可以输入任何HTML5特定的属性。

表单input 标签支持输入除text之外的type属性。这旨在允许渲染新的HTML5特定的输入类型,如email、date、range等。请注意,不必输入type=‘text’,因为text是默认类型。

RSS 和 Atom

AbstractAtomFeedView和AbstractRssFeedView都继承自AbstractFeedView基类,分别用于提供Atom和RSS Feed视图。它们基于ROME项目,位于包org.springframework.web.servlet.view.feed中。

AbstractAtomFeedView要求你实现buildFeedEntries()方法,并可以选择性地重写buildFeedMetadata()方法(默认实现为空)。以下示例显示了如何执行此操作:

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
	}
}

实现AbstractRssFeedView也有类似的要求,如下例所示:

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
	}
}

buildFeedItems()和buildFeedEntries()方法传递HTTP请求,以防需要访问Locale。HTTP响应仅用于设置cookie或其他HTTP头部。在方法返回后,feed会自动写入响应对象。

PDF 和 Excel

Spring提供了返回非HTML输出的方法,包括PDF和Excel电子表格。

文档视图简介

HTML页面并不总是用户查看模型输出的最佳方式,Spring使得从模型数据动态生成PDF文档或Excel电子表格变得简单。文档就是视图,并以正确的内容类型从服务器流式传输,以便使客户端PC能够运行他们的电子表格或PDF查看器应用程序。

为了使用Excel视图,需要将Apache POI库添加到类路径中。对于PDF生成,需要添加(最好是)OpenPDF库。

如果可能的话,应该使用最新版本的基础文档生成库。特别是,推荐OpenPDF(例如OpenPDF 1.2.12),而不是过时的原始iText 2.1.7,因为OpenPDF得到了积极的维护,并修复了针对不受信任的PDF内容的重要漏洞。

PDF 视图

单词列表的简单PDF视图可以扩展org.springframework.web.servlet.view.document.AbstractPdfView并实现buildPdfDocument()方法,如下例所示:

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));
		}
	}
}

控制器可以从外部视图定义(通过名称引用)返回这样的视图,或者作为处理程序方法的View实例返回。

Excel 视图

自Spring框架4.2起,org.springframework.web.servlet.view.document.AbstractXlsView作为Excel视图的基类提供。它基于Apache POI,有专门的子类(AbstractXlsxView和AbstractXlsxStreamingView)取代了过时的AbstractExcelView类。

编程模型与AbstractPdfView类似,以buildExcelDocument()作为中心模板方法,控制器可以从外部定义(通过名称)返回这样的视图,或者作为处理程序方法的View实例返回。

Jackson

Spring支持Jackson JSON库。

基于Jackson的JSON MVC视图

MappingJackson2JsonView使用Jackson库的ObjectMapper将响应内容呈现为JSON。默认情况下,模型映射的全部内容(框架特定类除外)都被编码为JSON。对于需要过滤映射内容的情况,可以通过使用modelKeys属性指定要编码的模型属性集。还可以使用extractValueFromSingleKeyModel属性,让单键模型中的值直接提取和序列化,而不是作为模型属性的映射。

可以根据需要使用Jackson提供的注解自定义JSON映射。当需要更多控制时,可以通过ObjectMapper属性注入自定义的ObjectMapper,以应对需要为特定类型提供自定义JSON序列化器和反序列化器的情况。

基于Jackson的XML视图

MappingJackson2XmlView使用Jackson XML扩展的XmlMapper将响应内容呈现为XML。如果模型包含多个条目,应该通过使用modelKey bean属性显式设置要序列化的对象。如果模型包含单个条目,它将自动序列化。

可以通过使用JAXB或Jackson提供的注解根据需要自定义XML映射。当需要更多控制时,可以通过ObjectMapper属性注入自定义的XmlMapper,以应对需要为特定类型提供自定义XML序列化器和反序列化器的情况。

XML序列化(XML Marshalling)

MarshallingView使用一个XML Marshaller(定义在org.springframework.oxm包中)将响应内容呈现为XML。可以通过使用MarshallingView实例的modelKey bean属性显式设置要序列化的对象。或者,视图可以遍历所有模型属性,并序列化Marshaller支持的第一种类型。

XSLT 视图

XSLT是一种用于XML的转换语言,在网络应用程序中作为视图技术很受欢迎。如果你的应用程序自然处理XML,或者你的模型可以轻松转换为XML,那么XSLT作为视图技术可能是一个很好的选择。下一节将展示如何在Spring Web MVC应用程序中生成一个XML文档作为模型数据,并使用XSLT进行转换。

这个例子是一个创建单词列表的简单Spring应用程序,它在控制器中将这些单词添加到模型映射中。该映射连同我们的XSLT视图的视图名一起返回。XSLT控制器将单词列表转换为一个简单的XML文档,准备进行转换。

Bean

配置对于简单的Spring Web应用程序来说是标准的:MVC配置必须定义一个XsltViewResolver bean和常规的MVC注解配置。以下示例展示了如何做到这一点:

@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;
	}
}

控制器

我们还需要有一个封装了我们的单词生成逻辑的控制器。

控制器逻辑被封装在一个@Controller类中,处理程序方法定义如下:

@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";
	}
}

到目前为止,我们只是创建了一个DOM文档并将其添加到了模型映射中。也可以加载一个XML文件作为资源并使用它,而不是自定义的DOM文档。

有软件包可以自动将对象图“转换为DOM”,但在Spring中,可以完全灵活地以你选择的任何方式从模型创建DOM。这防止了XML转换在模型数据结构中扮演过大的角色,这是在使用工具管理DOM化过程时的一个风险。

转换

最后,XsltViewResolver解析“home”XSLT模板文件并将DOM文档合并到其中以生成我们的视图。如XsltViewResolver配置所示,XSLT模板位于war文件的WEB-INF/xsl目录中,并以.xslt文件扩展名结尾。

下示例展示了一个XSLT转换:

<?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>

前面的转换渲染为以下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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值