23. 查看技术

23.1 简介

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

23.2 Thymeleaf

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

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

23.3 Groovy标记

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

这需要在类路径上使用Groovy 2.3.1。

23.3.1 配置

配置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页面的示例模板:

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

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

自Spring Framework 4.3起,由于没有主动维护Apache Velocity项目六年,Velocity支持已被弃用。我们建议使用Spring的FreeMarker支持,或者Spring支持自带的Thymeleaf。

23.4.1 依赖关系

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

23.4.2 上下文配置

通过将相关的configurer 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>

对于非Web应用程序,请将VelocityConfigurationFactoryBean或FreeMarkerConfigurationFactoryBean添加到应用程序上下文定义文件中。

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>

或者,您可以通过将“configLocation”属性替换为以下内联属性,直接在Velocity配置Bean的bean定义中指定velocity属性。

<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 for Spring配置Velocity,或Velocity文档中的示例和 'velocity.properties' 文件本身的定义。

FreeMarker

FreeMarker'Settings'和'SharedVariables'可以通过在 FreeMarkerConfigurer bean上设置适当的bean属性直接传递给Spring管理的FreeMarker Configuration 对象。 freemarkerSettings 属性需要 java.util.Properties 对象, freemarkerVariables 属性需要a 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中的相同功能,还有用于生成表单输入元素的额外便利宏。

绑定宏

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

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

简单绑定

在作为Spring MVC控制器的表单视图的HTML表单(vm / ftl模板)中,您可以使用类似于以下内容的代码绑定到字段值,并以与JSP等效方式类似的方式显示每个输入字段的错误消息。下面显示了前面配置的 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转义的使用,应尽可能使用这些宏。它们将在下一节中介绍。

表单输入生成宏

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

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

Table 23.1. Table of macro definitions

VTL定义FTL定义
message (根据代码参数从资源包中输出字符串)#springMessage($ code)<@ spring.message code />
messageText (根据代码参数从资源包中输出字符串,回退到默认参数的值)#springMessageText($ code $ text)<@ spring.messageText code,text />
url (使用应用程序的上下文根作为相对URL的前缀)#springUrl($ relativeUrl)<@ spring.url relativeUrl />
formInput (用于收集用户输入的标准输入字段)#springFormInput($ path $ attributes)<@ spring.formInput path,attributes,fieldType />
formHiddenInput *(用于提交非用户输入的隐藏输入字段)#springFormHiddenInput($ path $ attributes)<@ spring.formHiddenInput path,attributes />
formPasswordInput *(用于收集密码的标准输入字段。请注意,此类型的字段中不会填充任何值)#springFormPasswordInput($ path $ attributes)<@ spring.formPasswordInput path,attributes />
formTextarea (用于收集长,自由格式文本输入的大文本字段)#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" )

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

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

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

  • classOrStyle:对于showErrors宏,包含每个错误的span标记将使用的CSS类的名称。如果未提供任何信息(或值为空),则错误将包含在 </ 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)和附加属性参数,该参数在上面的示例中为空。宏以及所有其他表单生成宏在path参数上执行隐式 spring 绑定。绑定在发生新绑定之前一直有效,因此showErrors宏不需要再次传递path参数 - 它只是对上次创建绑定的任何字段进行操作。

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

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

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

生成的HTML如下所示:

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

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

选择字段

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

  • formSingleSelect

  • formMultiSelect

  • formRadioButtons

  • formCheckboxes

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

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

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

这将呈现一行单选按钮,使用分隔符“”为 cityMap 中的每个值。没有提供其他属性(缺少宏的最后一个参数)。 cityMap对映射中的每个键值对使用相同的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视图提供了一些开箱即用的解决方案。使用JSP或JSTL是使用 WebApplicationContext 中定义的普通视图解析器完成的。此外,当然您需要编写一些实际呈现视图的JSP。

设置应用程序以使用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 需要一个属性文件,用于定义映射到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' 目录下的目录中,这样客户端就无法直接访问。

23.5.2 '普通的'JSP与JSTL

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

23.5.3 Spring的JSP标记库

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

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

23.5.4 Spring的表单标记库

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

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

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

配置

表单标记库捆绑在 spring-webmvc.jar 中。库描述符名为 spring-form.tld

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

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

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

表单标记

此标记呈现HTML'表单'标记和公开内部标记的绑定路径以进行绑定。它将命令对象放在 PageContext 中,以便内部标记可以访问命令对象。此库中的所有其他标记都是 form 标记的嵌套标记。

假设我们有一个名为 User 的域对象。它是一个JavaBean,具有 firstNamelastName 等属性。我们将它用作表单控制器的表单支持对象,它返回 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 中的命令对象中检索的。继续阅读以查看内部标记与 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>

输入标签

此标记默认使用绑定值和type ='text'呈现HTML'input'标记。有关此标记的示例,请参阅 the section called “The form tag” 。从Spring 3.1开始,您可以使用其他类型的HTML5特定类型,如“email”,“tel”,“date”等。

复选框标记

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

让我们假设我们的 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 时,如果绑定值为 trueinput(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“输入”标记。

以前一个 checkbox 标签部分的示例为基础。有时您不希望在JSP页面中列出所有可能的爱好。您宁愿在运行时提供可用选项的列表,并将其传递给标记。这是 checkboxes 标签的目的。您传入 ArrayListMap ,其中包含 "items" 属性中的可用选项。通常,绑定属性是一个集合,因此它可以包含用户选择的多个值。下面是使用此标记的JSP示例:

<form:form>
    <table>
        <tr>
            <td>Interests:</td>
            <td>
                <%-- Property is of an array or of type java.util.Collection --%>
                <form:checkboxes path="preferences.interests" items="${interestList}"/>
            </td>
        </tr>
    </table>
</form:form>

此示例假定 "interestList" 是 List 可用作模型属性,其中包含要从中选择的值的字符串。在使用Map的情况下,映射条目键将用作值,映射条目的值将用作要显示的标签。您还可以使用自定义对象,您可以使用 "itemValue" 提供值的属性名称,使用 "itemLabel" 提供标签。

radiobutton标签

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

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

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

radiobuttons标签

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

就像上面的 checkboxes 标记一样,您可能希望将可用选项作为运行时变量传递。对于此用法,您将使用 radiobuttons 标记。您传入 ArrayListMap ,其中包含 "items" 属性中的可用选项。在使用Map的情况下,映射条目键将用作值,映射条目的值将用作要显示的标签。您还可以使用自定义对象,您可以在其中提供值的属性名称 "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 房子在格兰芬多,那么'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'选项'标记的列表。它根据绑定值设置“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 住在英国,“国家/地区”行的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中显式指定仅用于显示的值(它所属的位置),例如例如: "-- Please Select" 。

items 属性通常使用项目对象的集合或数组填充。 itemValueitemLabel 只是引用那些项目对象的bean属性,如果指定的话;否则,项目对象本身将被字符串化。或者,您可以指定 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>

隐藏的标签

此标记使用绑定值呈现类型为“hidden”的HTML“input”标记。要提交未绑定的隐藏值,请使用类型为“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.<br/>Field is required.</span>
    <table>
        <tr>
            <td>First Name:</td>
            <td><input name="firstName" type="text" value=""/></td>
            <td><span name="firstName.errors">Field is required.</span></td>
        </tr>

        <tr>
            <td>Last Name:</td>
            <td><input name="lastName" type="text" value=""/></td>
            <td><span name="lastName.errors">Field is required.</span></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form>

HTTP方法转换

REST的一个关键原则是使用统一接口。这意味着可以使用相同的四种HTTP方法操作所有资源(URL):GET,PUT,POST和DELETE。对于每个方法,HTTP规范定义了确切的语义。例如,GET应始终是一个安全的操作,这意味着它没有副作用,PUT或DELETE应该是幂等的,这意味着你可以反复重复这些操作,但最终结果应该是相同的。虽然HTTP定义了这四种方法,但HTML只支持两种:GET和POST。幸运的是,有两种可能的解决方法:您可以使用JavaScript来执行PUT或DELETE,或者只使用“real”方法作为附加参数进行POST(在HTML表单中建模为隐藏输入字段)。后一个技巧就是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方法隐藏在请求参数后面由_861622_提升,如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表单标记库允许输入动态属性,这意味着您可以输入任何HTML5特定属性。

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

23.6 脚本视图

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

它已经过测试:

23.6.1 要求

为了能够使用脚本模板集成,您需要在类路径中使用脚本引擎:

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

  • Rhino Javascript引擎内置Java 6和Java 7.请注意,不推荐使用Rhino,因为它不支持运行大多数模板引擎。
    应该添加

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

  • Jython 依赖项以获得Python支持。

您还应该为基于脚本的模板引擎添加依赖项。例如,对于Javascript,您可以使用 WebJars 添加Maven / Gradle依赖项,以便在类路径中使您的javascript库可用。

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

而Mustache模板是:

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

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

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

var window = {};

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

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 包中功能的更多信息,请参阅 Marshalling XML using O/X Mappers 一章。

23.8 瓷砖

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

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

23.8.1 依赖关系

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

23.8.2 配置

能够要使用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

由于下划线用于表示区域设置,因此建议避免在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 references的详细信息。

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

指定 SpringBeanPreparerFactory 以对指定的preparer名称而不是类进行操作,从DispatcherServlet的应用程序上下文中获取相应的Spring bean。在这种情况下,完整的bean创建过程将控制Spring应用程序上下文,允许使用显式依赖项注入配置,作用域bean等。请注意,您需要为每个preparer名称定义一个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 中创建一个单词列表并将它们添加到模型映射中。将返回映射以及XSLT视图的视图名称。有关Spring Web MVC的 Controller 接口的详细信息,请参阅 Section 22.3, “Implementing Controllers” 。 XSLT控制器将单词列表转换为准备转换的简单XML文档。

23.9.1 beans 子

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

@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 类中,处理程序方法的定义如下......

@Controller
public class XsltController {

    @RequestMapping("/")
    public String home(Model model) throws Exception {

        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        Element root = document.createElement("wordList");

        List<String> words = Arrays.asList("Hello", "Spring", "Framework");
        for (String word : words) {
            Element wordNode = document.createElement("word");
            Text textNode = document.createTextNode(word);
            wordNode.appendChild(textNode);
            root.appendChild(wordNode);
        }

        model.addAttribute("wordList", root);
        return "home";
    }

}

到目前为止,我们只创建了一个DOM文档并将其添加到模型图中。请注意,您还可以将XML文件作为 Resource 加载,并使用它而不是自定义DOM文档。

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

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

23.9.3 转型

最后, XsltViewResolver 将解析 "home" XSLT模板文件并将DOM文档合并到其中以生成我们的视图。如 XsltViewResolver 配置中所示,XSLT模板存在于战争中文件在 'WEB-INF/xsl' 目录中,以 "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页面并不总是用户查看模型输出的最佳方式,而Spring可以从模型数据动态生成PDF文档或Excel电子表格。该文档是视图,将从具有正确内容类型的服务器流式传输到(希望)使客户端PC能够运行其电子表格或PDF查看器应用程序作为响应。

要使用Excel视图,您需要将Apache POI库添加到类路径中,并为PDF生成优选的OpenPDF库。

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

23.10.2 配置

基于文档的视图以与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 控制器

我们将使用的控制器代码与之前的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));
        }
    }

}

以下是生成相同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具有稍好的图像处理能力。但是,使用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 依赖关系

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

  • BeanShell

  • Commons BeanUtils

  • Commons Collections

  • Commons Digester

  • Commons Logging

  • iText

  • POI

JasperReports还需要符合JAXP的XML解析器。

23.11.2 配置

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

配置ViewResolver

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

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

这里我们配置了一个 ResourceBundleViewResolver 类的实例,它将在资源包中查找基本名称为 views 的视图映射。 (此文件的内容将在下一节中介绍。)

配置查看

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

Table 23.2. JasperReports View classes

class 名称渲染格式
JasperReportsCsvViewCSV
JasperReportsHtmlViewHTML
JasperReportsPdfViewPDF
JasperReportsXlsViewMicrosoft Excel
JasperReportsMultiFormatView该视图为 decided upon at runtime

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

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

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

关于报告文件

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

使用JasperReportsMultiFormatView

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

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

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

}

在此示例中,映射关键字是根据请求URI的扩展名确定的,并以默认格式密钥添加到模型中: format 。如果您希望使用其他格式密钥,则可以使用 JasperReportsMultiFormatView 类的 formatKey 属性对其进行配置。

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

Table 23.3. JasperReportsMultiFormatView Default Mapping Key Mappings

制图键查看类
csvJasperReportsCsvView
htmlJasperReportsHtmlView
pdfJasperReportsPdfView
xlsJasperReportsXlsView

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

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都会在 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 实例正被添加到模型中。为确保使用正确的,我们只需根据需要修改视图配置:

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视图类时,可以指示Spring加载报告文件,并使用 subReportUrls 属性将其作为子报告传递到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 Feed视图:RSS,Atom

AbstractAtomFeedViewAbstractRssFeedView 都从基类 AbstractFeedView 继承,并用于分别提供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
    }

}

如果您需要访问Locale, buildFeedItems()buildFeedEntires() 方法会传入HTTP请求。 HTTP响应仅用于设置cookie或其他HTTP标头。方法返回后,Feed将自动写入响应对象。

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

23.13 JSON映射视图

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

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

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

23.14 XML映射视图

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

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

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

There are no comments.