23. View Technologies

23.1 Introduction

Spring 擅长的领域之一是将视图技术与 MVC 框架的其余部分分开。例如,决定使用 Groovy 标记模板或 Thymeleaf 代替现有的 JSP 主要是配置问题。本章介绍了与 Spring 一起使用的主要视图技术,并简要介绍了如何添加新技术。本章假定您已经熟悉第 22.5 节“解析视图”,它涵盖了一般如何将视图耦合到 MVC 框架的基础知识。

23.2 Thymeleaf

Thymeleaf是很好地适合 MVC 框架的视图技术的一个很好的例子。 Spring 团队不提供对此集成的支持,而是 Thymeleaf 团队本身提供支持。

为 Spring 配置 Thymeleaf 通常需要定义几个 Bean,例如ServletContextTemplateResolverSpringTemplateEngineThymeleafViewResolver。有关更多详细信息,请参阅Thymeleaf+Spring文档部分。

23.3 Groovy 标记

Groovy 标记模板引擎是 Spring 支持的另一种视图技术。此模板引擎是一个模板引擎,主要用于生成类似 XML 的标记(XML,XHTML,HTML5 等),但是可以用于生成任何基于文本的内容。

这需要在 Classpath 上使用 Groovy 2.3.1.

23.3.1 Configuration

配置 Groovy 标记模板引擎非常简单:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.groovy();
    }

    @Bean
    public GroovyMarkupConfigurer groovyMarkupConfigurer() {
        GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
        configurer.setResourceLoaderPath("/WEB-INF/");
        return configurer;
    }
}

使用 MVC 名称空间的 XML 对应项是:

<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:groovy/>
</mvc:view-resolvers>

<mvc:groovy-configurer resource-loader-path="/WEB-INF/"/>

23.3.2 Example

与传统的模板引擎不同,该引擎依赖于使用生成器语法的 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')
    }
}

23.4 速度和 FreeMarker

VelocityFreeMarker是可以在 Spring MVC 应用程序中用作视图技术的两种模板语言。这些语言非常相似并且满足相似的需求,因此在本节中将它们一起考虑。有关两种语言在语义和语法上的区别,请参见FreeMarker网站。

Note

从 Spring Framework 4.3 开始,由于六年来没有对 Apache Velocity 项目进行积极维护,因此不支持 Velocity。我们建议改用 Spring 的 FreeMarker 支持,或者 Spring 支持本身附带的 Thymeleaf。

23.4.1 Dependencies

您的 Web 应用程序将需要包含velocity-1.x.x.jarfreemarker-2.x.jar才能分别使用 Velocity 或 FreeMarker,而 Velocity 则需要commons-collections.jar。通常,它们包含在WEB-INF/lib文件夹中,可以确保 Java EE 服务器在其中找到它们并将它们添加到应用程序的 Classpath 中。当然,假设您的'WEB-INF/lib'目录中也已经有spring-webmvc.jar!如果在 Velocity 视图中使用 Spring 的“ dateToolAttribute”或“ numberToolAttribute”,则还需要包含velocity-tools-generic-1.x.jar

23.4.2 上下文配置

通过将相关的配置器 bean 定义添加到您的'*-servlet.xml'中来初始化合适的配置,如下所示:

<!--
This bean sets up the Velocity environment for us based on a root path for templates.
Optionally, a properties file can be specified for more control over the Velocity
environment, but the defaults are pretty sane for file based template loading.
-->
<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
    <property name="resourceLoaderPath" value="/WEB-INF/velocity/"/>
</bean>

<!--
View resolvers can also be configured with ResourceBundles or XML files. If you need
different view resolving based on Locale, you have to use the resource bundle resolver.
-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
    <property name="cache" value="true"/>
    <property name="prefix" value=""/>
    <property name="suffix" value=".vm"/>
</bean>
<!-- freemarker config -->
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
</bean>

<!--
View resolvers can also be configured with ResourceBundles or XML files. If you need
different view resolving based on Locale, you have to use the resource bundle resolver.
-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="cache" value="true"/>
    <property name="prefix" value=""/>
    <property name="suffix" value=".ftl"/>
</bean>

Note

对于非 Web 应用程序,请在应用程序上下文定义文件中添加VelocityConfigurationFactoryBeanFreeMarkerConfigurationFactoryBean

23.4.3 创建模板

您的模板需要存储在上面显示的*Configurer bean 指定的目录中。本文档未涵盖为这两种语言创建模板的详细信息-请参阅其相关网站以获取信息。如果您使用突出显示的视图解析器,则逻辑视图名称与模板文件名称相关,其方式与 JSP 的InternalResourceViewResolver相似。因此,如果您的控制器返回一个包含视图名称“ welcome”的 ModelAndView 对象,则解析器将根据需要查找/WEB-INF/freemarker/welcome.ftl/WEB-INF/velocity/welcome.vm模板。

23.4.4 高级配置

上面突出显示的基本配置将适合大多数应用程序要求,但是,在有特殊要求或高级要求时,可以使用其他配置选项。

velocity.properties

该文件是完全可选的,但如果指定,则包含传递给 Velocity 运行时以配置速度本身的值。仅高级配置才需要,如果需要此文件,请在上面的VelocityConfigurer bean 定义中指定其位置。

<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
    <property name="configLocation" value="/WEB-INF/velocity.properties"/>
</bean>

另外,您可以直接在 Velocity 配置 bean 的 bean 定义中指定速度属性,方法是将“ configLocation”属性替换为以下内联属性。

<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
    <property name="velocityProperties">
        <props>
            <prop key="resource.loader">file</prop>
            <prop key="file.resource.loader.class">
                org.apache.velocity.runtime.resource.loader.FileResourceLoader
            </prop>
            <prop key="file.resource.loader.path">${webapp.root}/WEB-INF/velocity</prop>
            <prop key="file.resource.loader.cache">false</prop>
        </props>
    </property>
</bean>

请参阅API documentation来进行 Velocity 的 Spring 配置,或参阅 Velocity 文档以获取'velocity.properties'文件本身的示例和定义。

FreeMarker

通过在FreeMarkerConfigurer bean 上设置适当的 bean 属性,可以将 FreeMarker 的“设置”和“ SharedVariables”直接传递给 SpringManagement 的 FreeMarker Configuration对象。 freemarkerSettings属性需要java.util.Properties对象,而freemarkerVariables属性需要java.util.Map

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

有关设置和变量应用于Configuration对象的详细信息,请参见 FreeMarker 文档。

23.4.5 绑定支持和表单处理

Spring 提供了一个供 JSP 使用的标签库,其中包含(除其他事项外)<spring:bind/>标签。该标签主要使表单能够显示来自表单支持对象的值,并显示来自 Web 或业务层中Validator的验证失败的结果。从 1.1 版开始,Spring 现在在 Velocity 和 FreeMarker 中都支持相同的功能,并带有用于生成表单 Importing 元素本身的附加便利宏。

绑定宏

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

Spring 库中定义的某些宏被视为内部(私有)宏,但是在宏定义中不存在这种范围,因此所有宏对于调用代码和用户模板都是可见的。以下各节仅关注需要从模板内部直接调用的宏。如果您希望直接查看宏代码,则这些文件称为 spring.vm/spring.ftl,分别位于软件包org.springframework.web.servlet.view.velocityorg.springframework.web.servlet.view.freemarker中。

Simple binding

在充当 Spring MVC 控制器的表单视图的 HTML 表单(vm/ftl 模板)中,您可以使用类似于以下代码的方式绑定到字段值,并以与 JSP 等效的方式显示每个 Importing 字段的错误消息。 。下面的代码示例显示了先前配置的personFormV/personFormF视图:

<!-- velocity macros are automatically available -->
<html>
    ...
    <form action="" method="POST">
        Name:
        #springBind("myModelObject.name")
        <input type="text"
            name="${status.expression}"
            value="$!status.value"/><br>
        #foreach($error in $status.errorMessages) <b>$error</b> <br> #end
        <br>
        ...
        <input type="submit" value="submit"/>
    </form>
    ...
</html>
<!-- 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 "myModelObject.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>

#springBind/<@spring.bind>需要一个'path'参数,该参数包含命令对象的名称(除非您在 FormController 属性中对其进行了更改,否则将为'command'),后跟一个句点和所需的命令对象上的字段名称绑定。嵌套字段也可以使用,例如“ command.address.street”。 bind宏假定 web.xml 中 ServletContext 参数defaultHtmlEscape指定的默认 HTML 转义行为

名为#springBindEscaped/<@spring.bindEscaped>的宏的可选形式带有第二个参数,并明确指定在状态错误消息或值中应使用 HTML 转义。根据需要设置为 true 或 false。附加的表单处理宏可简化 HTML 转义的使用,并且应尽可能使用这些宏。下一节将对它们进行说明。

表单 Importing 生成宏

两种语言的附加便利宏均简化了绑定和表单生成(包括验证错误显示)。从来没有必要使用这些宏来生成表单 Importing 字段,并且可以将它们与简单的 HTML 混合或匹配,或者直接调用先前突出显示的 spring 绑定宏。

下表列出了可用的宏,它们显示了 VTL 和 FTL 定义以及它们各自采用的参数列表。

表 23.1 宏定义表

macroVTL definitionFTL definition
message (根据 code 参数从资源包中输出一个字符串)#springMessage($code)<@spring.message code/>
messageText (根据 code 参数从资源包中输出一个字符串,回退到默认参数的值)#springMessageText($code $text)\ <@spring.messageText code, text/>
url (在相对 URL 前面加上应用程序的上下文根)#springUrl($relativeUrl)<@spring.url relativeUrl/>
formInput (用于收集用户 Importing 的标准 Importing 字段)#springFormInput($path $attributes)\ <@spring.formInput path, attributes, fieldType/>
formHiddenInput *(隐藏的 Importing 字段,用于提交非用户 Importing)#springFormHiddenInput($path $attributes)\ <@spring.formHiddenInput path, attributes/>
formPasswordInput *(用于收集密码的标准 Importing 字段.请注意,此类型的字段中不会填充任何值)#springFormPasswordInput($path $attributes)\ <@spring.formPasswordInput path, attributes/>
formTextarea (大文本字段,用于收集长而自由的文本 Importing)#springFormTextarea($path $attributes)\ <@spring.formTextarea path, attributes/>
formSingleSelect (选项的下拉框允许选择一个必需的值)#springFormSingleSelect($ path $ options $ attributes)\ <@spring.formSingleSelect path, options, attributes/>
formMultiSelect (选项列表框,允许用户选择 0 个或多个值)#springFormMultiSelect($ path $ options $ attributes)\ <@spring.formMultiSelect path, options, attributes/>
formRadioButtons (一组单选按钮,允许从可用选项中进行单个选择)#springFormRadioButtons($ path $ options $ separator $ attributes)\ <@spring.formRadioButtons path, options separator, attributes/>
formCheckboxes (一组允许选择 0 个或多个值的复选框)#springFormCheckboxes($ path $ options $ separator $ attributes)\ <@spring.formCheckboxes path, options, separator, attributes/>
formCheckbox (单个复选框)#springFormCheckbox($path $attributes)\ <@spring.formCheckbox path, attributes/>
showErrors (简化显示绑定字段的验证错误)#springShowErrors($separator $classOrStyle)\ <@spring.showErrors separator, classOrStyle/>
  • 在 FTL(FreeMarker)中,实际上不需要这两个宏,因为您可以使用普通的formInput宏,并指定'hidden' or ' `password' as the value for the `fieldType参数。

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

  • path:要绑定的字段的名称(即“ command.name”)

  • options:可以在 Importing 字段中选择的所有可用值的 Map。Map 的键表示将从表单中回发并绑定到命令对象的值。针对键存储的 Map 对象是在表单上显示给用户的标签,并且可能与表单回发的相应值不同。通常,这种 Map 由控制器作为参考数据提供。根据所需的行为,可以使用任何 Map 实现。对于严格排序的 Map,可以使用带有合适的 Comparator 的SortedMap(例如TreeMap);对于应按插入 Sequences 返回值的任意 Map,请使用 commons-collection 中的LinkedHashMapLinkedMap

  • 分隔符:多个选项可用作离散元素(单选按钮或复选框),是用于分隔列表中每个字符的字符序列(即“”)。

  • 属性:HTML 标记本身包含的任意标记或文本的附加字符串。该字符串实际上是由宏回显的。例如,在 textarea 字段中,您可以提供'rows =“ 5” cols =“ 60”'属性,也可以传递样式信息,例如'style =“ border:1px solid silver”'。

  • classOrStyle:对于 showErrors 宏,包装每个错误的 span 标签将使用的 CSS 类的名称。如果没有提供任何信息(或该值为空),那么错误将被包装在\ </b>标记中。

下面以 FTL 和 VTL 概述了宏的示例。如果两种语言之间存在用法差异,请在 Comments 中进行说明。

Input Fields
<!-- the Name field example from above using form macros in VTL -->
...
Name:
#springFormInput("command.name" "")<br>
#springShowErrors("<br>" "")<br>

formInput 宏带有 path 参数(command.name)和一个附加属性参数,在上面的示例中该参数为空。该宏与所有其他表单生成宏一起,对 path 参数执行隐式 spring 绑定。绑定一直保持有效,直到发生新的绑定,因此 showErrors 宏不需要再次传递 path 参数-它仅对最后创建绑定的字段进行操作。

showErrors 宏采用分隔符参数(将用于分隔给定字段上的多个错误的字符),并且还接受第二个参数,这是类名或样式属性。请注意,与 Velocity 不同,FreeMarker 能够为 attribute 参数指定默认值,并且以上两个宏调用可以在 FTL 中表示为:

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

下面显示了生成名称字段的表单片段的输出,并在提交表单后在该字段中没有值的情况下显示验证错误。验证通过 Spring 的 Validation 框架进行。

生成的 HTML 如下所示:

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

formTextarea 宏的工作方式与 formInput 宏相同,并且接受相同的参数列表。通常,第二个参数(属性)将用于传递样式信息或文本区域的行和列属性。

Selection Fields

四个选择字段宏可用于在 HTML 表单中生成常见的 UI 值选择 Importing。

  • formSingleSelect

  • formMultiSelect

  • formRadioButtons

  • formCheckboxes

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

下面是 FTL 中单选按钮的示例。表单支持对象为此字段指定默认值“伦敦”,因此无需验证。呈现表单时,将在模型中以“ cityMap”为名称提供可供选择的整个城市列表作为参考数据。

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

这将呈现一行单选按钮,使用分隔符“”代表cityMap中的每个值。没有提供其他属性(缺少该宏的最后一个参数)。 cityMap 对 Map 中的每个键值对使用相同的 String。Map 键是表单实际作为 POSTed 请求参数提交的,Map 值是用户看到的标签。在上面的示例中,给定三个知名城市的列表以及表单支持对象中的默认值,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, 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, String> model = new HashMap<>();
    model.put("cityMap", cityMap);
    return model;
}

现在,该代码将产生输出,其中无线电值是相关代码,但用户仍然可以看到更加用户友好的城市名称。

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>

HTML 转义和 XHTML 合规

上面的表单宏的默认用法将产生符合 HTML 4.01 的 HTML 标记,并且这些标记将使用 Spring 绑定支持所使用的 web.xml 中定义的 HTML 转义的默认值。为了使标签符合 XHTML 或覆盖默认的 HTML 转义值,您可以在模板(或模型中对模板可见的两个变量)中指定两个变量。在模板中指定它们的优点是,可以在稍后的模板处理中将它们更改为不同的值,以为表单中的不同字段提供不同的行为。

要为您的标记切换到 XHTML 兼容性,请为名为 xhtmlCompliant 的模型/上下文变量指定值“ true”:

# for Velocity..
#set($springXhtmlCompliant = true)

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

在处理此指令后,Spring 宏生成的所有标签现在都将符合 XHTML。

以类似的方式,可以为每个字段指定 HTML 转义:

<#-- until this point, default HTML escaping is used -->

<#assign htmlEscape = true in spring>
<#-- 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 -->

23.5 JSP 和 JSTL

Spring 为 JSP 和 JSTL 视图提供了一些现成的解决方案。使用WebApplicationContext中定义的普通视图解析器可以完成 JSP 或 JSTL 的使用。此外,当然,您需要编写一些将实际呈现视图的 JSP。

Note

设置应用程序以使用 JSTL 是常见的错误源,主要是由于对不同的 servlet 规范,JSP 和 JSTL 版本号,它们的含义以及如何正确声明 taglib 造成的困惑。文章如何在 Web 应用程序中引用和使用 JSTL提供了有关常见陷阱以及如何避免它们的有用指南。请注意,从 Spring 3.0 开始,支持的最低 Servlet 版本为 2.4(JSP 2.0 和 JSTL 1.1),这在某种程度上减少了混淆的范围。

23.5.1 查看解析器

就像您要与 Spring 集成的任何其他视图技术一样,对于 JSP,您将需要一个视图解析器来解析您的视图。使用 JSP 进行开发时,最常用的视图解析器是InternalResourceViewResolverResourceBundleViewResolver。两者都在WebApplicationContext中声明:

<!-- the ResourceBundleViewResolver -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
</bean>

# And a sample properties file is uses (views.properties in WEB-INF/classes):
welcome.(class)=org.springframework.web.servlet.view.JstlView
welcome.url=/WEB-INF/jsp/welcome.jsp

productList.(class)=org.springframework.web.servlet.view.JstlView
productList.url=/WEB-INF/jsp/productlist.jsp

如您所见,ResourceBundleViewResolver需要一个属性文件,该属性文件定义 Map 到 1)类和 2)URL 的视图名称。使用ResourceBundleViewResolver,您可以仅使用一个解析器来混合不同类型的视图。

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

可以如上所述配置InternalResourceBundleViewResolver以使用 JSP。作为最佳实践,我们强烈建议您将 JSP 文件放在'WEB-INF'目录下的目录中,以便 Client 端无法直接访问。

23.5.2'普通的'JSP 与 JSTL

使用 Java 标准标记库时,必须使用特殊的视图类JstlView,因为 JSTL 需要一些准备工作才能使用 I18N 功能。

23.5.3 Spring 的 JSP 标签库

如前几章所述,Spring 提供了将请求参数与命令对象的数据绑定。为了促进结合这些数据绑定功能的 JSP 页面的开发,Spring 提供了一些使事情变得更加容易的标记。所有的 Spring 标签都具有* HTML 转义*功能,以启用或禁用字符转义。

标签库 Descriptors(TLD)包含在spring-webmvc.jar中。有关各个标签的更多信息,请参见标题为???的附录。

23.5.4 Spring 的表单标签库

从 2.0 版开始,Spring 使用 JSP 和 Spring Web MVC 时,提供了一组全面的数据绑定感知标记,用于处理表单元素。每个标签都支持与其对应的 HTML 标签对等物的属性集,从而使标签熟悉且使用直观。标记生成的 HTML 符合 HTML 4.01/XHTML 1.0.

与其他表单/Importing 标签库不同,Spring 的表单标签库与 Spring Web MVC 集成在一起,使标签可以访问命令对象和控制器处理的参考数据。如您在以下示例中看到的那样,表单标记使 JSP 易于开发,读取和维护。

让我们浏览一下表单标签,并查看有关如何使用每个标签的示例。我们包含了生成的 HTML 代码段,其中某些标记需要进一步的 Comments。

Configuration

表单标签库 Binding 在spring-webmvc.jar中。库 Descriptors 称为spring-form.tld

要使用此库中的标记,请在 JSP 页面顶部添加以下指令:

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

其中form是要用于此库中标签的标签名称前缀。

表单标签

该标签呈现 HTML“ form”标签,并向内部标签公开绑定路径以进行绑定。它将命令对象放在PageContext中,以便内部标签可以访问该命令对象。 该库中的所有其他标签都是form标签的嵌套标签。

假设我们有一个名为User的域对象。它是具有firstNamelastName之类的属性的 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>

页面控制器从放置在PageContext中的命令对象中检索firstNamelastName值。continue 阅读以查看有关如何将内部标签与form标签一起使用的更复杂的示例。

生成的 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>

Importing 标签

默认情况下,此标记使用绑定值和 type ='text'呈现 HTML“ input”标记。有关此标记的示例,请参见名为“表单标签”的部分。从 Spring 3.1 开始,您可以使用其他类型,例如 HTML5 特定的类型,例如“电子邮件”,“电话”,“日期”等。

复选框标签

该标签呈现类型为“复选框”的 HTML“Importing”标签。

假设我们的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>

checkbox标记有 3 种方法可以满足您所有复选框的需求。

  • 方法一-当绑定值为java.lang.Boolean时,如果绑定值为true,则input(checkbox)被标记为“已选中”。 value属性对应于setValue(Object) value 属性的解析值。

  • 方法二-当边界值为arrayjava.util.Collection类型时,如果在边界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 表单数据绑定工作。 checkbox标签遵循现有的 Spring 约定,其中为每个复选框包括一个带有下划线(_)前缀的隐藏参数。通过这样做,您可以有效地告诉 Spring“ *复选框在表单中可见,并且我希望与表单数据绑定到的我的对象无论如何都可以反映复选框的状态”。

复选框标签

该标签呈现类型为“复选框”的多个 HTML“Importing”标签。

以上一个checkbox标签部分的示例为基础。有时,您宁愿不必在 JSP 页面中列出所有可能的爱好。您宁愿在运行时提供可用选项的列表,然后将其传递给标记。这就是checkboxes标记的目的。您传入ArrayListMap,其中包含“ items”属性中的可用选项。通常,bound 属性是一个集合,因此它可以容纳用户选择的多个值。以下是使用此标记的 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”是List,可作为包含要从中选择的值的字符串的模型属性使用。在使用 Map 的情况下,MapImporting 键将用作值,而 MapImporting 的值将用作要显示的标签。您还可以使用自定义对象,在其中可以使用“ itemValue”提供值的属性名称,并使用“ itemLabel”提供标签。

单选按钮标签

该标签呈现类型为“ radio”的 HTML“ input”标签。

典型的使用模式将涉及绑定到相同属性但值不同的多个标签实例。

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

单选按钮标签

该标签呈现类型为“ radio”的多个 HTML“ input”标签。

就像上面的checkboxes标记一样,您可能希望将可用选项作为运行时变量传递。对于这种用法,您将使用radiobuttons标签。您传入ArrayListMap,其中包含“ items”属性中的可用选项。在使用 Map 的情况下,MapImporting 键将用作值,而 MapImporting 的值将用作要显示的标签。您还可以使用自定义对象,在其中可以使用“ itemValue”提供值的属性名称,并使用“ itemLabel”提供标签。

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

密码标签

此标记使用绑定值呈现类型为“ 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>

选择标签

此标记呈现 HTML“ select”元素。它支持将数据绑定到所选选项以及使用嵌套的optionoptions标签。

假设User具有一系列技能。

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

如果User's技能是草药学,则“技能”行的 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>

选项标签

此标记呈现 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's房子位于格兰芬多,则“房子”行的 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>

选项标签

该标签呈现 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居住在英国,则“国家/locale”行的 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属性通常由项对象的集合或数组填充。 itemValueitemLabel只是引用那些项目对象的 bean 属性(如果已指定);否则,item 对象本身将被字符串化。或者,您可以指定Map的项目,在这种情况下,Map 键将解释为选项值,并且 Map 值对应于选项标签。如果也恰好指定了itemValue和/或itemLabel,则 item value 属性将应用于 Map 键,item label 属性将应用于 Map 值。

textarea 标签

此标记呈现 HTML'textarea'。

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

隐藏的标签

此标记使用绑定值呈现类型为“隐藏”的 HTML“Importing”标记。要提交未绑定的隐藏值,请使用类型为“ hidden”的 HTML input标签。

<form:hidden path="house"/>

如果我们选择将“ house”值作为隐藏值提交,则 HTML 如下所示:

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

错误标记

此标记在 HTML'span'标记中呈现字段错误。它提供对在控制器中创建的错误或由与控制器关联的任何验证程序创建的错误的访问。

假设提交表单后,我们想显示firstNamelastName字段的所有错误消息。我们有一个名为UserValidatorUser类实例的验证器。

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>

如果我们在firstNamelastName字段中提交具有空值的表单,则 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.
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>

HTTP 方法转换

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

为了支持 HTTP 方法转换,Spring MVC 表单标签已更新为支持设置 HTTP 方法。例如,以下片段摘自更新的 Petclinic 示例

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

这实际上将执行 HTTP POST,并将“真实” DELETE 方法隐藏在请求参数后面,并由HiddenHttpMethodFilter拾取,如 web.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>

相应的@Controller方法如下所示:

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

HTML5 tags

从 Spring 3 开始,Spring 表单标签库允许 Importing 动态属性,这意味着您可以 Importing 任何 HTML5 特定的属性。

在 Spring 3.1 中,表单 Importing 标签支持 Importing'text'以外的 type 属性。这旨在允许呈现新的 HTML5 特定 Importing 类型,例如“电子邮件”,“日期”,“范围”等。请注意,由于“文本”是默认类型,因此不需要 Importingtype ='text'。

23.6 脚本视图

使用 Spring 可以将运行在 JSR-223 脚本引擎之上的任何模板库集成到 Web 应用程序中。下面以广泛的方式描述如何执行此操作。脚本引擎必须同时实现ScriptEngineInvocable接口。

已通过以下测试:

23.6.1 Requirements

为了能够使用脚本模板集成,您需要在 Classpath 中提供脚本引擎:

  • Nashorn Java 8 内置 Javascript 引擎。强烈建议使用可用的最新更新版本。

  • Rhino Java 6 和 Java 7 内置 Javascript 引擎。请注意,不建议使用 Rhino,因为它不支持运行大多数模板引擎。

  • 应该添加JRuby依赖关系以获得 Ruby 支持。

  • 应该添加Jython依赖关系以获得 Python 支持。

您还需要为基于脚本的模板引擎添加依赖项。例如,对于 Javascript,您可以使用WebJars添加 Maven/Gradle 依赖项,以使您的 JavaScript 库在 Classpath 中可用。

23.6.2 脚本模板

为了能够使用脚本模板,必须对其进行配置以指定各种参数,例如要使用的脚本引擎,要加载的脚本文件以及应调用什么函数来呈现模板。这要归功于ScriptTemplateConfigurer bean 和可选的脚本文件。

例如,为了使用 Java 8 随附的 Nashorn Javascript 引擎来渲染 Mustache 模板,您应该声明以下配置:

@Configuration
@EnableWebMvc
public class MustacheConfig extends WebMvcConfigurerAdapter {

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

使用 MVC 名称空间的 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>

该控制器正是您所期望的:

@Controller
public class SampleController {

    @RequestMapping
    public ModelAndView test() {
        ModelAndView mav  = new ModelAndView();
        mav.addObject("title", "Sample title").addObject("body", "Sample body");
        mav.setViewName("template.html");
        return mav;
    }
}

胡子模板是:

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

使用以下参数调用 render 函数:

  • String template:模板内容

  • Map model:视图模型

  • String url:模板网址(自 4.2.2 开始)

Mustache.render()与该签名本地兼容,因此您可以直接调用它。

如果您的模板技术需要一些自定义,则可以提供一个实现自定义渲染功能的脚本。例如,Handlerbars需要在使用模板之前先对其进行编译,并且需要polyfill才能模拟某些服务器端脚本引擎中不可用的浏览器功能。

@Configuration
@EnableWebMvc
public class MustacheConfig extends WebMvcConfigurerAdapter {

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

Note

当使用非线程安全脚本引擎且模板库不是为并发设计的时(例如在 Nashorn 上运行的 Handlebars 或 React),需要将sharedEngine属性设置为false。在这种情况下,由于this bug,需要 Java 8u60 或更高版本。

polyfill.js仅定义车把正常运行所需的window对象:

var window = {};

这个基本的render.js实现在使用模板之前先对其进行编译。生产就绪的实现还应该存储和重用缓存的模板/预编译的模板。这可以在脚本端以及您需要的任何自定义(例如,Management 模板引擎配置)上完成。

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

查看 Spring 脚本模板单元测试(javaresources)以获取更多配置示例。

23.7 XML 封送处理

MarshallingView使用org.springframework.oxm包中定义的 XML Marshaller将响应内容呈现为 XML。可以使用MarshallingViewmodelKey bean 属性来显式设置要编组的对象。另外,视图将遍历所有模型属性,并封送Marshaller支持的第一个类型。有关org.springframework.oxm软件包中功能的更多信息,请参见使用 O/XMap 器编组 XML章。

23.8 Tiles

与使用其他视图技术一样,可以使用 Spring 将 Tiles 集成到 Web 应用程序中。下面以广泛的方式描述如何执行此操作。

Note

本节重点介绍org.springframework.web.servlet.view.tiles3软件包中 Spring 对 Tiles v3 的支持。

23.8.1 Dependencies

为了能够使用 Tiles,您必须在项目中添加对 Tiles 3.0.1 或更高版本以及它的传递依赖的依赖。

23.8.2 Configuration

为了能够使用 Tiles,您必须使用包含定义的文件对其进行配置(有关定义和其他 Tiles 概念的基本信息,请查看http://tiles.apache.org)。在 Spring,使用TilesConfigurer完成此操作。看一下以下示例 ApplicationContext 配置:

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

如您所见,有五个包含定义的文件,它们都位于'WEB-INF/defs'目录中。 WebApplicationContext初始化时,将加载文件并初始化定义工厂。完成之后,定义文件中包含的 Tiles 可以用作 Spring Web 应用程序中的视图。为了能够使用视图,您必须拥有ViewResolver,就像 Spring 使用的任何其他视图技术一样。在下面,您可以找到UrlBasedViewResolverResourceBundleViewResolver两种可能性。

您可以通过添加下划线,然后添加语言环境来指定特定于语言环境的 Tiles 定义。例如:

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

使用此配置,tiles_fr_FR.xml将用于具有fr_FR语言环境的请求,默认情况下将使用tiles.xml

Note

由于下划线用于指示语言环境,因此建议避免在 Tiles 定义的文件名中另外使用下划线。

UrlBasedViewResolver

UrlBasedViewResolver为要解析的每个视图实例化给定的viewClass

<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
</bean>

ResourceBundleViewResolver

ResourceBundleViewResolver必须提供一个属性文件,其中包含解析程序可以使用的视图名称和视图类:

<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
</bean>
...
welcomeView.(class)=org.springframework.web.servlet.view.tiles3.TilesView
welcomeView.url=welcome (this is the name of a Tiles definition)

vetsView.(class)=org.springframework.web.servlet.view.tiles3.TilesView
vetsView.url=vetsView (again, this is the name of a Tiles definition)

findOwnersForm.(class)=org.springframework.web.servlet.view.JstlView
findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp
...

如您所见,使用ResourceBundleViewResolver时,您可以轻松地混合使用不同的视图技术。

请注意,TilesView类开箱即用地支持 JSTL(JSP 标准标记库)。

SimpleSpringPreparerFactory 和 SpringBeanPreparerFactory

作为一项高级功能,Spring 还支持两个特殊的 Tiles PreparerFactory实现。请查看 Tiles 文档,以获取有关如何在 Tiles 定义文件中使用ViewPreparer引用的详细信息。

指定SimpleSpringPreparerFactory以基于指定的准备器类自动连接 ViewPreparer 实例,应用 Spring 的容器回调以及应用配置的 Spring BeanPostProcessor。如果已激活 Spring 的上下文范围内的注解配置,则将自动检测和应用 ViewPreparer 类中的注解。请注意,就像默认的PreparerFactory一样,这需要 Tiles 定义文件中的准备程序* classes *。

指定SpringBeanPreparerFactory以对指定的准备程序* names *而不是类进行操作,从而从 DispatcherServlet 的应用程序上下文中获取相应的 Spring bean。在这种情况下,完整的 bean 创建过程将由 Spring 应用程序上下文控制,从而允许使用显式依赖项注入配置,作用域 bean 等。请注意,您需要为每个准备者名称定义一个 Spring bean 定义(如您的图块定义)。

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

23.9 XSLT

XSLT 是 XML 的一种转换语言,在 Web 应用程序中作为一种视图技术而流行。如果您的应用程序自然处理 XML,或者您的模型可以轻松转换为 XML,那么 XSLT 可以作为视图技术的不错选择。下一节说明如何将 XML 文档生成为模型数据,以及如何在 Spring Web MVC 应用程序中使用 XSLT 对其进行转换。

这个例子是一个普通的 Spring 应用程序,它在Controller中创建一个单词列表,并将它们添加到模型图中。该 Map 连同我们的 XSLT 视图的视图名称一起返回。有关 Spring Web MVC 的Controller界面的详细信息,请参见第 22.3 节“实现控制器”。 XSLT 控制器会将单词列表转换为准备转换的简单 XML 文档。

23.9.1 Beans

配置是简单 Spring 应用程序的标准配置。 MVC 配置必须定义XsltViewResolver bean 和常规 MVCComments 配置。

@EnableWebMvc
@ComponentScan
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

	@Bean
	public XsltViewResolver xsltViewResolver() {
		XsltViewResolver viewResolver = new XsltViewResolver();
		viewResolver.setPrefix("/WEB-INF/xsl/");
		viewResolver.setSuffix(".xslt");
		return viewResolver;
	}

}

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

23.9.2 Controller

控制器逻辑封装在@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 文档并将其添加到 ModelMap 中。请注意,您还可以将 XML 文件作为Resource加载并使用它代替自定义 DOM 文档。

当然,有可用的软件包可以自动“ doming”对象图,但是在 Spring 中,您可以完全灵活地以任何选择的方式从模型中创建 DOM。这样可以防止 XML 转换在模型数据的结构中扮演过多的角色,而这在使用工具 Management 归类过程时是危险的。

接下来,XsltViewResolver将解析“主” XSLT 模板文件,并将 DOM 文档合并到其中以生成我们的视图。

23.9.3 Transformation

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

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

23.10 文档视图:PDF,Excel

23.10.1 Introduction

返回 HTML 页面并非始终是用户查看模型输出的最佳方法,而 Spring 使从模型数据动态生成 PDF 文档或 Excel 电子表格变得简单。该文档是视图,将以正确的 Content Type 从服务器流式传输,以(有希望)使 Client 端 PC 能够运行其电子表格或 PDF 查看器应用程序作为响应。

为了使用 Excel 视图,您需要将 Apache POI 库添加到您的 Classpath 中,并且为了生成 PDF,最好添加 OpenPDF 库。

Note

如果可能,请使用最新版本的基础文档生成库。特别是,我们强烈建议您使用 OpenPDF(例如 OpenPDF 1.0.5),而不要使用过时的原始 iText 2.1.7,因为它会得到积极维护并修复了不可信任 PDF 内容的一个重要漏洞。

23.10.2 Configuration

基于文档的视图的处理方式几乎与 XSLT 视图的处理方式相同,以下各节通过演示如何调用 XSLT 示例中使用的相同控制器以呈现与 PDF 文档和 Excel 电子表格相同的模型的方式,在前一部分的基础上进行构建。 (也可以在 Open Office 中查看或操作)。

23.10.3 视图定义

首先,让我们修改 views.properties 文件(或 xml 等效文件),并为两种文档类型添加一个简单的视图定义。现在,整个文件看起来就像上面显示的 XSLT 视图一样:

home.(class)=xslt.HomePage
home.stylesheetLocation=/WEB-INF/xsl/home.xslt
home.root=words

xl.(class)=excel.HomePage

pdf.(class)=pdf.HomePage

如果您想从模板电子表格或可填写的 PDF 表单开始添加模型数据,请在视图定义中将该位置指定为'url'属性

23.10.4 Controller

除了更改要使用的视图的名称外,我们将使用的控制器代码与 XSLT 示例中的代码完全相同。当然,您可能很聪明,可以根据 URL 参数或其他逻辑进行选择-证明 Spring 确实非常擅长将视图与控制器解耦!

23.10.5 Excel 视图

就像我们在 XSLT 示例中所做的一样,我们将适当的抽象类作为子类,以便在生成输出文档时实现自定义行为。对于 Excel,这涉及编写org.springframework.web.servlet.view.document.AbstractExcelView(对于 POI 生成的 Excel 文件)或org.springframework.web.servlet.view.document.AbstractJExcelView(对于 JExcelApi 生成的 Excel 文件)的子类,并实现buildExcelDocument()方法。

这是我们的 POI Excel 视图的完整列表,该视图在新电子表格的第一列的连续行中显示模型图中的单词列表:

package excel;

// imports omitted for brevity

public class HomePage extends AbstractExcelView {

    protected void buildExcelDocument(Map model, HSSFWorkbook wb, HttpServletRequest req,
            HttpServletResponse resp) throws Exception {

        HSSFSheet sheet;
        HSSFRow sheetRow;
        HSSFCell cell;

        // Go to the first sheet
        // getSheetAt: only if wb is created from an existing document
        // sheet = wb.getSheetAt(0);
        sheet = wb.createSheet("Spring");
        sheet.setDefaultColumnWidth((short) 12);

        // write a text at A1
        cell = getCell(sheet, 0, 0);
        setText(cell, "Spring-Excel test");

        List words = (List) model.get("wordList");
        for (int i=0; i < words.size(); i++) {
            cell = getCell(sheet, 2+i, 0);
            setText(cell, (String) words.get(i));
        }
    }

}

以下是现在使用 JExcelApi 生成相同 Excel 文件的视图:

package excel;

// imports omitted for brevity

public class HomePage extends AbstractJExcelView {

    protected void buildExcelDocument(Map model, WritableWorkbook wb,
            HttpServletRequest request, HttpServletResponse response) throws Exception {

        WritableSheet sheet = wb.createSheet("Spring", 0);

        sheet.addCell(new Label(0, 0, "Spring-Excel test"));

        List words = (List) model.get("wordList");
        for (int i = 0; i < words.size(); i++) {
            sheet.addCell(new Label(2+i, 0, (String) words.get(i)));
        }
    }
}

请注意 API 之间的差异。我们发现 JExcelApi 更加直观,而且 JExcelApi 具有更好的图像处理功能。但是,使用 JExcelApi 时,大型 Excel 文件存在内存问题。

如果现在修改控制器,使其返回xl作为视图的名称(return new ModelAndView("xl", map);)并再次运行您的应用程序,则您将发现当您请求与以前相同的页面时,会自动创建并下载 Excel 电子表格。

23.10.6 PDF 视图

单词列表的 PDF 版本更加简单。这次,该类扩展了org.springframework.web.servlet.view.document.AbstractPdfView并实现了buildPdfDocument()方法,如下所示:

package pdf;

// imports omitted for brevity

public class PDFPage extends AbstractPdfView {

    protected void buildPdfDocument(Map model, Document doc, PdfWriter writer,
        HttpServletRequest req, HttpServletResponse resp) throws Exception {
        List words = (List) model.get("wordList");
        for (int i=0; i<words.size(); i++) {
            doc.add( new Paragraph((String) words.get(i)));
        }
    }

}

再次修改控制器,以return new ModelAndView("pdf", map);返回pdf视图,然后在应用程序中重新加载 URL。这次将出现一个 PDF 文档,其中列出了模型图中的每个单词。

23.11 JasperReports

JasperReports(http://jasperreports.sourceforge.net)是一个功能强大的开源报告引擎,它支持使用易于理解的 XML 文件格式创建报告设计。 JasperReports 能够以四种不同的格式呈现报告:CSV,Excel,HTML 和 PDF。

23.11.1 Dependencies

您的应用程序将需要包含 JasperReports 的最新版本,例如 6.2. JasperReports 本身取决于以下项目:

  • BeanShell

  • Commons BeanUtils

  • Commons Collections

  • Commons Digester

  • Commons Logging

  • iText

  • POI

JasperReports 还需要兼容 JAXP 的 XML 解析器。

23.11.2 Configuration

要在 Spring 容器配置中配置 JasperReports 视图,您需要定义一个ViewResolver,以根据您要呈现报告的格式将视图名称 Map 到适当的视图类。

配置 ViewResolver

通常,您将使用ResourceBundleViewResolverMap 视图名称以查看属性文件中的类和文件。

<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
</bean>

在这里,我们配置了ResourceBundleViewResolver类的实例,该实例将在基本名称为views的资源包中查找视图 Map。 (此文件的内容在下一节中描述.)

配置视图

Spring 框架包含 JasperReports 的五种不同的View实现,其中四种与 JasperReports 支持的四种输出格式之一相对应,而另一种则允许在运行时确定格式:

表 23.2 JasperReports 查看类别

Class NameRender Format
JasperReportsCsvViewCSV
JasperReportsHtmlViewHTML
JasperReportsPdfViewPDF
JasperReportsXlsViewMicrosoft Excel
JasperReportsMultiFormatView视图是在运行时决定

将这些类之一 Map 到视图名称和报告文件,只需在上一节中配置的资源包中添加适当的条目即可,如下所示:

simpleReport.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView
simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper

在这里,您可以看到名称为simpleReport的视图已 Map 到JasperReportsPdfView类,从而使该报告的输出以 PDF 格式呈现。视图的url属性设置为基础报表文件的位置。

关于报告文件

JasperReports 具有两种不同类型的报告文件:设计文件(具有.jrxmlextensions)和编译的报告文件(具有.jasperextensions)。通常,您可以使用 JasperReports Ant 任务将.jrxml设计文件编译为.jasper文件,然后再将其部署到应用程序中。使用 Spring Framework,您可以将这些文件中的任何一个 Map 到您的报告文件,并且该框架将为您动态编译.jrxml文件。您应该注意,在 Spring 框架编译.jrxml文件之后,将在应用程序的生存期内缓存已编译的报告。因此,要更改文件,您将需要重新启动应用程序。

Using JasperReportsMultiFormatView

JasperReportsMultiFormatView允许在运行时指定报告格式。报表的实际呈现被委托给其他 JasperReports 视图类之一-JasperReportsMultiFormatView类仅添加了一个包装层,该层允许在运行时指定确切的实现。

JasperReportsMultiFormatView类引入了两个概念:格式键和鉴别符。 JasperReportsMultiFormatView类使用 Map 键查找实际的视图实现类,并使用格式键查找 Map 键。从编码角度来看,您可以使用格式键作为键和 Map 键作为值向模型中添加一个条目,例如:

public ModelAndView handleSimpleReportMulti(HttpServletRequest request,
HttpServletResponse response) throws Exception {

    String uri = request.getRequestURI();
    String format = uri.substring(uri.lastIndexOf(".") + 1);

    Map model = getModel();
    model.put("format", format);

    return new ModelAndView("simpleReportMulti", model);

}

在此示例中,Map 密钥是根据请求 URI 的 extensions 确定的,并在默认格式密钥format下添加到模型中。如果希望使用其他格式的密钥,则可以使用JasperReportsMultiFormatView类的formatKey属性进行配置。

默认情况下,以下 Map 键 Map 是在JasperReportsMultiFormatView中配置的:

表 23.3 JasperReportsMultiFormatView 默认 Map 键 Map

Mapping KeyView Class
csvJasperReportsCsvView
htmlJasperReportsHtmlView
pdfJasperReportsPdfView
xlsJasperReportsXlsView

因此,在上面的示例中,对 URI /foo/myReport.pdf 的请求将 Map 到JasperReportsPdfView类。您可以使用JasperReportsMultiFormatViewformatMappings属性来覆盖 Map 键以查看类 Map。

23.11.3 填充 ModelAndView

为了以您选择的格式正确呈现报告,您必须向 Spring 提供填充报告所需的所有数据。对于 JasperReports,这意味着您必须传递所有报告参数以及报告数据源。报表参数是简单的名称/值对,可以像添加任何名称/值对一样添加到模型的Map中。

将数据源添加到模型时,有两种方法可供选择。第一种方法是在任意键下向模型Map添加JRDataSourceCollection类型的实例。然后,Spring 将在模型中定位该对象,并将其视为报表数据源。例如,您可以像这样填充模型:

private Map getModel() {
    Map model = new HashMap();
    Collection beanData = getBeanData();
    model.put("myBeanData", beanData);
    return model;
}

第二种方法是在特定键下添加JRDataSourceCollection的实例,然后使用视图类的reportDataKey属性配置此键。在这两种情况下,Spring 都会将Collection的实例包装在JRBeanCollectionDataSource的实例中。例如:

private Map getModel() {
    Map model = new HashMap();
    Collection beanData = getBeanData();
    Collection someData = getSomeData();
    model.put("myBeanData", beanData);
    model.put("someData", someData);
    return model;
}

在这里您可以看到两个Collection实例正在添加到模型中。为了确保使用正确的视图,我们只需适当地修改视图配置即可:

simpleReport.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView
simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper
simpleReport.reportDataKey=myBeanData

请注意,在使用第一种方法时,Spring 将使用遇到的JRDataSourceCollection的第一个实例。如果您需要将JRDataSourceCollection的多个实例放入模型中,则需要使用第二种方法。

23.11.4 处理子报表

JasperReports 为您的主报告文件中的嵌入式子报告提供支持。有多种机制可将子报告包括在报告文件中。最简单的方法是将报表路径和子报表的 SQL 查询硬编码到您的设计文件中。这种方法的缺点显而易见:将值硬编码到报表文件中,从而降低了可重用性,并使修改和更新报表设计变得更加困难。为了克服这个问题,您可以声明性地配置子报告,并且可以直接从控制器中为这些子报告包括其他数据。

配置子报告文件

要使用 Spring 控制主报表中包含哪些子报表文件,必须将您的报表文件配置为接受来自外部源的子报表。为此,您可以在报告文件中声明一个参数,如下所示:

<parameter name="ProductsSubReport" class="net.sf.jasperreports.engine.JasperReport"/>

然后,您定义子报告以使用此子报告参数:

<subreport>
    <reportElement isPrintRepeatedValues="false" x="5" y="25" width="325"
        height="20" isRemoveLineWhenBlank="true" backcolor="#ffcc99"/>
    <subreportParameter name="City">
        <subreportParameterExpression><![CDATA[$F{city}]]></subreportParameterExpression>
    </subreportParameter>
    <dataSourceExpression><![CDATA[$P{SubReportData}]]></dataSourceExpression>
    <subreportExpression class="net.sf.jasperreports.engine.JasperReport">
        <![CDATA[$P{ProductsSubReport}]]></subreportExpression>
</subreport>

这定义了一个主报告文件,该文件期望子报告作为参数ProductsSubReport下的net.sf.jasperreports.engine.JasperReports实例传递。在配置 Jasper 视图类时,您可以使用subReportUrls属性指示 Spring 加载报告文件并将其作为子报告传递到 JasperReports 引擎中:

<property name="subReportUrls">
    <map>
        <entry key="ProductsSubReport" value="/WEB-INF/reports/subReportChild.jrxml"/>
    </map>
</property>

在此,Map的键对应于报告设计文件中子报告参数的名称,并且条目是报告文件的 URL。 Spring 将加载此报告文件,并在必要时进行编译,然后在给定的键下将其传递到 JasperReports 引擎中。

配置子报表数据源

使用 Spring 配置子报表时,此步骤是完全可选的。如果愿意,您仍然可以使用静态查询为子报表配置数据源。但是,如果您希望 Spring 将ModelAndView中返回的数据转换为JRDataSource的实例,则需要指定ModelAndView中的哪些参数应由 Spring 转换。为此,请使用所选视图类的subReportDataKeys属性配置参数名称列表:

<property name="subReportDataKeys" value="SubReportData"/>

在此,您提供的密钥必须ModelAndView中使用的密钥和报表设计文件中使用的密钥相对应。

23.11.5 配置导出器参数

如果您对导出器配置有特殊要求,例如您可能需要 PDF 报告的特定页面大小,则可以使用视图类的exporterParameters属性在 Spring 配置文件中声明性地配置这些导出器参数。 exporterParameters属性键入为Map。在您的配置中,条目的键应为包含导出程序参数定义的静态字段的全限定名称,条目的值应为您想要分配给参数的值。下面是一个示例:

<bean id="htmlReport" class="org.springframework.web.servlet.view.jasperreports.JasperReportsHtmlView">
    <property name="url" value="/WEB-INF/reports/simpleReport.jrxml"/>
    <property name="exporterParameters">
        <map>
            <entry key="net.sf.jasperreports.engine.export.JRHtmlExporterParameter.HTML_FOOTER">
                <value>Footer by Spring!
                    &lt;/td&gt;&lt;td width="50%"&gt;&amp;nbsp; &lt;/td&gt;&lt;/tr&gt;
                    &lt;/table&gt;&lt;/body&gt;&lt;/html&gt;
                </value>
            </entry>
        </map>
    </property>
</bean>

在这里,您可以看到JasperReportsHtmlView配置了net.sf.jasperreports.engine.export.JRHtmlExporterParameter.HTML_FOOTER的导出程序参数,该参数将在结果 HTML 中输出页脚。

23.12 供稿视图:RSS,Atom

AbstractAtomFeedViewAbstractRssFeedView都从 Base ClassAbstractFeedView继承,并且分别用于提供 Atom 和 RSS Feed 视图。它们基于 java.net 的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 SampleContentAtomView 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()buildFeedEntires()方法会传递 HTTP 请求。仅针对 Cookie 或其他 HTTPHeaders 的设置传入 HTTP 响应。方法返回后,提要将自动写入响应对象。

有关创建 Atom 视图的示例,请参阅 Alef Arendsen 的 Spring Team Blog entry

23.13 JSONMap 视图

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

可以根据需要使用 Jackson 提供的 Comments 自定义 JSONMap。当需要进一步控制时,对于需要为特定类型提供自定义 JSON 序列化器/反序列化器的情况,可以通过ObjectMapper属性注入自定义ObjectMapper

从 Spring Framework 4.3.18 开始,不支持JSONP支持,并且需要通过jsonpParameterNames属性自定义 JSONP 查询参数名称。从 Spring Framework 5.1 开始,将删除此支持,而应改用CORS

23.14 XMLMap 视图

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

可以根据需要通过使用 JAXB 或 Jackson 提供的 Comments 来定制 XMLMap。当需要进一步控制时,对于需要为特定类型提供自定义 XML 序列化器/反序列化器的情况,可以通过ObjectMapper属性注入自定义XmlMapper