Spring Framework 中文文档

4.3.21.RELEASE

23. View Technologies

23.1 简介

Spring 擅长的领域之一是将视图技术与 MVC framework 的 rest 分离。例如,决定使用 Groovy 标记模板或 Thymeleaf 代替现有 JSP 主要是 configuration。本章介绍了与 Spring 一起使用的主要视图技术,并简要介绍了如何添加新的技术。本章假设您已经熟悉第 22.5 节,“解析观点”,其中涵盖了一般视图如何与 MVC framework 相结合的基础知识。

23.2 Thymeleaf

Thymeleaf是视觉技术的一个很好的例子,非常适合 MVC framework。 Spring 团队不提供对此 integration 的支持,而是由 Thymeleaf 团队本身提供。

为 Spring 配置 Thymeleaf 通常需要定义一些 beans,如ServletContextTemplateResolverSpringTemplateEngineThymeleafViewResolver。有关更多详细信息,请参阅Thymeleaf Spring文档部分。

23.3 Groovy Markup

Groovy 标记模板引擎是另一种视图技术,由 Spring 支持。此模板引擎是一个模板引擎,主要用于生成 XML-like 标记(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 示例

与传统的模板引擎不同,这个引擎依赖于使用构建器语法的 DSL。这是 HTML 页面的 sample 模板:

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 Velocity&FreeMarker

速度FreeMarker是两种模板语言,可以在 Spring MVC applications 中用作视图技术。这些语言非常相似并且提供类似的需求,因此在本节中将一起考虑。有关两种语言之间的语义和语法差异,请参阅FreeMarker web 站点。

截至 Spring Framework 4.3,由于没有 Apache Velocity 项目的 active 维护六年,Velocity 支持已被弃用。我们建议使用 Spring 的 FreeMarker 支持,或者使用 Spring 支持本身的 Thymeleaf。

23.4.1 依赖关系

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

23.4.2 Context configuration

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

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

对于非 web-apps,在 application context 定义文件中添加VelocityConfigurationFactoryBeanFreeMarkerConfigurationFactoryBean

23.4.3 创建模板

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

23.4.4 高级配置

上面突出显示的基本配置适用于大多数 application 要求,但是当异常或高级要求指示时,可以使用其他 configuration 选项。

velocity.properties

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

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

或者,您可以通过将“configLocation”property 替换为以下内联 properties,直接在 Velocity config bean 的 bean 定义中指定 velocity properties。

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

有关'velocity.properties'文件的示例和定义,请参阅API 文档 for Spring configuration of Velocity,或 Velocity 文档。

FreeMarker

FreeMarker'Settings'和'SharedVariables'可以通过在FreeMarkerConfigurer bean 上设置适当的 bean properties 直接传递给 Spring 管理的 FreeMarker Configuration object。 freemarkerSettings property 需要java.util.Properties object,freemarkerVariables property 需要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 object 的设置和变量的详细信息,请参阅 FreeMarker 文档。

23.4.5 绑定支持和表单处理

Spring 提供了一个标记 library,用于 JSP 中包含(除其他外)<spring:bind/>标记。此标记主要使表单能够从表单支持 objects 显示值,并显示 web 或业务层中Validator的验证失败结果。从 version 1.1 开始,Spring 现在支持 Velocity 和 FreeMarker 中的相同功能,还有用于生成表单输入元素的额外便利宏。

绑定宏

在两个语言的spring-webmvc.jar文件中维护一组标准宏,因此它们始终可用于适当配置的 application。

Spring libraries 中定义的某些宏被视为内部(私有),但宏定义中不存在这样的范围,使得所有宏对调用 code 和用户模板都可见。以下部分仅关注您需要在模板中直接调用的宏。如果您希望直接查看宏 code,files 称为 spring.vm/spring.ftl,分别位于包org.springframework.web.servlet.view.velocityorg.springframework.web.servlet.view.freemarker中。

简单的 binding

在作为 Spring MVC 控制器的表单视图的 HTML 表单(vm/ftl 模板)中,您可以使用类似于以下内容的 code 绑定到字段值,并以与 JSP 等效方式类似的方式显示每个输入字段的错误消息。 Example code 如下所示,为前面配置的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'参数,它包含命令 object 的 name(它将是'command',除非你在 FormController properties 中更改它)后跟一个句点和你想要的命令 object 上的字段的 name 绑定到。也可以使用嵌套字段,例如“command.address.street”。 bind宏假定 web.xml 中 ServletContext 参数defaultHtmlEscape指定的默认 HTML 转义行为

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

表单输入生成宏

两种语言的附加便利宏简化了 binding 和表单生成(包括验证错误显示)。永远不必使用这些宏来生成表单输入字段,它们可以与简单的 HTML 或 calls 混合并匹配,直接指向之前突出显示的 spring 绑定宏。

以下 table 可用宏显示 VTL 和 FTL 定义以及每个定义的参数列表。

表格 1_.宏定义表

VTL 定义FTL 定义
message(根据 code 参数从资源包中输出 string)#springMessage($code)< _1 code/>
messageText(根据 code 参数从资源包输出 string,回退到默认参数的 value)#springMessageText($code $text)< _1 code, text/>
url(使用 application 的 context 根作为相对 URL 的前缀)#springUrl($relativeUrl)< 1 relativeUrl/>
formInput(用于收集用户输入的标准输入字段)#springFormInput($path $attributes)< 1 path, attributes, fieldType/>
formHiddenInput *(用于提交 non-user 输入的隐藏输入字段)#springFormHiddenInput($path $attributes)< 1 path, attributes/>
formPasswordInput *(用于收集密码的标准输入字段.请注意,此类型的字段中不会填充 value)#springFormPasswordInput($path $attributes)< 1 path, attributes/>
formTextarea(用于收集 long,自由格式文本输入的大文本字段)#springFormTextarea($path $attributes)< 1 path, attributes/>
formSingleSelect(下拉框选项允许选择一个必需的 value)#springFormSingleSelect($path $options $attributes)< 1 path, options, attributes/>
formMultiSelect(允许用户选择 0 或更多值的选项列表框)#springFormMultiSelect($path $options $attributes)< 1 path, options, attributes/>
formRadioButtons(一组单选按钮,允许从可用选项中进行单个选择)#springFormRadioButtons($path $options $separator $attributes)< 1 path, options separator, attributes/>
formCheckboxes(一组允许选择 0 或更多值的复选框)#springFormCheckboxes($path $options $separator $attributes)< 1 path, options, separator, attributes/>
formCheckbox(单个复选框)#springFormCheckbox($path $attributes)< 1 path, attributes/>
showErrors(简化绑定字段的验证错误显示)#springShowErrors($separator $classOrStyle)< 1 separator, classOrStyle/>
  • 在 FTL(FreeMarker)中,实际上并不需要这两个宏,因为您可以使用普通的formInput宏,指定'hidden' or ' password'作为fieldType参数的 value。

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

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

  • options:可以在输入字段中选择的所有可用值的 Map。 map 的键表示将从表单返回并绑定到命令 object 的值。针对键存储的 Map objects 是表单上显示给用户的标签,可能与表单发回的相应值不同。通常这样的 map 由控制器作为 reference 数据提供。根据所需行为,可以使用任何 Map implementation。对于严格排序的 maps,可以使用,例如带有合适比较器的TreeMap,对于任何应该_return 插入 order 中的值的 Maps,使用LinkedHashMap或 commons-collections 来自 commons-collections。

  • separator:多个选项可用作谨慎元素(单选按钮或复选框),用于分隔列表中每个元素的字符序列(即“<br>”)。

  • attributes:HTML 标记本身中包含的任意标记或文本的附加 string。这个 string 由宏按字面回显。例如,在 textarea 字段中,您可以提供属性为'rows =“5”cols =“60”',或者您可以传递样式信息,例如'style =“border:1px solid silver”'。

  • classOrStyle:对于 showErrors 宏,包含每个错误的 span 标记将使用的 CSS class 的 name。如果未提供任何信息(或 value 为空),则错误将包含在<b> </b>标记中。

宏的示例在下面概述了一些在 FTL 中,一些在 VTL 中。如果两种语言之间存在使用差异,则会在说明中对其进行说明。

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

formInput 宏采用路径参数(command.name)和一个附加属性参数,该参数在上面的 example 中为空。宏以及所有其他表单生成宏在 path 参数上执行隐式 spring 绑定。 binding 在发生新绑定之前一直有效,因此 showErrors 宏不需要再次传递 path 参数 - 它只是对最后一次创建绑定的字段进行操作。

showErrors 宏接受一个 separator 参数(将用于分隔给定字段上的多个错误的字符)并接受第二个参数,此 time 是 class name 或 style 属性。请注意,与 Velocity 不同,FreeMarker 能够为 attributes 参数指定默认值,并且上面的两个宏 calls 可以在 FTL 中表示如下:

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

输出显示在生成 name 字段的表单片段的下方,并在表单提交后在字段中没有 value 时显示验证错误。验证通过 Spring 的验证 framework 进行。

生成的 HTML 如下所示:

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

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

选择字段

可以使用四个选择字段宏在 HTML 表单中生成 common UI value 选择输入。

  • formSingleSelect

  • formMultiSelect

  • formRadioButtons

  • formCheckboxes

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

FTL 中的单选按钮的示例如下所示。表单支持 object 为此字段指定了“伦敦”的默认值值,因此无需进行验证。呈现表单时,可以在 name'cityMap'下的 model 中作为 reference 数据提供可供选择的整个城市列表。

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

这将呈现一个 line 的单选按钮,使用分隔符“”为cityMap中的每个 value 一个。没有提供其他属性(缺少宏的最后一个参数)。 cityMap 对 map 中的每个 key-value 对使用相同的 String。 map 的键是表单实际提交为 POSTed 请求参数的键,map 值是用户看到的标签。在上面的例子中,给出了三个众所周知的城市的列表和支持 object 形式的默认 value,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>

如果您的 application 期望按 example 的内部代码处理城市,则代码的 map 将使用合适的键创建,如下面的 example。

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

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

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 转义的默认 value。为了使标签与 XHTML 兼容或覆盖默认的 HTML 转义 value,您可以在模板中指定两个变量(或在模板中指定模板可见的 model)。在模板中指定它们的优点是,它们可以在模板处理中稍后更改为不同的值,以便为表单中的不同字段提供不同的行为。

要切换到标记的 XHTML 合规性,请为名为 xhtmlCompliant 的 model/context 变量指定值'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 视图提供了几个 out-of-the-box 解决方案。使用 JSP 或 JSTL 是使用WebApplicationContext中定义的普通视图解析器完成的。此外,当然您需要编写一些实际呈现视图的 JSP。

设置 application 以使用 JSTL 是一个常见的错误来源,主要是由于对不同的 servlet 规范的混淆,JSP 和 JSTL version numbers,它们的含义以及如何正确声明 taglib。文章如何在 Web Application 中 Reference 和使用 JSTL为 common 陷阱提供了有用的指导,以及如何避免这些陷阱。请注意,从 Spring 3.0 开始,支持的最小 servlet version 是 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需要一个 properties 文件,用于定义映射到 1)class 和 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 files 放在'WEB-INF'目录下的目录中,因此 clients 无法直接访问。

23.5.2'Plain-old'JSPs 与 JSTL

使用 Java 标准标记 Library 时,您必须使用特殊视图 class,JstlView,因为 JSTL 需要一些准备工作,比如 I18N features 之类的工作。

23.5.3 Spring 的 JSP 标签 library

Spring 将请求参数的数据绑定提供给命令 objects,如前面章节所述。为了便于 JSP 页面的开发与 binding features 数据的结合,Spring 提供了一些标签,使事情变得更加容易。所有 Spring 标签都具有 HTML 转义 features 以启用或禁用字符转义。

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

23.5.4 Spring 的表格标签 library

从 version 2.0 开始,Spring 提供了一组全面的数据 binding-aware 标签,用于在使用 JSP 和 Spring Web MVC 时处理表单元素。每个标记都支持其相应 HTML 标记对应的属性集,使标记熟悉且直观易用。 tag-generated HTML 符合 HTML 4.01/XHTML 1.0.

与其他 form/input tag libraries 不同,Spring 的表单标签 library 与 Spring Web MVC 集成,使标签可以访问控制器处理的命令 object 和 reference 数据。正如您将在以下示例中看到的那样,表单标记使 JSP 更易于开发,读取和维护。

让我们浏览一下表单标签,看一下每个标签的使用方式。我们已经包含生成的 HTML 代码段,其中某些代码需要进一步评论。

Configuration

表单标签 library 捆绑在spring-webmvc.jar中。 library 描述符称为spring-form.tld

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

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

其中form是您要用于此 library 的标记的标记 name 前缀。

表单标记

此标记呈现 HTML“表单”标记,并为 binding 公开内部标记的 binding 路径。它将命令 object 放在PageContext中,以便内部标记可以访问命令 object。此 library 中的所有其他标记都是form标记的嵌套标记。

假设我们有一个名为User的域 object。它是一个带有 properties 的 JavaBean,例如firstNamelastName。我们将它用作表单控制器的表单支持 object,它返回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>

firstNamelastName值是从页面控制器放置在PageContext中的命令 object 中检索的。继续阅读以查看内部标记与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 假定支持 object 的表单的变量 name 是'command'。如果你将表单支持 object 放入另一个 name 下的 model(绝对是最佳实践),那么你可以将表单绑定到命名变量,如下所示:

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

输入标签

默认情况下,此标记使用 bound value 和 type ='text'呈现 HTML'input'标记。有关此标记的 example,请参阅名为“表格标签”的部分。从 Spring 3.1 开始,您可以使用其他类型,例如'email','tel',' date'等 HTML5-specific 类型。

复选框标记

此标记呈现带有“复选框”类型的 HTML“输入”标记。

让我们假设我们的User具有诸如简报订阅和爱好列表之类的偏好。下面是Preferences class 的示例:

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 种方法可以满足您的所有复选框需求。

  • 方法一 - 当绑定 value 的类型为java.lang.Boolean时,如果绑定的 value 为true,则input(checkbox)被标记为'checked'。 value属性对应于setValue(Object) value property 的已解析 value。

  • 方法二 - 当绑定 value 的类型为arrayjava.util.Collection时,如果配置的setValue(Object)value 存在于绑定的Collection中,则input(checkbox)被标记为'checked'。

  • 方法三 - 对于任何其他绑定 value 类型,如果配置的setValue(Object)等于绑定 value,则input(checkbox)被标记为'checked'。

请注意,无论采用何种方法,都会生成相同的 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 页面中的复选框,则在提交表单后,其 value 将不会作为 HTTP 请求参数的一部分发送到服务器,因此我们需要针对 Spring 表单数据 binding 的 order 中的这个 quirk 进行解决方法上班。 checkbox标记遵循现有的 Spring 约定,包括每个复选框的下划线(“_”)前缀的隐藏参数。通过这样做,你实际上告诉 Spring“复选框在表单中可见,我希望我的 object 将绑定表单数据以反映复选框的 state,无论如何”。

复选框标记

此标记呈现多个带有“复选框”类型的 HTML“输入”标记。

在前一个checkbox标记部分的 example 上构建。有时您不希望在 JSP 页面中列出所有可能的爱好。您宁愿在运行时提供可用选项的列表,并将其传递给标记。这就是checkboxes标签的目的。传入包含“items”property 中可用选项的ArrayListMap。通常,绑定的 property 是一个集合,因此它可以包含用户选择的多个值。下面是使用此标记的 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>

此 example 假定“interestList”是List可用作 model 属性,其中包含要从中选择的值的 strings。在使用 Map 的情况下,map 条目 key 将用作 value,map 条目的 value 将用作要显示的标签。您还可以使用自定义 object,您可以使用“itemValue”为 value 提供 property 名称,使用“itemLabel”提供标签。

radiobutton 标签

此标记呈现带有“radio”类型的 HTML“input”标记。

典型用法 pattern 将涉及绑定到同一 property 但具有不同值的多个标记实例。

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

radiobuttons 标签

此标记呈现多个带有“radio”类型的 HTML“input”标记。

就像上面的checkboxes标记一样,您可能希望将可用选项作为运行时变量传递。对于此用法,您将使用radiobuttons标记。传入包含“items”property 中可用选项的ArrayListMap。在使用 Map 的情况下,map 条目 key 将用作 value,map 条目的 value 将用作要显示的标签。您还可以使用自定义 object,您可以使用“itemValue”为 value 提供 property 名称,使用“itemLabel”提供标签。

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

密码标签

此标记使用 bound value 呈现带有“password”类型的 HTML“input”标记。

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

请注意,默认情况下,不显示密码 value。如果确实需要显示密码 value,则将'showPassword'属性的 value 设置为 true,如下所示。

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

select 标签

此标记呈现 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“选项”。它根据绑定的 value 适当地设置'选中'。

<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房子在格兰芬多,那么'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>

选项标签

此标记呈现 HTML'选项'标记的列表。它根据绑定的 value 适当地设置'selected'属性。

<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 中显式指定仅用于显示(它所属的位置)的 value,例如默认的 string。 example:“ - 请选择”。

items属性通常填充 item objects 的集合或 array。 itemValueitemLabel只是指那些 item objects 的 bean properties,如果指定的话;否则,item objects 本身将被字符串化。或者,您可以指定Map项,在这种情况下,map 键被解释为选项值,map 值对应于选项标签。如果恰好指定itemValue and/or itemLabel,item value property 将应用于 map key,item 标签 property 将应用于 map value。

textarea 标签

此标记呈现 HTML“textarea”。

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

隐藏的标签

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

<form:hidden path="house"/>

如果我们选择将'house'value 作为隐藏的提交,则 HTML 将如下所示:

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

错误标记

此标记在 HTML“span”标记中呈现字段错误。它可以访问控制器中创建的错误或由与控制器关联的任何验证器创建的错误。

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

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>

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

  • path="*" - 显示所有错误

  • path="lastName" - 显示与lastName字段关联的所有错误

  • 如果省略path,则仅显示 object 错误

下面的 example 将在页面顶部显示错误列表,然后在字段旁边显示 field-specific 错误:

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

为了支持 HTTP 方法转换,更新了 Spring MVC 表单标记以支持设置 HTTP 方法。对于 example,从更新的 Petclinic sample 中获取以下代码段

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

从 Spring 3 开始,Spring 表单标记 library 允许输入动态属性,这意味着您可以输入任何 HTML5 特定属性。

在 Spring 3.1 中,表单输入标记支持输入“text”以外的 type 属性。这旨在允许呈现新的 HTML5 特定输入类型,例如“email”,“ date”,“range”等。请注意,不需要输入 type ='text',因为'text'是默认类型。

23.6 脚本视图

可以使用 Spring 在 web applications 中的 JSR-223 脚本引擎上集成任何模板 library running。以下以宽泛的方式描述了如何执行此操作。脚本引擎必须实现ScriptEngineInvocable接口。

它已经过测试:

23.6.1 要求

为了能够使用脚本模板 integration,您需要在 classpath 中使用脚本引擎:

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

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

  • 应该在 order 中添加JRuby依赖项以获得 Ruby 支持。

  • 应在 order 中添加Jython依赖项以获得 Python 支持。

您还应该为基于脚本的模板引擎添加依赖项。对于 example,对于 Javascript,您可以使用WebJars在 order 中添加 Maven/Gradle 依赖项,以使 classpath 中的 javascript libraries 可用。

23.6.2 脚本模板

为了能够使用脚本模板,您必须在 order 中配置它以指定各种参数,例如要使用的脚本引擎,要加载的脚本 files 以及应该调用哪些 function 来呈现模板。这要归功于ScriptTemplateConfigurer bean 和可选脚本 files。

对于 example,在 order 中为了呈现 Mustache 模板,感谢 Java 8 提供的 Nashorn Javascript 引擎,您应该声明以下 configuration:

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

而 Mustache 模板是:

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

使用以下参数调用 render function:

  • String template:模板内容

  • Map model:视图 model

  • String url:模板网址(自 4.2.2 以来)

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

如果您的模板技术需要一些自定义,您可以提供一个实现自定义 render function 的脚本。例如,Handlerbars需要在使用之前编译模板,并且需要填充工具 in order 来模拟 server-side 脚本引擎中不可用的某些浏览器工具。

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

当使用非 thread-safe 脚本引擎和模板 libraries 不是为并发而设计时,需要将sharedEngine property 设置为false,如 Handlebars 或 Nactorn 上的 React running for example。在这种情况下,由于这个 bug,需要 Java 8u60 或更高版本。

polyfill.js仅定义 Handlebars 正确 run 所需的window object:

var window = {};

这个基本的render.js implementation 在使用之前编译模板。 production ready implementation 还应该 store 并重用缓存的模板/ pre-compiled 模板。这可以在脚本端完成,也可以完成所需的任何自定义(管理 example 的模板引擎 configuration)。

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

查看 Spring 脚本模板单元测试(java资源)以获取更多 configuration 示例。

23.7 XML 编组

MarshallingView使用org.springframework.oxm包中定义的 XML Marshaller将响应内容呈现为 XML。可以使用MarshallingViewmodelKey bean property 显式设置要编组的 object。或者,视图将遍历所有 model properties 并封送Marshaller支持的第一个类型。有关org.springframework.oxm包中功能的更多信息,请参阅使用 O/X Mappers 编组 XML一章。

23.8 Tiles

可以使用 Spring 在 web applications 中集成 Tiles - 就像任何其他视图技术一样。以下以宽泛的方式描述了如何执行此操作。

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

23.8.1 依赖关系

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

23.8.2 Configuration

为了能够使用 Tiles,您必须使用包含定义的 files 来配置它(有关定义和其他 Tiles 概念的基本信息,请查看http://tiles.apache.org)。在 Spring 中,这是使用TilesConfigurer完成的。看看下面的 example ApplicationContext configuration:

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

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

您可以通过添加下划线然后添加 locale 来指定 locale 特定的 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>

使用此 configuration,tiles_fr_FR.xml将用于具有fr_FR locale 的请求,并且默认情况下将使用tiles.xml

由于下划线用于表示区域设置,因此建议避免在 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必须提供一个 property 文件,其中包含视图名称和解析器可以使用的视图 classes:

<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 class 支持开箱即用的 JSTL(JSP 标准标记 Library)。

SimpleSpringPreparerFactory 和 SpringBeanPreparerFactory

作为高级 feature,Spring 还支持两个特殊的 Tiles PreparerFactory implementations。查看 Tiles 文档,了解有关如何在 Tiles 定义 files 中使用ViewPreparer references 的详细信息。

指定SimpleSpringPreparerFactory以基于指定的 preparer classes 自动装配 ViewPreparer 实例,应用 Spring 的容器回调以及应用已配置的 Spring BeanPostProcessors。如果 Spring 的 context-wide annotation-config 已被激活,则会自动检测并应用 ViewPreparer classes 中的注释。请注意,这需要 Tiles 定义 files 中的 preparer classes,就像默认的PreparerFactory一样。

指定SpringBeanPreparerFactory以对指定的 preparer 名称而不是 classes 进行操作,从 DispatcherServlet 的 application context 中获取相应的 Spring bean。在这种情况下,完整的 bean creation process 将控制 Spring application context,允许使用显式依赖注入 configuration,scoped beans 等。请注意,您需要为每个 preparer name 定义一个 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 applications 中作为视图技术很受欢迎。如果您的 application 自然地处理 XML,或者您的 model 可以很容易地转换为 XML,那么 XSLT 可以作为视图技术的一个很好的选择。以下部分显示如何将 XML 文档生成为 model 数据,并在 Spring Web MVC application 中使用 XSLT 进行转换。

这个 example 是一个简单的 Spring application,可以在Controller中创建一个单词列表,并将它们添加到 model map 中。 map 与 XSLT 视图的 view name 一起返回。有关 Spring Web MVC 的Controller接口的详细信息,请参阅第 22.3 节,“实现控制器”。 XSLT 控制器将单词列表转换为准备转换的简单 XML 文档。

23.9.1 Beans

Configuration 是一个简单的 Spring application 的标准配置。 MVC configuration 必须定义XsltViewResolver bean 和常规 MVC annotation configuration。

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

}

我们需要一个封装字生成逻辑的 Controller。

23.9.2 控制器

控制器逻辑封装在@Controller class 中,处理程序方法的定义如下......

@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 文档并将其添加到 Model map 中。请注意,您还可以将 XML 文件作为Resource加载,并使用它而不是自定义 DOM 文档。

当然,有些软件包可以自动“统一”object 图形,但在 Spring 中,您可以完全灵活地以您选择的任何方式从 model 创建 DOM。这可以防止 XML 的转换在 model 数据的结构中扮演太大的角色,这在使用工具管理统一 process 时是一种危险。

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

23.9.3 转型

最后,XsltViewResolver将解析“home”XSLT 模板文件并将 DOM 文档合并到其中以生成我们的视图。如XsltViewResolver configuration 中所示,XSLT 模板位于'WEB-INF/xsl'目录的 war 文件中,并以"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>
	<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 简介

返回 HTML 页面并不总是用户查看 model 输出的最佳方式,而 Spring 使得从 model 数据动态生成 PDF 文档或 Excel 电子表格变得简单。该文档是视图,将从服务器使用正确的 content type 流式传输(希望)使 client PC 能够运行其电子表格或 PDF 查看器应用程序作为响应。

在 order 中使用 Excel 视图,您需要将 Apache POI library 添加到 classpath,并且要生成 PDF,最好是 OpenPDF library。

如果可能,请使用最新版本的基础文档生成 libraries。特别是,我们强烈建议使用 OpenPDF(e.g. OpenPDF 1.0.5)而不是过时的原始 iText 2.1.7,因为它是主动维护的,并修复了不受信任的 PDF 内容的重要漏洞。

23.10.2 Configuration

基于文档的视图以与 XSLT 视图几乎相同的方式处理,以下部分通过演示如何调用 XSLT example 中使用的相同控制器来呈现与 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 表单开始添加 model 数据,请在视图定义中将该位置指定为“url”property

23.10.4 控制器

我们将使用的控制器 code 与之前的 XSLT example 完全相同,只是更改要使用的视图的 name。当然,您可以聪明并根据 URL 参数或其他逻辑选择此选项 - 证明 Spring 非常擅长将视图与控制器分离!

23.10.5 Excel 视图

正如我们对 XSLT example 所做的那样,我们将在 order 中继承适当的抽象 classes,以在生成输出文档时实现自定义行为。对于 Excel,这涉及编写org.springframework.web.servlet.view.document.AbstractExcelView的子类(对于由 POI 生成的 Excel files)或org.springframework.web.servlet.view.document.AbstractJExcelView(对于 JExcelApi-generated Excel files)并实现buildExcelDocument()方法。

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

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

}

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

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 的 image-handling 功能稍好一些。但是,在使用 JExcelApi 时,大型 Excel files 存在 memory 问题。

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

23.10.6 PDF 视图

单词列表的 PDF version 甚至更简单。这个 time,class 扩展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)));
        }
    }

}

再次修改控制器以返回pdf视图,并在 application 中重新加载 URL。此 time 应该出现一个 PDF 文档,列出 model map 中的每个单词。

23.11 JasperReports

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

23.11.1 依赖关系

您的 application 将需要包含最新版本的 JasperReports,e.g. 6.2. JasperReports 本身取决于以下项目:

  • BeanShell

  • Commons BeanUtils

  • Commons _Collections

  • Commons _Digester

  • Commons Logging

  • iText

  • POI

JasperReports 还需要一个 JAXP-compliant XML 解析器。

23.11.2 Configuration

要在 Spring 容器 configuration 中配置 JasperReports 视图,您需要将ViewResolver到 map 视图名称定义到相应的视图 class,具体取决于您希望报表呈现的格式。

配置 ViewResolver

通常,您将使用ResourceBundleViewResolver到 map 视图名称来查看 properties 文件中的 classes 和 files。

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

这里我们已经配置了ResourceBundleViewResolver class 的一个实例,它将在资源包中查找带有 base name views的视图映射。 (这个文件的内容在下一个 section.)中描述

配置视图

Spring Framework 为 JasperReports 包含五个不同的View __mplement,其中四个对应于 JasperReports 支持的四种输出格式之一,并且允许在运行时确定格式:

表格 1_.JasperReports 查看 classes

班级名称渲染格式
JasperReportsCsvViewCSV
JasperReportsHtmlViewHTML
JasperReportsPdfViewPDF
JasperReportsXlsViewMicrosoft Excel
JasperReportsMultiFormatView视图是在运行时决定

将其中一个 classes 映射到视图 name 和报告文件,需要在上一节中配置的资源包中添加相应的条目,如下所示:

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

在这里,您可以看到 name simpleReport视图被映射到JasperReportsPdfView class,导致此报告的输出以 PDF 格式呈现。视图的url property 设置为基础报告文件的位置。

关于报告文件

JasperReports 有两种不同类型的报告文件:设计文件(扩展名为.jrxml)和编译后的报告文件(扩展名为.jasper)。通常,在将.jrxml设计文件部署到 application 之前,使用 JasperReports Ant 任务将.jrxml设计文件编译为.jasper文件。使用 Spring Framework,您可以_map 这些 files 中的任何一个到您的报告文件,framework 将负责为您动态编译.jrxml文件。您应该注意,在 Spring Framework 编译.jrxml文件之后,编译的报告将缓存 application 的生命周期。因此,要对文件进行更改,您需要重新启动 application。

使用 JasperReportsMultiFormatView

JasperReportsMultiFormatView允许在运行时指定报告格式。报告的实际呈现被委托给其他 JasperReports 视图 classes 之一 - JasperReportsMultiFormatView class 只是添加一个 wrapper 层,允许在运行时指定确切的 implementation。

JasperReportsMultiFormatView class 引入了两个概念:格式 key 和鉴别器 key。 JasperReportsMultiFormatView class 使用映射 key 查找实际视图 implementation class,它使用格式 key 查找映射 key。从编码的角度来看,你可以在 model 中添加一个条目,其格式为 key 作为 key,映射 key 作为 value,对于 example:

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

}

在此 example 中,映射 key 是根据请求 URI 的扩展名确定的,并以默认格式 key:format添加到 model。如果您希望使用不同的格式 key,则可以使用JasperReportsMultiFormatView class 的formatKey property 来配置它。

默认情况下,在JasperReportsMultiFormatView中配置以下映射 key 映射:

表格 1_.JasperReportsMultiFormatView 默认映射 Key 映射

映射 Key查看 Class
CSVJasperReportsCsvView
HTMLJasperReportsHtmlView
PDF 格式JasperReportsPdfView
XLSJasperReportsXlsView

因此,在上面的 example 中,对 URI /foo/myReport.pdf 的请求将映射到JasperReportsPdfView class。您可以使用JasperReportsMultiFormatViewformatMappings property 覆盖映射 key 以查看 class 映射。

23.11.3 填充 ModelAndView

在 order 中以您选择的格式正确呈现报告,您必须为 Spring 提供填充报告所需的所有数据。对于 JasperReports,这意味着您必须传递所有报告参数以及报告数据源。报告参数是简单的 name/value 对,可以添加到_m的Map中,就像添加任何 name/value 对一样。

将数据源添加到 model 时,您有两种方法可供选择。第一种方法是在任意 key 下的 model Map中添加JRDataSourceCollection类型的实例。然后,Spring 将在 model 中找到此 object,并将其视为报表数据源。对于 example,您可以像这样填充 model:

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

第二种方法是在特定 key 下添加JRDataSourceCollection的实例,然后使用 view class 的reportDataKey property 配置此 key。在这两种情况下,Spring 都会在JRBeanCollectionDataSource实例中包装Collection的实例。例如:

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

在这里,您可以看到两个Collection实例正被添加到 model。为确保使用正确的,我们只需根据需要修改 view configuration:

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

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

23.11.4 使用 sub-reports

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

配置 sub-report files

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

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

然后,定义 sub-report 以使用此 sub-report 参数:

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

这定义了一个 master 报告文件,该文件希望 sub-report 作为参数ProductsSubReport下的net.sf.jasperreports.engine.JasperReports实例传入。在配置 Jasper 视图 class 时,您可以指示 Spring 加载报告文件并使用subReportUrls property 将其作为 sub-report 传递到 JasperReports 引擎:

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

这里,Map的 key 对应于报表设计文件中 sub-report 参数的 name,该条目是报表文件的 URL。 Spring 将加载此报告文件,必要时对其进行编译,并将其传递到给定 key 下的 JasperReports 引擎中。

配置 sub-report 数据源

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

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

这里,您提供的 key 必须对应于ModelAndView中使用的 key 和报表设计文件中使用的 key。

23.11.5 配置导出器参数

如果您对 exporter configuration 有特殊要求 - 可能需要 PDF 报告的特定页面大小 - 您可以使用 view class 的exporterParameters property 在 Spring configuration 文件中以声明方式配置这些导出器参数。 exporterParameters property 被输入为Map。在 configuration 中,条目的 key 应该是包含导出器参数定义的静态字段的 fully-qualified name,条目的 value 应该是要分配给参数的 value。这个例子如下所示:

<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 Feed 视图:RSS,Atom

AbstractAtomFeedViewAbstractRssFeedView都从 base class AbstractFeedView继承,用于分别提供 Atom 和 RSS Feed 视图。它们基于 java.net 的罗马项目,位于包org.springframework.web.servlet.view.feed中。

AbstractAtomFeedView要求您实现buildFeedEntries()方法并可选地覆盖buildFeedMetadata()方法(默认 implementation 为空),如下所示。

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
    }

}

如果您需要访问 Locale,buildFeedItems()buildFeedEntires()方法会传入 HTTP 请求。 HTTP 响应仅传递给 cookies 或其他 HTTP headers 的设置。方法返回后,Feed 将自动写入响应 object。

有关创建 Atom 视图的示例,请参阅 Alef Arendsen 的 Spring 团队博客条目

23.13 JSON 映射视图

MappingJackson2JsonView使用 Jackson library 的ObjectMapper将响应内容呈现为 JSON。默认情况下,model map 的全部内容(类的 exception)将被编码为 JSON。对于需要过滤 map 内容的情况,用户可以通过modelKeys property 指定一组特定的 model 属性进行编码。 extractValueFromSingleKeyModel property 也可用于直接提取和序列化 single-key 模型中的 value,而不是 model 属性的 map。

通过使用 Jackson 提供的注释,可以根据需要自定义 JSON 映射。当需要进一步控制时,可以通过ObjectMapper property 注入自定义ObjectMapper,以用于需要为特定类型提供自定义 JSON serializers/deserializers 的情况。

从 Spring Framework 4.3.18 开始,不推荐使用JSONP支持,并且需要通过jsonpParameterNames property 自定义 JSONP 查询参数 name(s)。从 Spring Framework 5.1 开始,此支持将被删除,应该使用CORS

23.14 XML 映射视图

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

可以根据需要通过使用 JAXB 或 Jackson 提供的注释来自定义 XML 映射。当需要进一步控制时,可以通过ObjectMapper property 注入自定义XmlMapper,以用于需要为特定类型提供自定义 XML serializers/deserializers 的情况。

Updated at: 5 months ago
22.16.14. 使用 MVC 命名空间进行高级自定义Table of content24. 与其他 web 框架集成