22. Web MVC framework

22.1 Spring Web MVC framework 简介

Spring Web model-view-controller(MVC)framework 是围绕DispatcherServlet设计的,它通过可配置的处理程序映射,视图解析,locale,time zone 和主题解析以及对上传 files 的支持来向处理程序发送请求。默认处理程序基于@Controller@RequestMapping 注释,提供各种灵活的处理方法。随着 Spring 3.0 的引入,@Controller机制还允许您通过@PathVariable annotation 和其他 features 创建 RESTful Web 站点和 applications。


“打开扩展...”Spring Web MVC 和 Spring 中的 key 设计原则一般是“Open for extension,closed for modification”原则。

Spring Web MVC 的核心 classes 中的一些方法标记为final。作为开发人员,您无法覆盖这些方法来提供自己的行为。这不是任意做的,而是特别考虑到这一原则。

有关此原理的解释,请参阅 Seth Ladd 等人的 Expert Spring Web MVC 和 Web Flow;请参阅第一版第 117 页的“设计外观”一节。或者,请参阅

  • 鲍勃·马丁,Open-Closed 原则(PDF)

使用 Spring MVC 时,无法向最终方法添加建议。对于 example,您无法向AbstractController.setSynchronizeOnSession()方法添加建议。有关 AOP 代理的更多信息以及无法向最终方法添加建议的原因,请参阅第 11.6.1 节,“了解 AOP 代理”。


在 Spring Web MVC 中,您可以使用任何 object 作为命令或 form-backing object;您不需要实现 framework-specific 接口或 base class。 Spring 的数据 binding 非常灵活:例如,它将类型不匹配视为可由 application 评估的验证错误,而不是系统错误。因此,您不需要将您的业务 objects'properties 复制为表单 objects 中的简单无类型 strings,只是为了处理无效提交,或者正确转换 Strings。相反,通常最好直接绑定到您的业务 objects。

Spring 的视图分辨率非常灵活。 Controller通常负责使用数据准备 model Map并选择 view name,但它也可以直接写入响应流并完成请求。 View name resolution 可通过文件扩展名或 Accept header content type negotiation,bean 名称,properties 文件甚至自定义ViewResolver implementation 进行高度配置。 model(MVC 中的 M)是一个Map接口,它允许完全抽象视图技术。您可以直接与基于模板的渲染技术(如 JSP,Velocity 和 Freemarker)集成,也可以直接生成 XML,JSON,Atom 和许多其他类型的内容。 model Map只是转换为适当的格式,例如 JSP 请求属性,Velocity 模板 model。

22.1.1 Spring Web MVC 的特征


春天 Web 流

Spring Web Flow(SWF)旨在成为 web application 页面流的 management 的最佳解决方案。

SWF 在 Servlet 和 Portlet 环境中与现有框架(如 Spring MVC 和 JSF)集成。如果您有一个业务 process(或进程)可以从对话 model 而不是纯请求 model 中受益,那么 SWF 可能就是解决方案。

SWF 允许您将逻辑页面流捕获为可在不同情况下重用的 self-contained 模块,因此非常适合 building web application 模块,这些模块可引导用户完成驱动业务流程的受控导航。

有关 SWF 的更多信息,请参阅Spring Web Flow 网站。


Spring 的 web 模块包含许多独特的 web 支持 features:

  • 明确分离角色。每个角色 - 控制器,验证器,命令 object,表单 object,model object,DispatcherServlet,处理程序映射,视图解析器等 - 都可以由专门的 object 完成。

  • framework 和 application classes 作为 JavaBeans 的强大而直接的 configuration 配置。此 configuration 功能包括跨上下文轻松引用,例如从 web 控制器到 business objects 和验证器。

  • 适应性,non-intrusiveness 和灵活性。定义所需的任何控制器方法签名,可能使用给定方案的参数 annotations 之一(例如@RequestParam,@RequestHeader,@PathVariable 等)。

  • 可重复使用的业务 code,无需重复。使用现有业务 objects 作为命令或表单 objects 而不是镜像它们以扩展特定的 framework base class。

  • 可自定义的 binding 和验证。键入不匹配作为 application-level 验证错误,保留违规 value,本地化 date 和数字 binding 等,而不是 String-only form objects,手动解析并转换为 business objects。

  • 可定制的处理程序映射和视图解析。处理程序映射和视图解析策略的范围从简单的 URL-based configuration 到复杂的 purpose-built 解析策略。 Spring 比强制特定技术的 web MVC 框架更灵活。

  • 灵活的模型转移。使用 name/value Map进行 Model 传输支持使用任何视图技术轻松 integration。

  • 可定制的 locale,time zone 和主题解析,支持带或不带 Spring 标签 library 的 JSP,支持 JSTL,支持 Velocity 而无需额外的桥接,等等。

  • 一个简单而强大的 JSP 标记 library,称为 Spring 标记 library,它支持 features,如 data binding 和 themes。自定义标记在标记 code 方面具有最大的灵活性。有关标记 library 描述符的信息,请参阅标题为第 43 章,spring JSP 标签 Library的附录

  • 在 Spring 2.0 中引入的 JSP 表单标记 library,使得在 JSP 页面中编写表单变得更加容易。有关标记 library 描述符的信息,请参阅标题为第 44 章,spring-form JSP 标签 Library的附录

  • Beans,其生命周期范围限定为当前 HTTP 请求或 HTTP Session。这不是 Spring MVC 本身的特定 feature,而是 Spring MVC 使用的WebApplicationContext container(s)。这些 bean 范围在第 7.5.4 节,“请求,session,global session,application 和 WebSocket 范围”中描述

22.1.2 其他 MVC implementations 的可插拔性

对于某些项目,Non-Spring MVC implementations 更受欢迎。许多团队希望利用他们现有的技能和工具投资,例如 JSF。

如果您不想使用 Spring 的 Web MVC,但打算利用 Spring 提供的其他解决方案,您可以轻松地将 web MVC framework 与 Spring 集成。只需通过启动 Spring root application context,然后从任何操作 object 中通过ServletContext属性(或 Spring 各自的帮助方法)访问它。不涉及“plug-ins”,因此不需要专门的整合。从 web 层的角度来看,您只需使用 Spring 作为 library,将 root application context 实例作为入口点。

即使没有 Spring 的 Web MVC,您注册的 beans 和 Spring 的服务也可以触手可及。在这种情况下,Spring 不会与其他 web 框架竞争。它简单地解决了纯 web MVC 框架没有的许多方面,从 bean configuration 到数据访问和 transaction 处理。因此,您可以使用 Spring 中间层 and/or 数据访问层来丰富您的 application,即使您只是想使用 JDBC 或 Hibernate 来实现 transaction 抽象。

22.2 DispatcherServlet

Spring 的 web MVC framework 与许多其他 web MVC 框架一样,request-driven,围绕一个中央 Servlet 设计,它将请求分派给控制器并提供其他功能,以促进 web applications 的开发。 Spring 的DispatcherServlet然而,不仅如此。它与 Spring IoC 容器完全集成,因此允许您使用 Spring 所具有的所有其他 feature。

Spring Web MVC DispatcherServlet的请求处理工作流程如下图所示。 pattern-savvy reader 将识别DispatcherServlet是“Front Controller”设计 pattern 的表达式(这是 Spring Web MVC 与许多其他领先的 web 框架共享的 pattern)。

图 1_.Spring Web MVC 中的请求处理工作流程(高级别)

DispatcherServlet是一个实际的Servlet(它继承自HttpServlet base class),因此在 web application 中声明。您需要使用 URL 映射 map 您希望DispatcherServlet处理的请求。以下是 Servlet 3.0 环境中的标准 Java EE Servlet configuration:

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        ServletRegistration.Dynamic registration = container.addServlet("example", new DispatcherServlet());
        registration.setLoadOnStartup(1);
        registration.addMapping("/example/*");
    }
}

在前面的 example 中,所有以/example开头的请求都将由名为exampleDispatcherServlet实例处理。

WebApplicationInitializer是 Spring MVC 提供的接口,可确保检测到 code-based configuration 并自动用于初始化任何 Servlet 3 容器。这个名为AbstractAnnotationConfigDispatcherServletInitializer的接口的抽象 base class implementation 使得通过简单地指定其 servlet 映射并列出 configuration classes 来注册DispatcherServlet变得更加容易 - 它甚至是建议设置 Spring MVC application 的方法。有关详细信息,请参阅Code-based Servlet 容器初始化。

DispatcherServlet是一个实际的Servlet(它继承自HttpServlet base class),因此在 web application 的web.xml中声明。您需要通过在同一web.xml文件中使用 URL 映射来 map 您希望DispatcherServlet处理的请求。这是标准的 Java EE Servlet configuration;以下 example 显示了这样的DispatcherServlet声明和映射:

以下是基于 code 的 example 的web.xml等价物:

<web-app>
    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>/example/*</url-pattern>
    </servlet-mapping>

</web-app>

如第 7.15 节,“ApplicationContext 的附加功能”中所述,Spring 中的ApplicationContext个实例可以作用域。在 Web MVC framework 中,每个DispatcherServlet都有自己的WebApplicationContext,它继承了已经在根WebApplicationContext中定义的所有 beans。根WebApplicationContext应包含应在其他上下文和 Servlet 实例之间共享的所有基础结构 beans。这些继承的 beans 可以在 servlet-specific 范围内重写,您可以为给定的 Servlet 实例定义新的 scope-specific beans 本地。

图 1_.Spring Web MVC中的典型 context 层次结构

在初始化DispatcherServlet时,Spring MVC 在 web application 的WEB-INF目录中查找名为[74] -servlet.xml 的文件,并创建在那里定义的 beans,覆盖 global 范围内使用相同 name 定义的任何 beans 的定义。

考虑以下DispatcherServlet Servlet configuration(在web.xml文件中):

<web-app>
    <servlet>
        <servlet-name>golfing</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>golfing</servlet-name>
        <url-pattern>/golfing/*</url-pattern>
    </servlet-mapping>
</web-app>

使用上面的 Servlet configuration,你需要在 application 中有一个名为/WEB-INF/golfing-servlet.xml的文件;此文件将包含所有 Spring Web MVC-specific 组件(beans)。您可以通过 Servlet 初始化参数更改此 configuration 文件的确切位置(有关详细信息,请参阅下文)。

对于单个 DispatcherServlet 方案,也可以只有一个根 context。

图 1_.Spring Web MVC中的单根 context **

这可以通过设置一个空的 contextConfigLocation servlet init 参数来配置,如下所示:

<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

WebApplicationContext是普通ApplicationContext的扩展,它具有 web applications 所需的一些额外 features。它与普通ApplicationContext的不同之处在于它能够解析主题(参见第 22.9 节,“使用主题”),并且它知道它与哪个 Servlet 相关联(通过链接到ServletContext)。 WebApplicationContext绑定在ServletContext中,并且通过在RequestContextUtils class 上使用静态方法,如果需要访问它,可以始终查找WebApplicationContext

请注意,我们可以使用 java-based 配置实现相同的功能:

public class GolfingWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        // GolfingAppConfig defines beans that would be in root-context.xml
        return new Class<?>[] { GolfingAppConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        // GolfingWebConfig defines beans that would be in golfing-servlet.xml
        return new Class<?>[] { GolfingWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/golfing/*" };
    }
}

22.2.1 特殊 Bean 类型在 WebApplicationContext 中

Spring DispatcherServlet使用特殊 beans 来处理请求并呈现适当的视图。这些 beans 是 Spring MVC 的一部分。您可以通过在WebApplicationContext中简单配置其中一个或多个来选择要使用的特殊 beans。但是,您最初不需要这样做,因为 Spring MVC 维护一个默认 beans 列表,如果您不配置任何。更多内容将在下一节中介绍。首先看下面的 table 列出了DispatcherServlet依赖的特殊 bean 类型。

表格 1_.WebApplicationContext 中的特殊 bean 类型

Bean 类型说明
HandlerMappingMaps 根据某些标准对传递处理程序的传入请求以及 pre-and post-processors(处理程序拦截器)列表进行处理,其详细信息因HandlerMapping implementation 而异。最流行的 implementation 支持带注释的控制器,但也存在其他 implementations。
HandlerAdapter无论实际调用哪个处理程序,都可以帮助DispatcherServlet调用映射到请求的处理程序。对于 example,调用带注释的控制器需要解析各种注释。因此HandlerAdapter的主要目的是保护DispatcherServlet免受这些细节的影响。
HandlerExceptionResolverMaps exceptions 视图也允许更复杂的 exception 处理 code。
视图解析器将逻辑 String-based 视图名称解析为实际的View类型。
LocaleResolver & LocaleContextResolver解决 locale 正在使用的 locale 以及可能的 time zone,以便能够提供国际化的视图
ThemeResolver解析 web application 可以使用的主题,例如,提供个性化布局
MultipartResolver解析 multi-part 请求 example 以支持从 HTML 表单处理文件上载。
FlashMapManager存储并检索“输入”和“输出”FlashMap,它们可用于将属性从一个请求传递到另一个请求,通常是通过重定向。

22.2.2 默认 DispatcherServlet Configuration

正如上一节中针对每个特殊 bean 所提到的,DispatcherServlet维护了一个默认使用的 implementations 列表。此信息保存在包org.springframework.web.servlet的文件DispatcherServlet.properties中。

所有特殊的 beans 都有一些合理的默认值。迟早虽然你需要定制这些 beans 提供的一个或多个 properties。对于 example,将InternalResourceViewResolver设置prefix property 配置为 view files 的 parent 位置非常常见。

无论细节如何,这里要理解的重要概念是,一旦在WebApplicationContext中配置了一个特殊的 bean,例如InternalResourceViewResolver,就可以有效地覆盖那些特殊 bean 类型本来会使用的默认__mplementation 列表。对于 example,如果配置InternalResourceViewResolver,则忽略ViewResolver __mplementations 的默认列表。

在第 22.16 节,“配置 Spring MVC”中,您将了解配置 Spring MVC 的其他选项,包括 MVC Java 配置和 MVC XML 命名空间,这两个选项都提供了一个简单的起点,并且很少了解 Spring MVC 的工作原理。无论您如何选择配置 application,本节中介绍的概念都应该对您有所帮助。

22.2.3 DispatcherServlet 处理序列

设置DispatcherServlet后,请求进入特定的DispatcherServletDispatcherServlet开始处理请求,如下所示:

  • 在请求中搜索并绑定WebApplicationContext作为控制器和 process 中的其他元素可以使用的属性。它默认绑定在 key DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE下。

  • locale 解析器绑定到请求以启用 process 中的元素来解析处理请求时使用的 locale(呈现视图,准备数据等)。如果您不需要 locale 解析,则不需要它。

  • 主题解析器绑定到请求,以允许视图等元素确定要使用的主题。如果您不使用主题,则可以忽略它。

  • 如果指定 multipart 文件解析程序,则会检查请求的多部分;如果找到多部分,请求将包装在MultipartHttpServletRequest中,以供 process 中的其他元素进一步处理。有关 multipart 处理的更多信息,请参见第 22.10 节,“ Spring 的 multipart(文件上传)支持”。

  • 搜索适当的处理程序。如果找到了处理程序,则在 order 中执行与处理程序关联的执行链(预处理程序,后处理程序和控制器)以准备 model 或呈现。

  • 如果返回 model,则呈现视图。如果没有返回 model(可能是由于预处理器或后处理器拦截请求,可能是出于安全原因),则不会呈现任何视图,因为该请求可能已经完成。

WebApplicationContext中声明的 Handler exception 解析器会拾取在处理请求期间抛出的 exceptions。使用这些 exception 解析器可以定义自定义行为以解决 exceptions。

Spring DispatcherServlet还支持 last-modification-date 的 return,由 Servlet API 指定。确定特定请求的最后修改 date 的 process 很简单:DispatcherServlet查找适当的处理程序映射并测试找到的处理程序是否实现 LastModified 接口。如果是这样,LastModified接口的long getLastModified(request)方法的 value 将返回到 client。

您可以通过将 Servlet 初始化参数(init-param元素)添加到web.xml文件中的 Servlet 声明来自定义单个DispatcherServlet实例。有关支持的参数列表,请参阅以下 table。

表格 1_.DispatcherServlet 初始化参数

参数说明
contextClass实现ConfigurableWebApplicationContext的 Class,由此 Servlet 实例化并本地配置。默认情况下,使用XmlWebApplicationContext
contextConfigLocationString 传递给 context 实例(由contextClass指定)以指示可以找到 context(s 的位置.string 可能包含多个 strings(使用逗号作为分隔符)以支持多个上下文。如果多个 context 位置的 beans 定义了两次,则最新位置优先。
namespaceWebApplicationContext的命名空间。默认为[servlet-name]-servlet

22.3 实施控制器

控制器提供对通常通过服务接口定义的 application 行为的访问。控制器解释用户输入并将其转换为由视图表示给用户的 model。 Spring 以非常抽象的方式实现控制器,使您可以创建各种控制器。

Spring 2.5 为 MVC 控制器引入了一个 annotation-based 编程 model,它使用注释,如@RequestMapping@RequestParam@ModelAttribute等。这个 annotation 支持可用于 Servlet MVC 和 Portlet MVC。以此样式实现的控制器不必扩展特定的 base classes 或实现特定的接口。此外,它们通常不直接依赖于 Servlet 或 Portlet API,尽管您可以轻松配置对 Servlet 或 Portlet 工具的访问。

在spring-projects 在 Github 上的组织中可用,许多 web applications 利用本节中描述的 annotation 支持,包括 MvcShowcase,MvcAjax,MvcBasic,PetClinic,PetCare 等。

@Controller
public class HelloWorldController {

    @RequestMapping("/helloWorld")
    public String helloWorld(Model model) {
        model.addAttribute("message", "Hello World!");
        return "helloWorld";
    }
}

如您所见,@Controller@RequestMapping 注释允许灵活的方法名称和签名。在此特定的 example 中,方法接受Model并将 view name 作为String返回,但可以使用各种其他方法参数和 return 值,如本节后面所述。 @Controller@RequestMapping以及许多其他注释构成了 Spring MVC implementation 的基础。本节介绍了这些注释以及它们在 Servlet 环境中最常用的方式。

22.3.1 用 @Controller 定义控制器

@Controller annotation 表示特定的 class 用作控制器的角色。 Spring 不要求您扩展任何控制器 base class 或 reference Servlet API。但是,如果需要,您仍然可以 reference Servlet-specific features。

@Controller annotation 充当带注释的 class 的构造型,表示其角色。调度程序扫描这些带注释的 classes 以查找映射方法并检测@RequestMapping 注释(请参阅下一节)。

您可以使用调度程序的 context 中的标准 Spring bean 定义显式定义带注释的控制器 beans。但是,@Controller构造型还允许自动检测,与 Spring 通用支持一致,用于检测 classpath 中的 component classes 和 auto-registering bean 定义。

要启用此类带注释控制器的自动检测,请将 component 扫描添加到 configuration。使用 spring-context schema,如以下 XML 片段所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.springframework.samples.petclinic.web"/>

    <!-- ... -->

</beans>

22.3.2 使用 @RequestMapping 映射请求

您可以使用@RequestMapping annotation 将 map 等 map 映射到整个 class 或特定的处理程序方法。通常,class-level annotation maps 将特定请求路径(或路径 pattern)映射到表单控制器上,并使用额外的 method-level _notnotations 缩小特定 HTTP 方法请求方法(“GET”,“POST”,etc.)或 HTTP 请求参数条件)的主映射。

Petcare sample 中的以下 example 显示了使用此 annotation 的 Spring MVC application 中的控制器:

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @RequestMapping(method = RequestMethod.GET)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @RequestMapping(path = "/{day}", method = RequestMethod.GET)
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @RequestMapping(path = "/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

在上面的例子中,@RequestMapping用于许多地方。第一种用法是在类型(class)level 上,它表示此控制器中的所有处理程序方法都与/appointments路径相关。 get()方法还有一个@RequestMapping细化:它只接受GET个请求,这意味着的 HTTP GET会调用此方法。 add()具有类似的细化,getNewForm()将 HTTP 方法和路径的定义合并为一个,因此appointments/newGET请求由该方法处理。

getForDay()方法显示了@RequestMapping:URI 模板的另一种用法。 (见“URI 模板模式”一节)。

class level 上的@RequestMapping不是必需的。没有它,所有 paths 都是绝对的,而不是相对的。 PetClinic sample application 中的以下 example 显示使用@RequestMapping的 multi-action 控制器:

@Controller
public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
        this.clinic = clinic;
    }

    @RequestMapping("/")
    public void welcomeHandler() {
    }

    @RequestMapping("/vets")
    public ModelMap vetsHandler() {
        return new ModelMap(this.clinic.getVets());
    }

}

上面的 example 没有指定GETPUTPOST等,因为默认情况下@RequestMapping maps 所有 HTTP 方法。使用@RequestMapping(method=GET)@GetMapping缩小映射范围。

组成 @RequestMapping 变体

Spring Framework 4.3 引入了以下 method-level 组成的@RequestMapping annotation 变体,它们有助于简化 common HTTP 方法的映射,更好地表达带注释的处理程序方法的语义。例如,@GetMapping可以读作GET @RequestMapping

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

下面的 example 显示了上一节中AppointmentsController的修改后的 version,它已经用组合的@RequestMapping 注释进行了简化。

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @GetMapping
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @GetMapping("/{day}")
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @GetMapping("/new")
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @PostMapping
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

@Controller 和 AOP 代理

在某些情况下,控制器可能需要在运行时使用 AOP 代理进行修饰。一个示例是您选择直接在控制器上使用@Transactional annotations。在这种情况下,对于控制器,我们建议使用 class-based 代理。这通常是控制器的默认选择。但是,如果控制器必须实现不是 Spring Context 回调(e.g. InitializingBean*Aware等)的接口,则可能需要显式配置 class-based 代理。对于带有<tx:annotation-driven/>的 example,请更改为<tx:annotation-driven proxy-target-class="true"/>

Spring MVC 中的 @RequestMapping 方法的新支持 Classes 3.1

Spring 3.1 为@RequestMapping方法引入了一组新的支持 classes,分别称为RequestMappingHandlerMappingRequestMappingHandlerAdapter。建议使用它们,甚至需要利用 Spring MVC 3.1 中的新 features 并继续使用。 MVC 命名空间和 MVC Java 配置默认启用新的支持 classes,但如果两者都不使用,则必须明确配置。本节介绍旧支持 classes 和新支持 classes 之间的一些重要区别。

在 Spring 3.1 之前,在两个单独的阶段中检查了类型和 method-level 请求映射 - 首先由DefaultAnnotationHandlerMapping选择控制器,然后通过AnnotationMethodHandlerAdapter缩小调用的实际方法。

使用 Spring 3.1 中的新支持 classes,RequestMappingHandlerMapping是唯一决定应该处理请求的方法的地方。将控制器方法视为唯一 endpoints 的集合,其中包含从类型和 method-level @RequestMapping信息派生的每个方法的映射。

这实现了一些新的可能性。一旦HandlerInterceptorHandlerExceptionResolver现在可以期望 Object-based 处理程序是HandlerMethod,这允许它们检查确切的方法,其参数和相关的注释。不再需要跨不同的控制器分割 URL 的处理。

还有一些事情不再可能:

  • 首先使用SimpleUrlHandlerMappingBeanNameUrlHandlerMapping选择控制器,然后根据@RequestMapping 注释缩小方法。

  • 依赖于方法名称作为 fall-back 机制来消除两个@RequestMapping方法之间的歧义,这两个方法没有明确的路径映射 URL 路径,否则 match 同等,e.g. 通过 HTTP 方法。在新的支持 classes @RequestMapping方法必须唯一映射。

  • 如果没有其他控制器方法更具体地匹配,则使用单个默认方法(没有显式路径映射)处理请求。在新的支持 classes 中,如果找不到匹配的方法,则会引发 404 错误。

现有的支持 classes 仍支持上述 features。但是,要利用新的 Spring MVC 3.1 features,您需要使用新的支持 classes。

URI 模板模式

URI 模板可用于在@RequestMapping方法中方便地访问 URL 的选定部分。

URI 模板是 URI-like string,包含一个或多个变量名称。当您为这些变量替换值时,模板将成为 URI。 URI 模板的提出的 RFC定义了 URI 的参数化方式。例如,URI 模板http://www.example.com/users/{userId}包含变量 userId。将 value fred 分配给变量会产生http://www.example.com/users/fred

在 Spring MVC 中,您可以在方法参数上使用@PathVariable annotation 将其绑定到 URI 模板变量的 value:

@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    model.addAttribute("owner", owner);
    return "displayOwner";
}

URI 模板“/owners/{ownerId}”指定变量 name ownerId。当控制器处理此请求时,ownerId的 value 设置为 URI 的相应部分中找到的 value。例如,当/owners/fred的请求进入时,ownerId的 value 为fred

要处理 @PathVariable annotation,Spring MVC 需要通过 name 找到匹配的 URI 模板变量。您可以在 annotation 中指定它:

@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
    // implementation omitted
}

或者,如果 URI 模板变量 name 与方法参数 name 匹配,则可以省略该详细信息。如果您的 code 是使用调试信息或 Java8 上的-parameters编译器 flag 编译的,Spring MVC 将方法参数 name 匹配到 URI 模板变量 name:

@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
    // implementation omitted
}

一个方法可以有任意数量的@PathVariable 注释:

@GetMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    Pet pet = owner.getPet(petId);
    model.addAttribute("pet", pet);
    return "displayPet";
}

Map<String, String>参数上使用@PathVariable annotation 时,map 将填充所有 URI 模板变量。

可以从类型和方法 level @RequestMapping annotations 组装 URI 模板。因此,可以使用诸如/owners/42/pets/21之类的 URL 调用findPet()方法。

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @RequestMapping("/pets/{petId}")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

@PathVariable参数可以是任何简单类型,例如intlongDate等.Spring 会自动转换为适当的类型,如果不能这样做则抛出TypeMismatchException。您还可以注册支持解析其他数据类型。见“方法参数和类型转换”一节和“自定义 WebDataBinder 初始化”一节。

具有正则表达式的 URI 模板模式

有时您需要更精确地定义 URI 模板变量。考虑 URL "/spring-web/spring-web-3.0.5.jar"。你怎么把它分成多个部分?

@RequestMapping annotation 支持在 URI 模板变量中使用正则表达式。语法是{varName:regex},其中第一部分定义变量 name,第二部分定义正则表达式。例如:

@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String extension) {
    // ...
}

路径模式

除 URI 模板外,@RequestMapping annotation 和所有组合的@RequestMapping变体也支持 Ant-style 路径模式(对于 example,/myPath/*.do)。还支持 URI 模板变量和 Ant-style globs 的组合(e.g ./owners/*/pets/{petId})。

Path Pattern Comparison

当 URL 匹配多个模式时,将使用排序来查找最具体的 match。

具有较低 URI 变量和通配符计数的 pattern 被认为更具体。对于 example /hotels/{hotel}/*有 1 个 URI 变量和 1 个通配符,被认为比/hotels/{hotel}/**更具体,它是 1 个 URI 变量和 2 个通配符。

如果两个模式具有相同的计数,则更长的模式被认为更具体。对于 example /foo/bar*更长,并且被认为比/foo/*更具体。

当两个模式具有相同的计数和长度时,具有较少通配符的 pattern 被认为更具体。对于 example /hotels/{hotel}/hotels/*更具体。

还有一些额外的特殊规则:

  • 默认映射 pattern /**的特定性不如任何其他 pattern。对于 example /api/{a}/{b}/{c}更具体。

  • 前缀 pattern(如/public/**)的特定性不如任何其他不包含 double 通配符的 pattern。对于 example /public/path3/{a}/{b}/{c}更具体。

有关完整的详细信息,请参阅AntPathMatcher中的AntPatternComparator。请注意,可以自定义 PathMatcher(请参阅有关配置 Spring MVC 的部分中的第 22.16.11 节,“路径匹配”)。

带占位符的路径模式

@RequestMapping annotations 中的模式支持针对本地 properties and/or system properties 和环境变量的${…}占位符。在控制器映射到的路径可能需要通过 configuration 自定义的情况下,这可能很有用。有关占位符的更多信息,请参阅PropertyPlaceholderConfigurer class 的 javadoc。

后缀 Pattern 匹配

默认情况下 Spring MVC 执行".*"后缀 pattern 匹配,以便映射到/person的控制器也隐式映射到/person.*。这样可以通过 URL 路径(e.g. /person.pdf/person.xml)轻松请求资源的不同表示形式。

后缀 pattern 匹配可以关闭或限制为为内容 negotiation 目的明确注册的一组路径 extensions。通常建议使用 common 请求映射(例如/person/{id},其中点可能不表示文件扩展名,e.g)来最小化歧义。 /person/[email protected] vs /person/[email protected]。此外,如下面的注释所述,后缀 pattern 匹配以及内容 negotiation 可能在某些情况下用于尝试恶意攻击,并且有充分的理由对其进行有意义的限制。

对于后缀 pattern matching configuration,请参见第 22.16.11 节,“路径匹配”,对于内容 negotiation configuration,请参见第 22.16.6 节,“内容谈判”。

后缀 Pattern 匹配和 RFD

反射文件下载(RFD)攻击最初是在 2014 年Trustwave 的论文中描述的。该攻击类似于 XSS,因为它依赖于响应中反映的输入(e.g. 查询参数,URI 变量)。然而,RFD 攻击依赖于浏览器切换来执行下载,并将响应视为可执行脚本(如果 double-clicked 基于文件扩展名(e.g. .bat,.cmd)),而不是将 JavaScript 插入 HTML。

在 Spring MVC @ResponseBodyResponseEntity方法存在风险,因为它们可以呈现 clients 可以通过 URL 路径 extensions 请求的不同内容类型。但请注意,既不禁用后缀 pattern 匹配也不禁用 path extensions 仅用于内容协商目的,这对于防止 RFD 攻击都是有效的。

为了全面防范 RFD,在呈现响应主体之前 Spring MVC 添加Content-Disposition:inline;filename=f.txt标头以建议固定且安全的下载文件文件名。仅当 URL 路径包含既未列入白名单也未明确注册用于内容协商目的的文件扩展名时,才会执行此操作。但是,当直接在浏览器中输入 URL 时,它可能会产生副作用。

默认情况下,许多 common path extensions 都列入白名单。此外,REST API calls 通常不能直接在浏览器中用作 URL。尽管如此,使用自定义HttpMessageConverter __mplement 的应用程序可以显式地为内容 negotiation 注册文件 extensions,并且不会为这样的 extensions 添加 Content-Disposition 标头。见第 22.16.6 节,“内容谈判”。

这最初是作为CVE-2015-5211工作的一部分而引入的。以下是报告中的其他建议:

  • 编码而不是逃避 JSON 响应。这也是 OWASP XSS 的推荐。有关如何使用 Spring 执行此操作的示例,请参阅spring-jackson-owasp。

  • 将后缀 pattern 匹配配置为仅关闭或仅限于显式注册的后缀。

  • 配置内容 negotiation,将 properties“useJaf”和“ignoreUnknownPathExtensions”设置为 false,这将导致对具有未知 extensions 的 URL 的 406 响应。但是请注意,如果 URL 自然希望在末尾有一个点,则可能无法选择此选项。

  • X-Content-Type-Options: nosniff标头添加到响应中。 Spring Security 4 默认执行此操作。

矩阵变量

URI 规范RFC 3986定义了在路径段中包含 name-value 对的可能性。规范中没有使用特定术语。可以应用一般的“URI 路径参数”,虽然源自 Tim Berners-Lee 的旧帖子的更独特的“矩阵 URI”也经常被使用并且是众所周知的。在 Spring MVC 中,这些被称为矩阵变量。

矩阵变量可以出现在任何路径段中,每个矩阵变量用“;”分隔(分号)。对于 example:"/cars;color=red;year=2012"。多个值可以是“,”(逗号)分隔"color=red,green,blue"或变量 name 可以重复"color=red;color=green;color=blue"

如果 URL 预计包含矩阵变量,则请求映射 pattern 必须使用 URI 模板表示它们。这确保了请求可以正确匹配,无论是否存在矩阵变量以及它们提供的顺序。

下面是提取矩阵变量“q”的示例:

// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11

}

由于所有路径段都可能包含矩阵变量,因此在某些情况下,您需要更具体地确定变量的预期位置:

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22

}

矩阵变量可以定义为可选,并指定默认 value:

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1

}

所有矩阵变量都可以在 Map 中获得:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 11, "s" : 23]

}

请注意,要启用矩阵变量,必须将RequestMappingHandlerMappingremoveSemicolonContent property 设置为false。默认情况下,它设置为true

MVC Java 配置和 MVC 命名空间都提供了启用矩阵变量使用的选项。

如果您使用的是 Java 配置,则使用 MVC Java 配置进行高级自定义部分描述了如何自定义RequestMappingHandlerMapping

在 MVC 名称空间中,<mvc:annotation-driven>元素具有应设置为trueenable-matrix-variables属性。默认情况下,它设置为false

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven enable-matrix-variables="true"/>

</beans>

耗材介质类型

您可以通过指定可使用的介质类型列表来缩小主映射。仅当Content-Type请求标头与指定的媒体类型匹配时,才会匹配请求。例如:

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // implementation omitted
}

消耗媒体类型表达式也可以被否定,如!text/plain到匹配除text/plain text/plain之外的所有请求。还要考虑使用MediaType中提供的常量,例如APPLICATION_JSON_VALUEAPPLICATION_JSON_UTF8_VALUE

类型和方法 level 上支持消耗条件。与大多数其他条件不同,当在 level 类型中使用时,method-level 消耗类型会覆盖而不是扩展 type-level 消耗类型。

可生产的媒体类型

您可以通过指定可生成的媒体类型列表来缩小主映射。仅当Accept请求标头与其中一个值匹配时,才会匹配请求。此外,使用生成条件可确保用于生成响应的实际 content type 遵循生成条件中指定的媒体类型。例如:

@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
    // implementation omitted
}

请注意,生成条件中指定的介质类型也可以选择指定字符集。对于 example,在上面的 code 片段中,我们指定的媒体类型与MappingJackson2HttpMessageConverter中配置的默认媒体类型相同,包括UTF-8字符集。

就像使用消耗一样,可生成的媒体类型表达式可以被否定,如!text/plain到匹配除text/plain标题 value 为text/plain的请求之外的所有请求。还要考虑使用MediaType中提供的常量,例如APPLICATION_JSON_VALUEAPPLICATION_JSON_UTF8_VALUE

类型和方法 level 上支持生成条件。与大多数其他条件不同,当在 level 类型中使用时,method-level 可生成类型会覆盖而不是扩展 type-level 可生成类型。

请求参数和标题值

您可以通过请求参数条件(例如"myParam""!myParam""myParam=myValue")来缩小请求匹配。前两个测试请求参数 presence/absence,第三个测试特定参数 value。这是一个带有请求参数 value 条件的 example:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

可以根据特定的请求头 value 来测试请求头 presence/absence 或 match:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @GetMapping(path = "/pets", headers = "myHeader=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

虽然您可以使用媒体类型通配符匹配 Content-Type 并接受标题值(对于 example“content-type=text/ *”将匹配到“text/plain”和“text/html”),但建议分别使用消耗和生成条件。它们专门用于此目的。

HTTP HEAD 和 HTTP OPTIONS

映射到“GET”的@RequestMapping方法也隐式映射到“HEAD”,i.e。没有必要明确声明“HEAD”。处理 HTTP HEAD 请求就像它是 HTTP GET 一样,除了不写入主体,只计算字节数并设置“Content-Length”标头。

@RequestMapping方法对 HTTP OPTIONS 有 built-in 支持。默认情况下,通过将“Allow”响应标头设置为在具有匹配 URL 模式的所有@RequestMapping方法上显式声明的 HTTP 方法来处理 HTTP OPTIONS 请求。当没有显式声明 HTTP 方法时,“Allow”标头设置为“GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS”。理想情况下,始终声明方法要处理的 HTTP method(s),或者使用专用的@RequestMapping变体之一(请参阅名为“Composed @RequestMapping Variants”的部分)。

虽然没有必要,但@RequestMapping方法可以映射到并处理 HTTP HEAD 或 HTTP OPTIONS,或两者兼而有之。

22.3.3 定义 @RequestMapping 处理程序方法

@RequestMapping处理程序方法可以具有非常灵活的签名。支持的方法 arguments 和 return 值将在下一节中介绍。大多数 arguments 可以在任意 order 中使用,只有 exception 是BindingResult arguments。这将在下一节中介绍。

Spring 3.1 为@RequestMapping方法引入了一组新的支持 classes,分别称为RequestMappingHandlerMappingRequestMappingHandlerAdapter。建议使用它们,甚至需要利用 Spring MVC 3.1 中的新 features 并继续使用。默认情况下,从 MVC 命名空间启用新的支持 classes,并使用 MVC Java 配置,但如果不同时使用,则必须明确配置。

支持的方法参数类型

以下是支持的方法 arguments:

  • 请求或响应 objects(Servlet API)。为 example ServletRequestHttpServletRequest选择任何特定请求或响应类型。

  • Session object(Servlet API):类型为HttpSession。这种类型的参数强制存在相应的 session。因此,这样的论证永远不会null

Session 访问可能不是 thread-safe,特别是在 Servlet 环境中。如果允许多个请求同时访问 session,请考虑将RequestMappingHandlerAdapter的“synchronizeOnSession”flag 设置为“true”。

  • org.springframework.web.context.request.WebRequestorg.springframework.web.context.request.NativeWebRequest。允许通用请求参数访问以及 request/session 属性访问,而不与原生 Servlet/Portlet API 绑定。

  • java.util.Locale表示当前请求 locale,由最可用的 locale 解析器确定,实际上是 MVC 环境中配置的LocaleResolver/LocaleContextResolver

  • java.util.TimeZone(Java 6)/ java.time.ZoneId(在 Java 8 上)与当前请求关联的 time zone,由LocaleContextResolver确定。

  • java.io.InputStream/java.io.Reader用于访问请求的内容。此 value 是 Servlet API 公开的原始 InputStream/Reader。

  • java.io.OutputStream/java.io.Writer用于生成响应的内容。此 value 是 Servlet API 公开的原始 OutputStream/Writer。

  • org.springframework.http.HttpMethod表示 HTTP 请求方法。

  • java.security.Principal包含当前经过身份验证的用户。

  • @PathVariable用于访问 URI 模板变量的注释参数。见“URI 模板模式”一节。

  • @MatrixVariable带注释的参数,用于访问位于 URI 路径段中的 name-value 对。见名为“矩阵变量”的部分。

  • @RequestParam用于访问特定 Servlet 请求参数的注释参数。参数值将转换为声明的方法参数类型。见“Binding 请求参数与@RequestParam 的方法参数”一节。

  • @RequestHeader带注释的参数,用于访问特定的 Servlet 请求 HTTP headers。参数值将转换为声明的方法参数类型。见“使用 @RequestHeader annotation 映射请求标头属性”一节。

  • @RequestBody用于访问 HTTP 请求正文的注释参数。使用HttpMessageConverter s 将参数值转换为声明的方法参数类型。见“使用 @RequestBody annotation 映射请求正文”一节。

  • @RequestPart带注释的参数,用于访问“multipart/form-data”请求部分的内容。见部分 22.10.5,“处理来自程序化客户端的文件上载请求”和第 22.10 节,“ Spring 的 multipart(文件上传)支持”。

  • @SessionAttribute用于访问现有的永久 session 属性的注释参数(e.g. 用户身份验证 object),而不是通过@SessionAttributes临时存储在 session 中作为控制器工作流一部分的 model 属性。

  • @RequestAttribute用于访问请求属性的注释参数。

  • HttpEntity<?>用于访问 Servlet 请求 HTTP headers 和内容的参数。请求流将使用HttpMessageConverter s 转换为实体主体。见名为“使用 HttpEntity”的部分。

  • java.util.Map/org.springframework.ui.Model/org.springframework.ui.ModelMap用于丰富暴露于 web 视图的隐式 model。

  • org.springframework.web.servlet.mvc.support.RedirectAttributes指定在重定向的情况下要使用的确切属性集,并且还要添加 flash 属性(临时存储在 server-side 上的属性,以使其在重定向后可用于请求)。见“将数据传递给重定向目标”一节和第 22.6 节,“使用 flash 属性”。

  • 命令或表单 objects 将请求参数绑定到 bean properties(通过 setter)或直接绑定到字段,具有可自定义的类型转换,具体取决于@InitBinder方法 and/or HandlerAdapter configuration。请参阅RequestMappingHandlerAdapter上的webBindingInitializer property。默认情况下,使用命令 class name - e.g,此类命令 object 及其验证结果将作为 model 属性公开。 model 属性“orderAddress”用于类型为“some.package.OrderAddress”的命令 object。可以在方法参数上使用ModelAttribute annotation 来自定义使用的 model 属性 name。

  • org.springframework.validation.Errors/org.springframework.validation.BindingResult前面命令或表单 object(前一个方法参数)的验证结果。

  • org.springframework.web.bind.support.SessionStatus status 句柄,用于将表单处理标记为完成,触发清除_se属性,这些属性已由处理程序类型 level 上的@SessionAttributes annotation 指示。

  • org.springframework.web.util.UriComponentsBuilder用于准备相对于当前请求的 host, port,scheme,context 路径和 servlet 映射的文字部分的 URL 的构建器。

ErrorsBindingResult参数必须遵循立即绑定的 model object,因为方法签名可能有多个 model object,Spring 将为每个参数创建一个单独的BindingResult实例,因此以下 sample 将不起作用:

BindingResult 和@ModelAttribute 的无效 ordering.

@PostMapping
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }

注意,PetBindingResult之间有一个Model参数。要使其正常工作,您必须按如下方式重新排序参数:

@PostMapping
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }

支持 JDK 1.8 的java.util.Optional作为方法参数类型,其中 annotations 具有required属性(e.g. @RequestParam@RequestHeader等。在这些情况下使用java.util.Optional等同于required=false

支持的方法 return 类型

以下是支持的 return 类型:

  • 一个ModelAndView object,model 隐式丰富了命令 objects 和@ModelAttribute注释 reference 数据访问器方法的结果。

  • 一个Model object,视图 name 通过RequestToViewNameTranslator隐式确定,model 隐式地使用命令 objects 和@ModelAttribute注释 reference 数据访问器方法的结果。

  • 一个Map object 用于公开 model,其中 view name 通过RequestToViewNameTranslator隐式确定,而 model 隐式丰富了命令 objects 和@ModelAttribute annotated reference 数据访问器方法的结果。

  • 一个View object,model 通过命令 objects 和@ModelAttribute annotated reference 数据访问器方法隐式确定。处理程序方法还可以通过声明Model参数以编程方式丰富 model(参见上文)。

  • value 被解释为逻辑视图 name,model 通过命令 objects 和@ModelAttribute annotated reference 数据访问器方法隐式确定。处理程序方法还可以通过声明Model参数以编程方式丰富 model(参见上文)。

  • void如果方法处理响应本身(通过直接写响应内容,为此目的声明类型ServletResponse/HttpServletResponse的参数)或者如果视图 name 应该通过RequestToViewNameTranslator隐式确定(不在声明响应参数中)处理程序方法签名)。

  • 如果使用@ResponseBody注释该方法,则 return 类型将写入响应 HTTP 正文。 return value 将使用HttpMessageConverter s 转换为声明的方法参数类型。见“使用 @ResponseBody annotation 映射响应正文”一节。

  • HttpEntity<?>ResponseEntity<?> object 提供对 Servlet 响应 HTTP headers 和内容的访问。实体主体将使用HttpMessageConverter s 转换为响应流。见名为“使用 HttpEntity”的部分。

  • 一个HttpHeaders object 来_没有身体的回复。

  • 当 application 想要在 Spring MVC 管理的线程中异步生成 return value 时,可以返回Callable<?>

  • 当 application 想要从它自己选择的线程产生 return value 时,可以返回DeferredResult<?>

  • 当 application 想要从线程池提交中生成 value 时,可以返回ListenableFuture<?>CompletableFuture<?>/CompletionStage<?>

  • 可以返回ResponseBodyEmitter以异步地将多个 objects 写入响应;也支持作为ResponseEntity内的身体。

  • 可以返回SseEmitter以异步方式将 Server-Sent Events 写入响应;也支持作为ResponseEntity内的身体。

  • 可以返回StreamingResponseBody以异步写入响应 OutputStream;也支持作为ResponseEntity内的身体。

  • 任何其他 return 类型都被视为要向视图公开的单个 model 属性,使用方法 level 上的@ModelAttribute指定的属性 name(或基于 return 类型 class name 的默认属性 name)。 model 隐含地使用命令 objects 和@ModelAttribute annotated reference 数据访问器方法的结果进行了丰富。

Binding 请求参数与方法参数 @RequestParam

使用@RequestParam annotation 将请求参数绑定到控制器中的方法参数。

以下 code 代码段显示了用法:

@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

默认情况下,使用此 annotation 的参数是必需的,但您可以通过将@RequestParamrequired属性设置为false(e.g. ,@RequestParam(name="id", required=false))来指定参数是可选的。

如果目标方法参数类型不是String,则会自动应用类型转换。见“方法参数和类型转换”一节。

Map<String, String>MultiValueMap<String, String>参数上使用@RequestParam annotation 时,map 将填充所有请求参数。

使用 @RequestBody annotation 映射请求正文

@RequestBody方法参数 annotation 指示应将方法参数绑定到 HTTP 请求正文的 value。例如:

@PutMapping("/something")
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}

您可以使用HttpMessageConverter将请求主体转换为方法参数。 HttpMessageConverter负责从 HTTP 请求消息转换为 object 并从 object 转换为 HTTP 响应正文。 RequestMappingHandlerAdapter支持@RequestBody annotation,其默认值为HttpMessageConverters

  • ByteArrayHttpMessageConverter转换字节数组。

  • StringHttpMessageConverter转换 strings。

  • FormHttpMessageConverter转换表单数据 to/from MultiValueMap <String, String>。

  • SourceHttpMessageConverter将 to/from 转换为 javax.xml.transform.Source。

有关这些转换器的更多信息,请参阅消息转换器。另请注意,如果使用 MVC 命名空间或 MVC Java 配置,则默认情况下会注册更多范围的消息转换器。有关更多信息,请参见部分 22.16.1,“启用 MVC Java 配置或 MVC XML 命名空间”。

如果您打算读写 XML,则需要使用org.springframework.oxm包中的特定MarshallerUnmarshaller implementation 配置MarshallingHttpMessageConverter。下面的 example 显示了如何直接在 configuration 中执行此操作,但如果您的 application 是通过 MVC 命名空间配置的,或者 MVC Java 配置请参见部分 22.16.1,“启用 MVC Java 配置或 MVC XML 命名空间”。

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <util:list id="beanList">
            <ref bean="stringHttpMessageConverter"/>
            <ref bean="marshallingHttpMessageConverter"/>
        </util:list>
    </property
</bean>

<bean id="stringHttpMessageConverter"
        class="org.springframework.http.converter.StringHttpMessageConverter"/>

<bean id="marshallingHttpMessageConverter"
        class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
    <property name="marshaller" ref="castorMarshaller"/>
    <property name="unmarshaller" ref="castorMarshaller"/>
</bean>

<bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>

可以使用@Valid注释@RequestBody方法参数,在这种情况下,将使用配置的Validator实例对其进行验证。使用 MVC 命名空间或 MVC Java 配置时,如果 classpath 上有 JSR-303 implementation,则会自动配置 JSR-303 验证器。

@ModelAttribute参数一样,Errors参数可用于检查错误。如果未声明此类参数,则会引发MethodArgumentNotValidException。 _ex在DefaultHandlerExceptionResolver中处理,它将400错误发送回 client。

有关通过 MVC 命名空间或 MVC Java 配置配置消息转换器和验证器的信息,另请参阅部分 22.16.1,“启用 MVC Java 配置或 MVC XML 命名空间”。

使用 @ResponseBody annotation 映射响应正文

@ResponseBody annotation 类似于@RequestBody。此 annotation 可以放在方法上,并指示 return 类型应直接写入 HTTP 响应主体(而不是放在 Model 中,或解释为视图 name)。例如:

@GetMapping("/something")
@ResponseBody
public String helloWorld() {
    return "Hello World";
}

上面的 example 将导致文本Hello World被写入 HTTP 响应流。

@RequestBody一样,Spring 使用HttpMessageConverter将返回的 object 转换为响应正文。有关这些转换器的更多信息,请参阅上一节和消息转换器。

使用 @RestController annotation 创建 REST 控制器

控制器实现 REST API 是一个非常常见的用例,因此只提供 JSON,XML 或自定义 MediaType 内容。为方便起见,您可以使用@RestController注释控制器 Class,而不是使用@ResponseBody注释所有@RequestMapping方法。

@RestController是一个结合了@ResponseBody@Controller的构造型注释。更重要的是,它为您的 Controller 提供了更多的意义,并且可能在 framework 的未来版本中带来额外的语义。

与常规@Controller s 一样,@RestController@RestControllerAdvice beans 可以辅助@RestController。有关详细信息,请参阅“使用 @ControllerAdvice 和@RestControllerAdvice 建议控制器”一节部分。

使用 HttpEntity

HttpEntity类似于@RequestBody@ResponseBody。除了访问请求和响应主体之外,HttpEntity(和 response-specific 子类ResponseEntity)还允许访问请求和响应 headers,如下所示:

@RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
    String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader"));
    byte[] requestBody = requestEntity.getBody();

    // do something with request header and body

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("MyResponseHeader", "MyValue");
    return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}

上面的 example 获取MyRequestHeader请求标头的 value,并将主体读取为 byte array。它将MyResponseHeader添加到响应中,将Hello World写入响应流,并将响应状态 code 设置为 201(创建)。

@RequestBody@ResponseBody一样,Spring 使用HttpMessageConverter来转换请求和响应流。有关这些转换器的更多信息,请参阅上一节和消息转换器。

在方法上使用 @ModelAttribute

@ModelAttribute annotation 可用于方法或方法 arguments。本节介绍了它在方法上的用法,下一节解释了它在方法 arguments 上的用法。

方法上的@ModelAttribute表示该方法的目的是添加一个或多个 model 属性。此类方法支持与@RequestMapping方法相同的参数类型,但不能直接映射到请求。而是在@RequestMapping方法之前,在同一个控制器中调用控制器中的@ModelAttribute方法。几个例子:

// Add one attribute
// The return value of the method is added to the model under the name "account"
// You can customize the name via @ModelAttribute("myAccount")

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountManager.findAccount(number);
}

// Add multiple attributes

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountManager.findAccount(number));
    // add more ...
}

@ModelAttribute方法用于使用 example 的常用属性填充 model 以使用状态或宠物类型填充 drop-down,或者在 order 中检索命令 object,例如 Account,以使用它来表示 HTML 表单上的数据。后一种情况将在下一节进一步讨论。

请注意两种样式的@ModelAttribute方法。在第一个中,该方法通过返回隐式添加属性。在第二种方法中,该方法接受Model并向其添加任意数量的 model 属性。您可以根据需要在两种样式之间进行选择。

控制器可以有任意数量的@ModelAttribute方法。在同一控制器的@RequestMapping方法之前调用所有这些方法。

@ModelAttribute方法也可以在@ControllerAdvice -annotated class 中定义,这些方法适用于许多控制器。有关详细信息,请参阅“使用 @ControllerAdvice 和@RestControllerAdvice 建议控制器”一节部分。

如果未明确指定 model 属性 name,会发生什么?在这种情况下,会根据其类型为 model 属性分配默认 name。对于 example,如果方法返回Account类型的 object,则使用的默认 name 是“account”。您可以通过@ModelAttribute annotation 的 value 更改它。如果直接向Model添加属性,请使用适当的重载addAttribute(..)方法 - i.e。,带或不带属性 name。

@ModelAttribute annotation 也可用于@RequestMapping方法。在这种情况下,@RequestMapping方法的 return value 被解释为 model 属性而不是视图 name。然后,视图 name 基于 view name 约定派生,就像返回void的方法一样 - 请参阅第 22.13.3 节,“默认视图 name”。

在方法参数上使用 @ModelAttribute

如前一节所述,@ModelAttribute可用于方法或方法 arguments。本节介绍其在方法 arguments 上的用法。

方法参数上的@ModelAttribute表示应该从 model 检索参数。如果 model 中不存在,则应首先实例化参数,然后将其添加到 model。一旦出现在 model 中,参数的字段应该从具有匹配名称的所有请求参数中填充。这在 Spring MVC 中称为 data binding,这是一种非常有用的机制,可以使您不必单独解析每个表单字段。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }

鉴于上面的例子,Pet 实例可以从哪里来?有几种选择:

  • 由于使用@SessionAttributes,它可能已经在 model 中 - 请参阅名为“在请求之间的 HTTP session 中使用 @SessionAttributes 到 store model 属性”一节。

  • 由于同一控制器中的@ModelAttribute方法,它可能已经在 model 中 - 如上一节所述。

  • 可以基于 URI 模板变量和类型转换器来检索它(下面更详细地解释)。

  • 它可以使用其默认构造函数进行实例化。

@ModelAttribute方法是一种从数据库中检索属性的通用方法,可以选择通过使用@SessionAttributes在请求之间存储。在某些情况下,使用 URI 模板变量和类型转换器检索属性可能很方便。这是一个 example:

@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
    // ...
}

在此 example 中,model 属性的 name(i.e.“account”)与 URI 模板变量的 name 匹配。如果您注册Converter<String, Account>可以将String帐户 value 转换为Account实例,那么上述 example 将无需@ModelAttribute方法即可运行。

下一个 step 是数据 binding。 WebDataBinder class 通过 name 将请求参数名称(包括查询 string 参数和表单字段)与 model 属性字段进行匹配。在必要时应用类型转换(从 String 到目标字段类型)后填充匹配字段。数据 binding 和验证包含在第 9 章,验证,数据绑定和类型转换中。自定义控制器 level 的数据 binding process 包含在“自定义 WebDataBinder 初始化”一节中。

由于数据绑定,可能会出现错误,例如缺少必填字段或类型转换错误。要检查此类错误,请在@ModelAttribute参数后面立即添加BindingResult参数:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

使用BindingResult,您可以检查是否发现错误,在这种情况下,common 呈现相同的表单,其中可以在 Spring 的<errors>表单标记的帮助下显示错误。

请注意,在某些情况下,在没有数据 binding 的情况下访问 model 中的属性可能很有用。对于这种情况,您可以_将Model注入控制器,或者在 annotation 上使用binding flag:

@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}

@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) {

    // ...
}

除了数据 binding 之外,您还可以使用自己的自定义验证程序调用验证,该验证程序传递用于 record data binding 错误的相同BindingResult。这允许数据绑定和验证错误在一个地方累积,然后报告给用户:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    new PetValidator().validate(pet, result);
    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

或者,您可以通过添加 JSR-303 @Valid annotation 自动调用验证:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

有关如何配置和使用验证的详细信息,请参阅第 9.8 节,“ Spring 验证”和第 9 章,验证,数据绑定和类型转换。

在请求之间的 HTTP session 中使用 @SessionAttributes 来 store model 属性

type-level @SessionAttributes annotation 声明特定处理程序使用的 session 属性。这通常会列出 model 属性的名称或 model 属性的类型,这些属性应该透明地存储在 session 或某些会话存储中,在后续请求之间充当 form-backing beans。

以下 code 片段显示了此 annotation 的用法,指定了 model 属性 name:

@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

使用 @SessionAttribute 访问 pre-existing global session 属性

如果需要访问全局管理的 pre-existing session 属性,i.e。在控制器外部(e.g. 通过过滤器),可能存在或不存在使用方法参数上的@SessionAttribute annotation:

@RequestMapping("/")
public String handle(@SessionAttribute User user) {
    // ...
}

对于需要添加或删除 session 属性的用例,请考虑将org.springframework.web.context.request.WebRequestjavax.servlet.http.HttpSession注入控制器方法。

作为控制器工作流的一部分,在 session 中临时存储 model 属性时,请考虑使用SessionAttributes,如名为“在请求之间的 HTTP session 中使用 @SessionAttributes 到 store model 属性”一节中所述。

使用 @RequestAttribute 访问请求属性

@SessionAttribute类似,@RequestAttribute annotation 可用于访问由过滤器或拦截器创建的 pre-existing 请求属性:

@RequestMapping("/")
public String handle(@RequestAttribute Client client) {
    // ...
}

使用“application/x-www-form-urlencoded”数据

前面几节介绍了如何使用@ModelAttribute来支持来自浏览器客户端的表单提交请求。建议将相同的注释用于 non-browser clients 的请求。但是,在处理 HTTP PUT 请求时,有一个显着的区别。浏览器可以通过 HTTP GET 或 HTTP POST 提交表单数据。 Non-browser 客户还可以通过 HTTP PUT 提交表单。这提出了一个挑战,因为 Servlet 规范要求ServletRequest.getParameter*()系列方法仅支持 HTTP POST 的表单字段访问,而不支持 HTTP PUT。

为了支持 HTTP PUT 和 PATCH 请求,spring-web模块提供了过滤器HttpPutFormContentFilter,可以在web.xml中配置:

<filter>
    <filter-name>httpPutFormFilter</filter-name>
    <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>httpPutFormFilter</filter-name>
    <servlet-name>dispatcherServlet</servlet-name>
</filter-mapping>

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

上面的过滤器使用 content type application/x-www-form-urlencoded拦截 HTTP PUT 和 PATCH 请求,从请求正文中读取表单数据,并将ServletRequest包装在 order 中,以通过ServletRequest.getParameter*()系列方法提供表单数据。

由于HttpPutFormContentFilter使用请求的主体,因此不应将其配置为依赖于application/x-www-form-urlencoded的其他转换器的 PUT 或 PATCH URL。这包括@RequestBody MultiValueMap<String, String>HttpEntity<MultiValueMap<String, String>>

@CookieValue annotation 允许将方法参数绑定到 HTTP cookie 的 value。

让我们考虑以下带有 http 请求的 cookie:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

以下 code sample 演示了如何获取JSESSIONID cookie 的 value:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
    //...
}

如果目标方法参数类型不是String,则会自动应用类型转换。见“方法参数和类型转换”一节。

Servlet 和 Portlet 环境中的带注释的处理程序方法支持此 annotation。

使用 @RequestHeader annotation 映射请求标头属性

@RequestHeader annotation 允许将方法参数绑定到请求标头。

这是一个 sample 请求标头:

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

以下 code sample 演示了如何获取Accept-EncodingKeep-Alive headers 的 value:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}

如果方法参数不是String,则会自动应用类型转换。见“方法参数和类型转换”一节。

Map<String, String>MultiValueMap<String, String>HttpHeaders参数上使用@RequestHeader annotation 时,map 将填充所有标头值。

Built-in 支持可用于将 comma-separated string 转换为 array/collection 的 strings 或类型转换系统已知的其他类型。对于 example,使用@RequestHeader("Accept")注释的方法参数可以是String类型,但也可以是String[]List<String>

Servlet 和 Portlet 环境中的带注释的处理程序方法支持此 annotation。

方法参数和类型转换

从请求中提取的 String-based 值(包括请求参数,路径变量,请求 headers 和 cookie 值)可能需要转换为方法参数或字段的目标类型(e.g. ,将请求参数绑定到@ModelAttribute参数中的字段)他们必然会。如果目标类型不是String,Spring 会自动转换为适当的类型。支持所有简单类型,如 int,long,Date 等。您可以通过WebDataBinder(参见“自定义 WebDataBinder 初始化”一节)或使用FormattingConversionService(请参阅第 9.6 节,“弹簧字段格式”)注册Formatters来进一步自定义转换 process。

自定义 WebDataBinder 初始化

要通过 Spring 的WebDataBinder使用 PropertyEditors 自定义请求参数 binding,您可以在控制器中使用@InitBinder -annotated 方法,在@ControllerAdvice class 中使用@InitBinder方法,或者提供自定义WebBindingInitializer。有关详细信息,请参阅“使用 @ControllerAdvice 和@RestControllerAdvice 建议控制器”一节部分。

使用 @InitBinder 自定义数据 binding

使用@InitBinder注释控制器方法允许您直接在控制器 class 中配置 web 数据 binding。 @InitBinder标识初始化WebDataBinder的方法,这些方法将用于填充命令并形成带注释的处理程序方法的 object arguments。

这样的 init-binder 方法支持@RequestMapping方法支持的所有 arguments,但 command/formobjects 和相应的验证结果 objects 除外。 Init-binder 方法不能有 return value。因此,它们通常被声明为void。典型的 arguments 包括WebDataBinderWebRequestjava.util.Locale的组合,允许 code 注册 context-specific 编辑器。

以下 example 演示了使用@InitBinder为所有java.util.Date form properties 配置CustomDateEditor

@Controller
public class MyFormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}

或者,从 Spring 4.2 开始,考虑使用addCustomFormatter指定Formatter __mplementations 而不是PropertyEditor实例。如果您碰巧在共享FormattingConversionService中设置了Formatter -based,并且使用相同的方法重复 controller-specific 调整 binding 规则,这将特别有用。

@Controller
public class MyFormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }

    // ...
}
配置自定义 WebBindingInitializer

要外部化数据 binding 初始化,您可以提供WebBindingInitializer接口的自定义 implementation,然后通过为AnnotationMethodHandlerAdapter提供自定义 bean configuration 来启用它,从而覆盖默认的 configuration。

PetClinic application 中的以下示例使用WebBindingInitializer接口org.springframework.samples.petclinic.web.ClinicBindingInitializer的自定义 implementation 显示 configuration,它配置了几个 PetClinic 控制器所需的 PropertyEditors。

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="cacheSeconds" value="0"/>
    <property name="webBindingInitializer">
        <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer"/>
    </property>
</bean>

@InitBinder方法也可以在@ControllerAdvice -annotated class 中定义,在这种情况下,它们适用于匹配的控制器。这提供了使用WebBindingInitializer的替代方法。有关详细信息,请参阅“使用 @ControllerAdvice 和@RestControllerAdvice 建议控制器”一节部分。

使用 @ControllerAdvice 和 @RestControllerAdvice 向控制器提供建议

@ControllerAdvice annotation 是一个 component annotation,允许 implementation classes 为 auto-detected 到 classpath 扫描。使用 MVC 命名空间或 MVC Java 配置时会自动启用它。

带有@ControllerAdvice注释的 Classes 可以包含@ExceptionHandler@InitBinder@ModelAttribute带注释的方法,这些方法将应用于跨所有控制器层次结构的@RequestMapping方法,而不是声明它们的控制器层次结构。

@RestControllerAdvice是一种替代方法,其中@ExceptionHandler方法默认采用@ResponseBody语义。

@ControllerAdvice@RestControllerAdvice都可以定位控制器的子集:

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}

查看@ControllerAdvice 文件了解更多详情。

Jackson 序列化视图支持

有时可以有用地过滤将被序列化到 HTTP 响应主体的 object。在 order 中提供此类功能,Spring MVC 支持使用Jackson 的序列化视图进行渲染。

要使用控制器方法或_ret _ResponseEntity的控制器方法,只需添加带有 class 参数的@JsonView annotation,指定要使用的视图 class 或接口:

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}

请注意,尽管@JsonView允许指定多个 class,但只有一个 class 参数才支持在控制器方法上使用。如果需要启用多个视图,请考虑使用复合接口。

对于依赖于视图分辨率的控制器,只需将序列化视图 class 添加到 model:

@Controller
public class UserController extends AbstractController {

    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}

Jackson JSONP 支持

在 order 中为@ResponseBodyResponseEntity方法启用JSONP支持,声明一个扩展AbstractJsonpResponseBodyAdvice@ControllerAdvice bean,如下所示,其中构造函数参数指示 JSONP 查询参数 name(s):

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }
}

对于依赖于视图解析的控制器,当请求具有名为jsonpcallback的查询参数时,将自动启用 JSONP。这些名称可以通过jsonpParameterNames property 进行自定义。

从 Spring Framework 4.3.18 开始,不推荐使用 JSONP 支持,从 Spring Framework 5.1 开始删除,应该使用CORS代替。

22.3.4 异步请求处理

Spring MVC 3.2 引入了基于 Servlet 3 的异步请求处理。像往常一样,控制器方法不是像往常一样返回 value,而是_ret 并从 Spring MVC 托管线程生成 return value。同时,退出并释放主 Servlet 容器线程,并允许其处理其他请求。 Spring MVC 在TaskExecutor的帮助下在一个单独的线程中调用Callable,当Callable返回时,请求被调度回 Servlet 容器以使用Callable返回的 value 继续处理。这是一个这样的控制器方法的示例:

@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };

}

另一个选项是控制器方法 return DeferredResult的实例。在这种情况下,return value 也将从任何线程 i.e 产生。一个不由 Spring MVC 管理的。对于 example,可以生成结果以响应某些外部 event,例如 JMS 消息,计划任务等。这是一个这样的控制器方法的示例:

@RequestMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// In some other thread...
deferredResult.setResult(data);

如果不了解 Servlet 3.0 异步请求处理 features,这可能很难理解。阅读这一点肯定会有所帮助。以下是有关基础机制的一些基本事实:

  • 通过调用request.startAsync()可以将ServletRequest置于异步模式。这样做的主要作用是 Servlet 以及任何过滤器可以退出,但响应将保持打开状态以允许稍后完成处理。

  • request.startAsync()的调用返回AsyncContext,可用于进一步控制异步处理。对于 example,它提供方法dispatch,类似于 Servlet API 中的 forward,除了它允许 application 在 Servlet 容器线程上恢复请求处理。

  • ServletRequest提供对当前DispatcherType的访问,可用于区分处理初始请求,异步调度,转发和其他调度程序类型。

考虑到上述情况,以下是使用Callable进行异步请求处理的 events 序列:

  • 控制器返回Callable

  • Spring MVC 启动异步处理并将Callable提交给TaskExecutor以便在单独的线程中进行处理。

  • DispatcherServlet和所有 Filter 都退出 Servlet 容器线程,但响应仍保持打开状态。

  • Callable生成结果,Spring MVC 将请求调度回 Servlet 容器以继续处理。

  • 再次调用DispatcherServlet并继续处理来自Callable的异步生成结果。

DeferredResult的序列非常相似,除了 application 从任何线程产生异步结果:

  • Controller 返回DeferredResult并将其保存在可以访问的某个 in-memory 队列或列表中。

  • Spring MVC 启动异步处理。

  • DispatcherServlet和所有已配置的 Filter 都退出请求处理线程但响应仍保持打开状态。

  • application 从某个线程设置DeferredResult,Spring MVC 将请求调度回 Servlet 容器。

  • 再次调用DispatcherServlet并继续处理异步生成的结果。

有关异步请求处理的动机以及何时或为何使用它的进一步背景,请阅读这个博客文章系列。

异常请求的异常处理

如果从控制器方法返回的Callable在执行时引发 Exception 会发生什么?简短的回答与控制器方法引发 exception 时发生的情况相同。它通过常规 exception 处理机制。更长的解释是,当Callable引发 Exception 时,Spring MVC 将Exception作为结果调度到 Servlet 容器,并导致使用Exception而不是控制器方法 return value 恢复请求处理。使用DeferredResult时,您可以选择是否使用Exception实例调用setResultsetErrorResult

拦截异步请求

HandlerInterceptor也可以在 order 中实现AsyncHandlerInterceptor来实现afterConcurrentHandlingStarted回调,在异步处理开始时调用afterConcurrentHandlingStarted回调而不是postHandleafterCompletion

HandlerInterceptor还可以在 order 中注册CallableProcessingInterceptorDeferredResultProcessingInterceptor以更深入地集成异步请求的生命周期,并且 example 处理超时 event。有关详细信息,请参阅AsyncHandlerInterceptor的 Javadoc。

DeferredResult类型还提供onTimeout(Runnable)onCompletion(Runnable)等方法。有关详细信息,请参阅DeferredResult的 Javadoc。

当使用Callable时,您可以使用WebAsyncTask的实例来包装它,该实例还提供超时和完成的注册方法。

HTTP Streaming

控制器方法可以使用DeferredResultCallable异步生成 return value,并且可以用来实现long 民意调查等技术,服务器可以尽快将 event 推送到 client。

如果您想在单个 HTTP 响应上推送多个 events,该怎么办?这是一种与“Long Polling”相关的技术,称为“HTTP Streaming”。 Spring MVC 通过ResponseBodyEmitter return value 类型实现了这一点,它可以用来发送多个 Objects,而不是通常@ResponseBody的情况,其中发送的每个 Object 都用HttpMessageConverter写入响应。

这是一个例子:

@RequestMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

请注意,ResponseBodyEmitter也可以用作ResponseEntity in order 中的主体来自定义响应的状态和 headers。

HTTP 流与 Server-Sent Events

SseEmitterResponseBodyEmitter的子类,为Server-Sent Events提供支持。 Server-sent events 是同一“HTTP Streaming”技术的另一个变体,除了从服务器推送的 events 根据 W3C Server-Sent Events 规范进行格式化。

Server-Sent Events 可用于其预期目的,即将 events 从服务器推送到 clients。在 Spring MVC 中很容易做到,只需要返回一个SseEmitter类型的 value。

但请注意,Internet Explorer 不支持 Server-Sent Events,而对于更高级的 web application 消息传递方案,如在线游戏,协作,财务应用程序等,最好考虑 Spring 的 WebSocket 支持,其中包括 SockJS-style WebSocket 仿真,可以回退到很宽的范围浏览器(包括 Internet Explorer)以及 higher-level 消息传递模式,用于通过更多 messaging-centric architecture 中的 publish-subscribe model 与 clients 进行交互。有关详细信息,请参阅以下博客文章。

HTTP 直接流式传输到 OutputStream

ResponseBodyEmitter允许通过HttpMessageConverter将 Objects 写入响应来发送 events。对于编写 JSON 数据时的 example,这可能是最常见的情况。但是,有时绕过消息转换并直接写入响应OutputStream for example 以进行文件下载非常有用。这可以在StreamingResponseBody return value 类型的帮助下完成。

这是一个例子:

@RequestMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}

请注意,StreamingResponseBody也可以用作ResponseEntity in order 中的主体来自定义响应的状态和 headers。

配置异步请求处理

Servlet Container Configuration

对于使用web.xml配置的 applications,请务必更新为 version 3.0:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            http://java.sun.com/xml/ns/javaee
            http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    ...

</web-app>

必须通过web.xml中的<async-supported>true</async-supported> sub-element 在DispatcherServlet上启用异步支持。此外,任何参与 asyncrequest 处理的Filter都必须配置为支持 ASYNC 调度程序类型。为 Spring Framework 提供的所有过滤器启用 ASYNC 调度程序类型应该是安全的,因为它们通常会扩展OncePerRequestFilter并且运行时检查过滤器是否需要参与异步调度。

下面是一些 example web.xml configuration:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
            http://java.sun.com/xml/ns/javaee
            http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <filter>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <filter-class>org.springframework.~.OpenEntityManagerInViewFilter</filter-class>
        <async-supported>true</async-supported>
    </filter>

    <filter-mapping>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>ASYNC</dispatcher>
    </filter-mapping>

</web-app>

如果通过WebApplicationInitializer使用 Servlet 3,基于 Java 的 configuration example,你还需要设置“asyncSupported”flag 以及 ASYNC 调度程序类型,就像web.xml一样。要简化所有这些 configuration,请考虑扩展AbstractDispatcherServletInitializer或更好的AbstractAnnotationConfigDispatcherServletInitializer,它会自动设置这些选项并使注册Filter实例变得非常容易。

Spring MVC Configuration

MVC Java 配置和 MVC 命名空间提供了配置异步请求处理的选项。 WebMvcConfigurer的方法为configureAsyncSupport<mvc:annotation-driven><async-support> sub-element。

这些允许您配置用于异步请求的默认超时 value,如果未设置,则取决于底层 Servlet 容器(Tomcat 上 e.g.10 秒)。您还可以配置AsyncTaskExecutor以用于执行从控制器方法返回的Callable实例。强烈建议配置此 property,因为默认情况下 Spring MVC 使用SimpleAsyncTaskExecutor。 MVC Java 配置和 MVC 命名空间还允许您注册CallableProcessingInterceptorDeferredResultProcessingInterceptor实例。

如果需要覆盖特定DeferredResult的默认超时 value,则可以使用相应的 class 构造函数来执行此操作。类似地,对于Callable,您可以将其包装在WebAsyncTask中并使用适当的 class 构造函数来自定义 timeout value。 WebAsyncTask的 class 构造函数也允许提供AsyncTaskExecutor

22.3.5 测试控制器

spring-test模块为测试带注释的控制器提供了第一个 class 支持。见第 15.6 节,“ Spring MVC Test Framework”。

22.4 处理程序映射

在以前的 Spring 版本中,用户需要在 web application context 中定义一个或多个HandlerMapping beans,以便将__bb 请求传入适当的处理程序。通过引入带注释的控制器,您通常不需要这样做,因为RequestMappingHandlerMapping会自动在所有@Controller beans 上查找@RequestMapping 注释。但是,请记住,从AbstractHandlerMapping扩展的所有HandlerMapping classes 都具有以下可用于自定义其行为的 properties:

  • interceptors要使用的拦截器列表。 HandlerInterceptor在部分 22.4.1,“使用 HandlerInterceptor 拦截请求”中讨论。

  • defaultHandler当此处理程序映射不会导致匹配处理程序时要使用的默认处理程序。

  • order基于 order property 的 value(参见org.springframework.core.Ordered接口),Spring 对 context 中可用的所有处理程序映射进行排序,并应用第一个匹配的处理程序。

  • alwaysUseFullPath如果true,Spring 使用当前 Servlet context 中的完整路径来查找适当的处理程序。如果false(默认值),则使用当前 Servlet 映射中的路径。对于 example,如果使用/testing/*映射 Servlet 并且alwaysUseFullPath property 设置为 true,则使用/testing/viewPage.html,而如果 property 设置为 false,则使用/viewPage.html

  • urlDecode默认为true,从 Spring 2.5 开始。如果您希望比较编码的 paths,请将此 flag 设置为false。但是,HttpServletRequest始终以解码形式公开 Servlet 路径。请注意,与编码的_path 相比,Servlet 路径不会 match。

以下 example 显示了如何配置拦截器:

<beans>
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <bean class="example.MyInterceptor"/>
        </property>
    </bean>
<beans>

22.4.1 使用 HandlerInterceptor 拦截请求

Spring 的处理程序映射机制包括处理程序拦截器,当您要对特定请求应用特定功能时非常有用,例如,检查主体。

位于处理程序映射中的拦截器必须从org.springframework.web.servlet包中实现HandlerInterceptor。该接口定义了三个方法:在执行实际处理程序之前调用preHandle(..);执行处理程序后调用postHandle(..);完成请求完成后调用afterCompletion(..)。这三种方法应该提供足够的灵活性来进行各种预处理和后处理。

preHandle(..)方法返回 boolean value。您可以使用此方法 break 或继续处理执行链。当此方法返回true时,处理程序执行链将_继续;当它返回 false 时,DispatcherServlet假定拦截器本身已经处理了请求(并且,对于 example,呈现了适当的视图)并且不继续执行其他拦截器和执行链中的实际处理程序。

可以使用interceptors property 配置拦截器,该interceptors property 存在于从AbstractHandlerMapping扩展的所有HandlerMapping classes 中。这显示在下面的 example 中:

<beans>
    <bean id="handlerMapping"
            class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
    </bean>

    <bean id="officeHoursInterceptor"
            class="samples.TimeBasedAccessInterceptor">
        <property name="openingTime" value="9"/>
        <property name="closingTime" value="18"/>
    </bean>
</beans>
package samples;

public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {

    private int openingTime;
    private int closingTime;

    public void setOpeningTime(int openingTime) {
        this.openingTime = openingTime;
    }

    public void setClosingTime(int closingTime) {
        this.closingTime = closingTime;
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
        Calendar cal = Calendar.getInstance();
        int hour = cal.get(HOUR_OF_DAY);
        if (openingTime <= hour && hour < closingTime) {
            return true;
        }
        response.sendRedirect("http://host.com/outsideOfficeHours.html");
        return false;
    }
}

此映射处理的任何请求都被TimeBasedAccessInterceptor拦截。如果当前 time 不在办公时间,则会将用户重定向到静态 HTML 文件,例如,您只能在办公时间访问该网站。

使用RequestMappingHandlerMapping时,实际的处理程序是HandlerMethod的一个实例,它标识将被调用的特定控制器方法。

如您所见,Spring 适配器 class HandlerInterceptorAdapter使扩展HandlerInterceptor接口更容易。

在上面的 example 中,配置的拦截器将应用于使用带注释的控制器方法处理的所有请求。如果要缩小拦截器适用的 URL paths,可以使用 MVC 命名空间或 MVC Java 配置,或声明类型为MappedInterceptor的 bean 实例来执行此操作。见部分 22.16.1,“启用 MVC Java 配置或 MVC XML 命名空间”。

请注意,HandlerInterceptorpostHandle方法并不总是非常适合与@ResponseBodyResponseEntity方法一起使用。在这种情况下,在调用postHandle之前写入并提交响应,这使得无法更改响应,为 example 添加标头。相反,application 可以实现ResponseBodyAdvice并将其声明为@ControllerAdvice bean 或直接在RequestMappingHandlerAdapter上配置它。

22.5 解析观点

web applications 的所有 MVC 框架都提供了一种处理视图的方法。 Spring 提供视图解析器,使您可以在浏览器中呈现模型,而无需将您与特定的视图技术联系起来。开箱即用,Spring 允许您使用 JSP,Velocity 模板和 XSLT 视图,用于 example。有关如何集成和使用多种不同视图技术的讨论,请参见第 23 章,查看技术。

对 Spring 处理视图的方式很重要的两个接口是ViewResolverViewViewResolver提供视图名称和实际视图之间的映射。 View接口解决了请求的准备问题,并将请求交给其中一种视图技术。

22.5.1 使用 ViewResolver 接口解析视图

正如第 22.3 节,“实现控制器”中所讨论的,Spring Web MVC 控制器中的所有处理程序方法必须显式地(e.g. ,通过返回StringViewModelAndView)或隐式(i.e.,基于约定)解析为逻辑视图 name。 Spring 中的视图由逻辑视图 name 解决,并由视图解析器解析。 Spring 带有相当多的视图解析器。这个 table lists 大多数;下面是几个例子。

表格 1_.查看解析器

视图解析器描述
AbstractCachingViewResolver缓存视图的抽象视图解析器。视图通常需要准备才能使用;扩展此视图解析器提供缓存。
XmlViewResolver实现ViewResolver接受用 XML 编写的 configuration 文件,其中包含与 Spring 的 XML bean 工厂相同的 DTD。默认的 configuration 文件是/WEB-INF/views.xml
ResourceBundleViewResolver的实现,在ResourceBundle中使用 bean 定义,由 bundle base name 指定。通常,您在 properties 文件中定义捆绑包,该文件位于 classpath 中。默认文件 name 是views.properties
UrlBasedViewResolverViewResolver接口的简单 implementation 实现了逻辑视图名称直接解析为 URL,没有显式映射定义。如果您的逻辑名称以直接的方式匹配视图资源的名称,而不需要任意映射,这是合适的。
InternalResourceViewResolver方便的UrlBasedViewResolver子类,支持InternalResourceView(实际上是 Servlets 和 JSP)和子类,如JstlViewTilesView。您可以使用setViewClass(..)为此解析程序生成的所有视图指定视图 class。有关详细信息,请参阅UrlBasedViewResolver javadocs。
VelocityViewResolver / FreeMarkerViewResolver方便的UrlBasedViewResolver子类,分别支持VelocityView(实际上是 Velocity 模板)或FreeMarkerView,以及它们的自定义子类。
ContentNegotiatingViewResolver根据请求文件 name 或Accept标头解析视图的ViewResolver接口的实现。见第 22.5.4 节,“ContentNegotiatingViewResolver”。

作为一个例子,使用 JSP 作为视图技术,可以使用UrlBasedViewResolver。此视图解析程序将视图 name 转换为 URL,并将请求移交给 RequestDispatcher 以呈现视图。

<bean id="viewResolver"
        class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

test作为逻辑视图 name 返回时,此视图解析程序将请求转发给将发送请求的RequestDispatcher /WEB-INF/jsp/test.jsp

在 web application 中组合不同的视图技术时,可以使用ResourceBundleViewResolver

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

ResourceBundleViewResolver检查由 basename 标识的ResourceBundle,并且对于它应该解析的每个视图,它使用 property [viewname].(class)的 value 作为 view class,property [viewname].url的 value 作为 view url。示例可以在下一章中找到,其中包括视图技术。如您所见,您可以识别 parent 视图,properties 文件中的所有视图都从该视图“扩展”。这样,您可以为 example 指定默认视图 class。

它们解析的AbstractCachingViewResolver缓存视图实例的子类。缓存提高了某些视图技术的性能。可以通过将cache property 设置为false来关闭缓存。此外,如果必须在运行时刷新某个视图(对于修改 Velocity 模板时的 example),可以使用removeFromCache(String viewName, Locale loc)方法。

22.5.2 链接 ViewResolvers

Spring 支持多个视图解析器。因此,您可以链接解析器,例如,在某些情况下覆盖特定视图。您可以通过向 application context 添加多个解析程序来链接视图解析器,如有必要,可以通过设置order property 来指定 ordering。请记住,order property 越高,视图解析器在链中的位置越晚。

在下面的示例中,视图解析器链包含两个解析器,一个InternalResourceViewResolver,它始终自动定位为链中的最后一个解析器,另一个XmlViewResolver用于指定 Excel 视图。 InternalResourceViewResolver不支持 Excel 视图。

<bean id="jspViewResolver" 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>

<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="order" value="1"/>
    <property name="location" value="/WEB-INF/views.xml"/>
</bean>

<!-- in views.xml -->

<beans>
    <bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>

如果特定视图解析程序未生成视图,Spring 将检查其他视图解析程序的 context。如果存在其他视图解析器,Spring 将继续检查它们,直到解析视图。如果没有视图解析器返回视图,Spring 将抛出ServletException

视图解析程序的 contract 指定视图解析程序可以 return null 以指示无法找到该视图。但是,并非所有视图解析器都这样做,因为在某些情况下,解析器根本无法检测视图是否存在。对于 example,InternalResourceViewResolver在内部使用RequestDispatcher,并且调度是确定 JSP 是否存在的唯一方法,但此操作只能执行一次。这同样适用于VelocityViewResolver和其他一些。检查特定视图解析程序的 javadoc 以查看它是否报告 non-existing 视图。因此,将链中的InternalResourceViewResolver放在除最后一个之外的位置会导致链未被完全检查,因为InternalResourceViewResolver将始终 return 视图!

22.5.3 重定向到视图

如前所述,控制器通常返回逻辑视图 name,视图解析器将解析为特定的视图技术。对于通过 Servlet 或 JSP 引擎处理的 JSP 等视图技术,此解决方案通常通过InternalResourceViewResolverInternalResourceView的组合来处理,它们通过 Servlet API 的RequestDispatcher.forward(..)方法或RequestDispatcher.include()方法发出内部转发或包含。对于其他视图技术,例如 Velocity,XSLT 等,视图本身将内容直接写入响应流。

在呈现视图之前,有时需要向 client 发出 HTTP 重定向。这是可取的,例如,当使用POST数据调用一个控制器时,响应实际上是对另一个控制器的委托(对于成功表单提交的 example)。在这种情况下,正常的内部转发将意味着另一个控制器也将看到相同的POST数据,如果它可以将其与其他预期数据混淆,则可能存在问题。在显示结果之前执行重定向的另一个原因是消除用户多次提交表单数据的可能性。在这种情况下,浏览器将首先发送一个初始POST;然后它会收到重定向到不同 URL 的响应;最后,浏览器将为重定向响应中指定的 URL 执行后续GET。因此,从浏览器的角度来看,当前页面不会反映POST的结果,而是反映GET的结果。最终效果是用户无法通过执行刷新意外地重新获得相同的数据。刷新强制结果页面的GET,而不是重新发送初始POST数据。

RedirectView

作为控制器响应的结果,强制重定向的一种方法是控制器创建和 return Spring 的RedirectView实例。在这种情况下,DispatcherServlet不使用普通的视图解析机制。而是因为它已经被赋予(重定向)视图,DispatcherServlet只是指示视图完成其工作。 RedirectView依次 calls HttpServletResponse.sendRedirect()将 HTTP 重定向发送到 client 浏览器。

如果您使用RedirectView并且视图是由控制器本身创建的,则建议您将重定向 URL 配置为注入控制器,以使其不会烘焙到控制器中,而是在 context 中与视图名称一起配置。 名为“重定向:前缀”的部分促进了这种解耦。

将数据传递给重定向目标

默认情况下,所有 model 属性都被视为在重定向 URL 中作为 URI 模板变量公开。在其余属性中,原始类型或原始类型的 collections/arrays 会自动附加为查询参数。

如果专门为重定向准备了 model 实例,则将原始类型属性作为查询参数附加可能是期望的结果。但是,在带注释的控制器中,model 可能包含为渲染目的而添加的其他属性(e.g. drop-down 字段值)。为了避免在 URL 中出现此类属性的可能性,@RequestMapping方法可以声明类型为RedirectAttributes的参数,并使用它来指定可供RedirectView使用的确切属性。如果方法重定向,则使用RedirectAttributes的内容。否则使用 model 的内容。

RequestMappingHandlerAdapter提供了一个名为"ignoreDefaultModelOnRedirect"的 flag,可用于指示如果控制器方法重定向,则永远不应使用默认Model的内容。相反,控制器方法应声明类型为RedirectAttributes的属性,或者如果不这样做,则不应将任何属性传递给RedirectView。 MVC 命名空间和 MVC Java 配置都将 flag 设置为false in order 以保持向后兼容性。但是,对于新的 applications,我们建议将其设置为true

请注意,扩展重定向 URL 时,当前请求中的 URI 模板变量会自动变为可用,并且不需要通过ModelRedirectAttributes显式添加。例如:

@PostMapping("/files/{path}")
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}

将数据传递到重定向目标的另一种方法是通过 Flash 属性。与其他重定向属性不同,Flash 属性保存在 HTTP session 中(因此不会出现在 URL 中)。有关更多信息,请参见第 22.6 节,“使用 flash 属性”。

重定向:前缀

虽然RedirectView的使用工作正常,但如果控制器本身创建RedirectView,则无法避免控制器意识到重定向正在发生的事实。这实际上并不是最理想的,而是将事情过于紧密。控制器不应该真正关心如何处理响应。通常,它应该仅根据已注入其中的视图名称进行操作。

特殊的redirect:前缀允许您完成此操作。如果返回的视图 name 具有前缀redirect:UrlBasedViewResolver(以及所有子类)将识别此为需要重定向的特殊指示。视图 name 的 rest 将被视为重定向 URL。

净效果与控制器返回RedirectView的效果相同,但现在控制器本身可以简单地根据逻辑视图名称进行操作。逻辑视图 name(例如redirect:/myapp/some/resource)将相对于当前 Servlet context 重定向,而 name(例如redirect:http://myhost.com/some/arbitrary/path)将重定向到绝对 URL。

请注意,控制器处理程序使用@ResponseStatus进行批注,annotation value 优先于RedirectView设置的响应状态。

转发:前缀

对于最终由UrlBasedViewResolver和子类解析的视图名称,也可以使用特殊的forward:前缀。这会在视图 name 的 rest 周围创建一个InternalResourceView(最终会产生一个RequestDispatcher.forward()),这被认为是一个 URL。因此,此前缀对InternalResourceViewResolverInternalResourceView(对于 example 的 JSP)无用。但是,当您主要使用其他视图技术时,前缀可能会有所帮助,但仍希望强制由 Servlet/JSP 引擎处理资源的转发。 (注意,您也可以链接多个视图解析器,instead.)

redirect:前缀一样,如果带有forward:前缀的 view name 被注入控制器,则控制器不会检测到在处理响应方面发生了什么特殊情况。

22.5.4 ContentNegotiatingViewResolver

ContentNegotiatingViewResolver本身不解析视图,而是委托给其他视图解析器,选择类似于 client 请求的表示的视图。对于从服务器请求表示的 client 存在两种策略:

  • 通常通过在 URI 中使用不同的文件扩展名为每个资源使用不同的 URI。例如,URI http://www.example.com/users/fred.pdf请求用户 fred 的 PDF 表示,http://www.example.com/users/fred.xml请求 XML 表示。

  • 使用相同的 URI 为 client 定位资源,但设置Accept HTTP 请求标头以列出它理解的媒体类型。例如,http://www.example.com/users/fred的 HTTP 请求(Accept标头设置为application/pdf)请求用户 fred 的 PDF 表示,而http://www.example.com/users/fredAccept标头设置为text/xml请求 XML 表示。这种策略称为内容谈判。

Accept标题的一个问题是无法在 HTML 中的 web 浏览器中设置它。例如,在 Firefox 中,它固定为:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

因此,当 developing 基于浏览器的 web applications 时,看到每个表示使用不同的 URI 是常见的。

为了支持资源的多个表示,Spring 提供ContentNegotiatingViewResolver以基于 HTTP 请求的文件扩展名或Accept标头来解析视图。 ContentNegotiatingViewResolver不执行视图解析本身,而是委托给您通过 bean property ViewResolvers指定的视图解析器列表。

ContentNegotiatingViewResolver选择适当的View来处理请求,方法是将请求媒体 type(s 与每个ViewResolvers关联的View支持的媒体类型(也称为Content-Type)进行比较。列表中具有兼容Content-Type的第一个View将表示返回给 client。如果ViewResolver链不能提供兼容的视图,那么将查阅通过DefaultViews property 指定的视图列表。后一个选项适用于 singleton Views,它可以呈现当前资源的适当表示,而不管逻辑视图 name。对于 example text/*Accept标头可能包含通配符,在这种情况下,View Accepttext/xmlView是兼容的 match。

要支持基于文件扩展名的视图的自定义分辨率,请使用ContentNegotiationManager:请参阅第 22.16.6 节,“内容谈判”。

这是ContentNegotiatingViewResolver的 example configuration:

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    <property name="viewResolvers">
        <list>
            <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
            <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <property name="prefix" value="/WEB-INF/jsp/"/>
                <property name="suffix" value=".jsp"/>
            </bean>
        </list>
    </property>
    <property name="defaultViews">
        <list>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </list>
    </property>
</bean>

<bean id="content" class="com.foo.samples.rest.SampleContentAtomView"/>

InternalResourceViewResolver处理视图名称和 JSP 页面的转换,而BeanNameViewResolver则返回基于 bean 的 name 的视图。 (有关 Spring 如何查找并实例化 view.)的更多详细信息,请参阅“使用 ViewResolver 接口解析视图”。在此 example 中,content bean 是一个继承自AbstractAtomFeedView的 class,它返回一个 Atom RSS 提要。有关创建 Atom Feed 表示的详细信息,请参阅原子视图部分。

在上面的 configuration 中,如果使用.html扩展名发出请求,则视图解析程序会查找与text/html媒体类型匹配的视图。 InternalResourceViewResolver提供text/html的匹配视图。如果请求是使用文件扩展名.atom进行的,则视图解析程序将查找与application/atom+xml媒体类型匹配的视图。如果返回的视图 name 是content,则BeanNameViewResolver提供此视图,为SampleContentAtomView。如果请求是使用文件扩展名.json进行的,则无论视图 name 如何,都将选择DefaultViews列表中的MappingJackson2JsonView实例。或者,client 请求可以在没有文件扩展名的情况下进行,但Accept标头设置为首选 media-type,并且会发生与视图请求相同的分辨率。

如果没有显式配置`ContentNegotiatingViewResolver 的 ViewResolvers 列表,它会自动使用 application context 中定义的任何 ViewResolvers。

返回一个 Atom RSS 提要的相应控制器 code,其形式为http://localhost/content.atomhttp://localhost/content的_,

@Controller
public class ContentController {

    private List<SampleContent> contentList = new ArrayList<SampleContent>();

    @GetMapping("/content")
    public ModelAndView getContent() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("content");
        mav.addObject("sampleContentList", contentList);
        return mav;
    }

}

22.6 使用 flash 属性

Flash 属性为一个请求提供了一种方法来存储打算在另一个请求中使用的属性。这在重定向时最常见 - 例如,Post/Redirect/Get pattern。 Flash 重定向(通常在 session 中)之前临时保存 Flash 属性,以便在重定向后立即删除请求。

Spring MVC 有两个主要的抽象支持 flash 属性。 FlashMap用于保存 flash 属性,而FlashMapManager用于 store,检索和管理FlashMap实例。

Flash 属性支持始终处于“打开”状态,并且不需要显式启用,但如果不使用,则永远不会导致创建 HTTP session。在每个请求上都有一个“输入”FlashMap,其中包含从先前请求(如果有)传递的属性,以及一个“输出”FlashMap,其中包含要为后续请求保存的属性。 FlashMap实例都可以通过RequestContextUtils中的静态方法从 Spring MVC 中的任何位置访问。

带注释的控制器通常不需要直接使用FlashMap。相反,@RequestMapping方法可以接受RedirectAttributes类型的参数,并使用它为重定向方案添加 flash 属性。通过RedirectAttributes添加的 Flash 属性会自动传播到“输出”FlashMap。同样,在重定向之后,“input”FlashMap中的属性会自动添加到服务于目标 URL 的控制器的Model中。


匹配闪存属性的请求

Flash 属性的概念存在于许多其他 Web 框架中,并且已被证明有时会暴露于并发问题。这是因为根据定义,闪存属性将被存储直到下一个请求。但是,非常“下一个”请求可能不是预期的收件人,而是另一个异步请求(e.g. 轮询或资源请求),在这种情况下,过早删除 flash 属性。

为了减少此类问题的可能性,RedirectView使用目标重定向 URL 的路径和查询参数自动“标记”FlashMap实例。反过来,当查找“输入”FlashMap时,默认FlashMapManager将该信息与传入请求相匹配。

这并不能完全消除并发问题的可能性,但是使用重定向 URL 中已有的信息可以大大减少它。因此,建议主要针对重定向方案使用闪存属性。


22.7 Building URI

Spring MVC 提供了一种使用UriComponentsBuilderUriComponents构建和编码 URI 的机制。

对于 example,您可以展开和编码 URI 模板 string:

UriComponents uriComponents = UriComponentsBuilder.fromUriString(
        "http://example.com/hotels/{hotel}/bookings/{booking}").build();

URI uri = uriComponents.expand("42", "21").encode().toUri();

请注意,UriComponents是不可变的,expand()encode()操作必要时返回新实例。

您还可以使用单个 URI 组件进行扩展和编码:

UriComponents uriComponents = UriComponentsBuilder.newInstance()
        .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
        .expand("42", "21")
        .encode();

在 Servlet 环境中,ServletUriComponentsBuilder子类提供静态工厂方法,以从 Servlet 请求中复制可用的 URL 信息:

HttpServletRequest request = ...

// Re-use host, scheme, port, path and query string
// Replace the "accountId" query param

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}").build()
        .expand("123")
        .encode();

或者,您可以选择复制可用信息的子集,直至并包括 context 路径:

// Re-use host, port and context path
// Append "/accounts" to the path

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
        .path("/accounts").build()

或者在DispatcherServlet由 name(e.g. /main/*)映射的情况下,您还可以包含 servlet 映射的文字部分:

// Re-use host, port, context path
// Append the literal part of the servlet mapping to the path
// Append "/accounts" to the path

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
        .path("/accounts").build()

22.7.1 Building URI 到控制器和方法

Spring MVC 提供了一种准备控制器方法链接的机制。例如,以下 MVC 控制器可以轻松地创建链接:

@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {

    @GetMapping("/bookings/{booking}")
    public ModelAndView getBooking(@PathVariable Long booking) {
        // ...
    }
}

您可以通过 name 引用方法来准备链接:

UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

在上面的例子中,我们提供了实际的方法参数值,在本例中是 long value 21,用作路径变量并插入到 URL 中。此外,我们在 order 中提供 value 42 来填充任何剩余的 URI 变量,例如从 type-level 请求映射继承的“hotel”变量。如果方法有更多 arguments,则可以为 URL 不需要的 arguments 提供 null。通常,只有@PathVariable@RequestParamarguments 与构造 URL 相关。

还有其他方法可以使用MvcUriComponentsBuilder。对于 example,您可以使用类似于通过代理进行 mock 测试的技术,以避免通过 name 引用控制器方法(example 假定MvcUriComponentsBuilder.on的静态 import):

UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

控制器方法签名在被设计用于与fromMethodCall创建链接时受到限制。除了需要正确的参数签名之外,return 类型还存在技术限制:即为链接构建器调用生成运行时代理,因此 return 类型不能为final。特别是,视图名称的 common String return 类型在这里不起作用;使用ModelAndView或甚至普通的Object(带有String return value)代替。

以上示例在MvcUriComponentsBuilder中使用静态方法。在内部,它们依赖ServletUriComponentsBuilder从当前请求的 scheme,host,port,context 路径和 servlet 路径准备基本 URL。这在大多数情况下效果很好,但有时可能不够。对于 example,您可能在请求的 context 之外(e.g. 准备链接的批处理 process)或者您可能需要插入路径前缀(e.g. 从请求路径中删除的 locale 前缀,需要 re-inserted 到链接)。

对于这种情况,您可以使用接受UriComponentsBuilder的静态“fromXxx”重载方法来使用基本 URL。或者,您可以使用基本 URL 创建MvcUriComponentsBuilder的实例,然后使用 instance-based“withXxx”方法。例如:

UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

22.7.2 使用“Forwarded”和“X-Forwarded - *”Headers

当请求通过代理(例如负载均衡器)时,host,port 和 scheme 可能会更改为需要创建资源链接的 applications 提出挑战,因为链接应该反映原始请求的 host,port 和 scheme,如下所示: 客户视角。

RFC 7239定义代理的“转发”HTTP 标头,用于提供有关原始请求的信息。还有其他 non-standard _header 在使用,例如“X-Forwarded-Host”,“X-Forwarded-Port”和“X-Forwarded-Proto”。

ServletUriComponentsBuilderMvcUriComponentsBuilder都检测,提取和使用“转发”标题中的信息,或者如果“转发”不存在则从“X-Forwarded-Host”,“X-Forwarded-Port”和“X-Forwarded-Proto”检测信息,以便生成的链接反映原始请求。

ForwardedHeaderFilter为整个 application 提供了一次和全局相同的替代方法。过滤器将 order 中的请求包装为覆盖 host,port 和 scheme 信息,并“隐藏”任何转发的 headers 以供后续处理。

请注意,使用转发的 headers 时需要考虑安全性,如 RFC 7239 的第 8 节所述。在 application level 中,很难确定是否可以信任转发的 headers。这就是为什么应该正确配置网络上游以从外部过滤掉不受信任的转发_header。

没有代理但不需要使用转发 headers 的应用程序可以配置ForwardedHeaderFilter删除并忽略此类 headers。

22.7.3 从视图构建控制器和方法的 URI

您还可以从 JSP,Thymeleaf,FreeMarker 等视图中构建指向带注释控制器的链接。这可以使用MvcUriComponentsBuilder中的fromMappingName方法来完成,该方法引用 name 的映射。

每个@RequestMapping都会根据 class 的大写字母和完整方法 name 分配一个默认的 name。例如,class FooController中的方法getFoo被赋予 name“FC#getFoo”。可以通过创建HandlerMethodMappingNamingStrategy的实例并将其插入RequestMappingHandlerMapping来替换或自定义此策略。默认策略 implementation 还会查看@RequestMapping上的 name 属性并使用该属性(如果存在)。这意味着如果分配的默认映射 name 与另一个(e.g. 重载方法)冲突,则可以在@RequestMapping上明确指定 name。

分配的请求映射名称在启动时记录在 TRACE level 中。

Spring JSP 标记 library 提供了一个名为mvcUrl的函数,可用于根据此机制准备指向控制器方法的链接。

对于示例给出:

@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {

    @RequestMapping("/{country}")
    public HttpEntity getAddress(@PathVariable String country) { ... }
}

您可以按如下方式从 JSP 准备链接:

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>

上面的 example 依赖于 Spring 标记 library(i.e.META-INF/spring.tld)中声明的mvcUrl JSP function。对于更高级的情况(e.g. 自定义基本 URL,如上一节所述),可以很容易地定义自己的 function,或使用自定义标记文件在 order 中使用MvcUriComponentsBuilder的特定实例和自定义基本 URL。

22.8 使用区域设置

Spring architecture 的大多数部分支持国际化,就像 Spring web MVC framework 一样。 DispatcherServlet使您能够使用 client 的 locale 自动解析消息。这是通过LocaleResolver objects 完成的。

当一个请求进来时,DispatcherServlet会查找一个 locale 解析器,如果找到一个,它会尝试使用它来设置 locale。使用RequestContext.getLocale()方法,您始终可以检索 locale 解析程序解析的 locale。

除了自动 locale 解析之外,您还可以将拦截器附加到处理程序映射(有关处理程序映射拦截器的更多信息,请参阅部分 22.4.1,“使用 HandlerInterceptor 拦截请求”),以便在特定情况下根据请求中的参数更改 locale。

Locale 解析器和拦截器在org.springframework.web.servlet.i18n包中定义,并以正常方式在 application context 中配置。以下是 Spring 中包含的 locale 解析器的选择。

22.8.1 获取 Time Zone 信息

除了获取 client 的 locale 之外,了解它们的 time zone 通常很有用。 LocaleContextResolver接口提供LocaleResolver的扩展,允许解析器提供更丰富的LocaleContext,其中可能包含 time zone 信息。

可用时,可以使用RequestContext.getTimeZone()方法获取用户的TimeZone。在 Spring 的ConversionService中注册的 Date/Time ConverterFormatter objects 将自动使用 Time zone 信息。

22.8.2 AcceptHeaderLocaleResolver

此 locale 解析程序检查 client(e.g. ,web 浏览器)发送的请求中的accept-language标头。通常此头字段包含 client 操作系统的 locale。请注意,此解析程序不支持 time zone 信息。

22.8.3 CookieLocaleResolver

此 locale 解析程序检查 client 上可能存在的Cookie,以查看是否指定了LocaleTimeZone。如果是,则使用指定的详细信息。使用此 locale 解析程序的 properties,可以指定 cookie 的 name 以及最大年龄。在下面找到定义CookieLocaleResolver的示例。

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">

    <property name="cookieName" value="clientlanguage"/>

    <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
    <property name="cookieMaxAge" value="100000"/>

</bean>

表格 1_.CookieLocaleResolver properties

属性默认描述
cookieNameclassname LOCALEcookie 的 name
名 cookieMaxAgeServlet 容器默认一个 cookie 的最大 time 将在 client 上保持持久性。如果指定-1,cookie 将不会被保留;它只有在 client 关闭浏览器之后才可用。
cookiePath/限制 cookie 对您网站某个部分的可见性。指定 cookiePath 时,cookie 将仅对该路径可见,并且对其下方的 paths 可见。

22.8.4 SessionLocaleResolver

SessionLocaleResolver允许您从 session 中检索可能与用户请求相关联的LocaleTimeZone。与CookieLocaleResolver相反,此策略存储 Servlet 容器的HttpSession中本地选择的 locale 设置。因此,这些设置对于每个 session 都是暂时的,因此在每个 session 终止时都会丢失。

请注意,与 Spring Session 项目之类的外部 session management 机制没有直接关系。这个SessionLocaleResolver将简单地评估和修改当前HttpServletRequest的相应HttpSession属性。

22.8.5 LocaleChangeInterceptor

您可以通过将LocaleChangeInterceptor添加到其中一个处理程序映射来启用语言环境的更改(请参阅第 22.4 节,“处理程序映射”)。它将检测请求中的参数并更改 locale。它_call 在LocaleResolver上也存在于 context 中。以下示例显示 calls 包含名为siteLanguage的参数的所有*.view资源现在将更改 locale。因此,对于 example,请求以下网址,http://www.sf.net/home.view?siteLanguage=nl会将网站语言更改为荷兰语。

<bean id="localeChangeInterceptor"
        class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="siteLanguage"/>
</bean>

<bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <value>/**/*.view=someController</value>
    </property>
</bean>

22.9 使用主题

22.9.1 主题概述

您可以应用 Spring Web MVC framework 主题来设置 application 的整体 look-and-feel,从而增强用户体验。主题是静态资源的集合,通常是样式表和图像,它们会影响 application 的视觉样式。

22.9.2 定义主题

要在 web application 中使用主题,必须设置org.springframework.ui.context.ThemeSource接口的 implementation。 WebApplicationContext接口扩展ThemeSource但将其职责委托给专用的 implementation。默认情况下,委托将是一个org.springframework.ui.context.support.ResourceBundleThemeSource implementation,它从 classpath 的根目录加载 properties files。要使用自定义ThemeSource implementation 或配置ResourceBundleThemeSource的基本 name 前缀,可以在 application context 中使用 reserved name themeSource注册 bean。 web application context 会自动检测具有该 name 的 bean 并使用它。

使用ResourceBundleThemeSource时,主题在简单的 properties 文件中定义。 properties 文件列出构成主题的资源。这是一个 example:

styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg

properties 的键是引用 view code 中的主题元素的名称。对于 JSP,通常使用spring:theme自定义标记执行此操作,该标记与spring:message标记非常相似。以下 JSP 片段使用前一个 example 中定义的主题来自定义外观:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
    <head>
        <link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
    </head>
    <body style="background=<spring:theme code='background'/>">
        ...
    </body>
</html>

默认情况下,ResourceBundleThemeSource使用空基 name 前缀。因此,properties files 从 classpath 的根加载。因此,您可以将cool.properties主题定义放在 classpath 的根目录中,对于 example,在/WEB-INF/classes中。 ResourceBundleThemeSource使用标准的 Java 资源包 loading 机制,允许主题的完全国际化。对于 example,我们可以使用引用带有荷兰文本的特殊背景图像。

22.9.3 主题解析器

定义主题后,如上一节所述,您可以决定使用哪个主题。 DispatcherServlet将查找名为themeResolver的 bean,以找出要使用的ThemeResolver implementation。主题解析器的工作方式与LocaleResolver的工作方式大致相同。它检测用于特定请求的主题,还可以更改请求的主题。以下主题解析器由 Spring 提供:

表格 1_.ThemeResolver implementations

描述
FixedThemeResolver选择使用defaultThemeName property 设置的固定主题。
SessionThemeResolver主题在用户的 HTTP session 中维护。它只需要为每个 session 设置一次,但不会在会话之间保持不变。
CookieThemeResolver所选主题存储在 client 上的 cookie 中。

Spring 还提供了ThemeChangeInterceptor,允许使用简单的请求参数对每个请求进行主题更改。

22.10 Spring 的 multipart(文件上传)支持

22.10.1 简介

Spring 的 built-in multipart 支持处理 web applications 中的文件上传。使用org.springframework.web.multipart包中定义的可插入MultipartResolver objects 启用此 multipart 支持。 Spring 提供一个MultipartResolver implementation 用于Commons _FileUpload,另一个用于 Servlet 3.0 multipart 请求解析。

默认情况下,Spring 不进行 multipart 处理,因为一些开发人员想要自己处理多部分。通过向 web application 的 context 添加 multipart 解析器来启用 Spring multipart 处理。检查每个请求以查看它是否包含 multipart。如果未找到 multipart,则请求将按预期继续。如果在请求中找到 multipart,则使用已在 context 中声明的MultipartResolver。之后,请求中的 multipart 属性将被视为任何其他属性。

22.10.2 使用带有 Commons _FileUpload 的 MultipartResolver

以下 example 显示了如何使用CommonsMultipartResolver

<bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

    <!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize" value="100000"/>

</bean>

当然,您还需要在 classpath 中放置适当的 jars 以使 multipart 解析器工作。对于CommonsMultipartResolver,您需要使用commons-fileupload.jar

当 Spring DispatcherServlet检测到 multi-part 请求时,它会激活 context 中声明的解析器并移交请求。解析器然后将当前HttpServletRequest包装成支持 multipart 文件上载的MultipartHttpServletRequest。使用MultipartHttpServletRequest,您可以获得有关此请求包含的多部分的信息,并实际访问控制器中的 multipart files 本身。

22.10.3 将 MultipartResolver 与 Servlet 一起使用 3.0

在 order 中使用基于 Servlet 3.0 的 multipart 解析,您需要在web.xml中使用"multipart-config"部分标记DispatcherServlet,或者在编程 Servlet 注册中使用javax.servlet.MultipartConfigElement,或者在 Servlet class 上使用javax.servlet.annotation.MultipartConfig annotation 自定义 Servlet class。 _Conviguration 设置(例如最大大小或存储位置)需要在 Servlet 注册 level 上应用,因为 Servlet 3.0 不允许从 MultipartResolver 完成这些设置。

一旦使用上述方法之一启用 Servlet 3.0 multipart 解析,就可以将StandardServletMultipartResolver添加到 Spring configuration:

<bean id="multipartResolver"
        class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>

22.10.4 处理表单中的文件上载

MultipartResolver完成其 job 之后,请求将像其他任何一样处理。首先,创建一个带有文件输入的表单,允许用户上传表单。编码属性(enctype="multipart/form-data")使浏览器知道如何将表单编码为 multipart 请求:

<html>
    <head>
        <title>Upload a file please</title>
    </head>
    <body>
        <h1>Please upload a file</h1>
        <form method="post" action="/form" enctype="multipart/form-data">
            <input type="text" name="name"/>
            <input type="file" name="file"/>
            <input type="submit"/>
        </form>
    </body>
</html>

下一个步骤是创建一个处理文件上传的控制器。这个控制器非常类似于正常注释 @Controller,除了我们在方法参数中使用MultipartHttpServletRequestMultipartFile

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }

        return "redirect:uploadFailure";
    }

}

注意@RequestParam方法如何将 map 引用到表单中声明的输入元素。在这个例子中,byte[]没有做任何事情,但实际上你可以将它保存在数据库中,将它存储在文件系统上,等等。

使用 Servlet 3.0 multipart 解析时,您还可以使用javax.servlet.http.Part作为方法参数:

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") Part file) {

        InputStream inputStream = file.getInputStream();
        // store bytes from uploaded file somewhere

        return "redirect:uploadSuccess";
    }

}

22.10.5 处理来自程序化客户端的文件上载请求

Multipart 请求也可以在 RESTful 服务方案中从 non-browser clients 提交。所有上述示例和 configuration 也适用于此处。但是,与通常提交 files 和简单表单字段的浏览器不同,编程 client 还可以发送特定 content type 的更复杂数据 - 例如 multipart 请求包含文件,第二部分包含 JSON 格式数据:

POST /someUrl
Content-Type: multipart/mixed

--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{
	"name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...

您可以使用@RequestParam("meta-data") String metadata controller 方法参数访问名为“meta-data”的部件。但是,您可能更愿意接受从请求部分正文中的 JSON 格式数据初始化的强类型 object,非常类似于@RequestBodyHttpMessageConverter的帮助下将 non-multipart 请求的主体转换为目标 object 的方式。

为此,您可以使用@RequestPart annotation 而不是@RequestParam annotation。它允许您考虑 multipart 的'Content-Type'标头,让HttpMessageConverter的特定 multipart 的内容通过HttpMessageConverter

@PostMapping("/someUrl")
public String onSubmit(@RequestPart("meta-data") MetaData metadata,
        @RequestPart("file-data") MultipartFile file) {

    // ...

}

注意如何使用@RequestParam@RequestPart可以互换地访问MultipartFile方法 arguments。但是,在这种情况下,@RequestPart("meta-data") MetaData方法参数将根据其'Content-Type'标头读取为 JSON 内容,并在MappingJackson2HttpMessageConverter的帮助下进行转换。

22.11 处理 exceptions

22.11.1 HandlerExceptionResolver

Spring HandlerExceptionResolver implementations 处理控制器执行期间发生的意外 exceptions。 HandlerExceptionResolver有点类似于您可以在 web application 描述符web.xml中定义的 exception 映射。但是,它们提供了一种更灵活的方法。对于 example,它们提供有关在抛出 exception 时正在执行哪个处理程序的信息。此外,处理 exceptions 的编程方法为您提供了更多选项,以便在请求转发到另一个 URL 之前进行适当的响应(与使用 Servlet 特定的 exception 映射时相同的最终结果)。

除了实现HandlerExceptionResolver接口,这只是实现resolveException(Exception, Handler)方法并返回ModelAndView的问题,您还可以使用提供的SimpleMappingExceptionResolver或创建@ExceptionHandler方法。 SimpleMappingExceptionResolver使您可以获取可能被抛出的任何 exception 的 class name,并将其映射到视图 name。这在功能上等同于 Servlet API 中的 exception mapping feature,但也可以从不同的处理程序实现更精细的 exceptions 映射。另一方面,@ExceptionHandler annotation 可用于应调用以处理 exception 的方法。这些方法可以在@Controller中本地定义,也可以在@ControllerAdvice class 中定义时应用于许多@Controller classes。以下部分更详细地解释了这一点。

22.11.2 @ExceptionHandler

HandlerExceptionResolver接口和SimpleMappingExceptionResolver __mplement 允许您在转发到这些视图之前,以声明方式将 Exceptions 与一些可选的 Java 逻辑一起映射到特定视图。但是,在某些情况下,尤其是在依赖@ResponseBody方法而不是视图分辨率时,直接设置响应的状态并可选地将错误内容写入响应主体可能更方便。

你可以用@ExceptionHandler方法做到这一点。在控制器中声明时,此类方法适用于该控制器(或其任何子类)的@RequestMapping方法引发的 exceptions。您还可以在@ControllerAdvice class 中声明@ExceptionHandler方法,在这种情况下,它会处理来自许多控制器的@RequestMapping方法的 exceptions。以下是 controller-local @ExceptionHandler方法的示例:

@Controller
public class SimpleController {

    // ...

    @ExceptionHandler
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
}

exception 可以_对传播的 top-level exception(i.e.直接IOException抛出),或者wrapper exception(e.g. 包裹在IllegalStateException内)的直接原因。

对于匹配 exception 类型,最好将目标 exception 声明为方法参数,如上所示。当多个 exception 方法 match 时,root exception match 通常优先于 cause exception match。更具体地说,ExceptionDepthComparator用于根据抛出的 exception 类型的深度对 exceptions 进行排序。

或者,@ExceptionHandler value 可以设置为 exception 类型的 array。如果抛出的 exception 与列表中的某个类型匹配,则将调用使用匹配@ExceptionHandler注释的方法。如果未设置 annotation value,则将使用声明的方法参数类型进行匹配。

对于@ExceptionHandler方法,root exception match 将首选匹配当前 exception 的原因,在特定控制器或通知 bean 的处理程序方法中。但是,higher-priority 上的原因 match 仍然优先于 lower-priority advice bean 上的任何 match(无论是 root 还是 cause level)。因此,在使用 multi-advice 排列时,请在优先级建议 bean 上声明主根 exception 映射,并使用相应的 order!

与使用@RequestMapping annotation 注释的标准控制器方法非常相似,@ExceptionHandler方法的方法 arguments 和 return 值可以是灵活的。例如,可以在 Servlet 环境中访问HttpServletRequest,在 Portlet 环境中访问PortletRequest。 return 类型可以是String,它被解释为视图 name,ModelAndView object,ResponseEntity,或者您也可以添加@ResponseBody以使用消息转换器转换方法 return value 并将其写入响应流。

最后但并非最不重要的是,@ExceptionHandler方法 implementation 可以选择通过以原始形式重新抛出它来退出处理给定的 exception 实例。这在您只对 root-level 匹配感兴趣或在特定 context 中无法静态确定的匹配中的情况下非常有用。重新抛出的 exception 将通过剩余的分辨率链传播,就像给定的@ExceptionHandler方法首先不匹配一样。

22.11.3 处理标准 Spring MVC Exceptions

Spring MVC 在处理请求时可能会引发一些 exceptions。 SimpleMappingExceptionResolver可以根据需要轻松地将任何 exception 映射到默认错误视图。但是,在使用以自动方式解释响应的 clients 时,您需要在响应上设置特定状态 code。根据引发的 exception,状态 code 可能指示 client 错误(4xx)或服务器错误(5xx)。

DefaultHandlerExceptionResolver将 Spring MVC exceptions 转换为特定的错误状态代码。默认情况下,它与 MVC 命名空间,MVC Java 配置以及DispatcherServlet(i.e.未使用 MVC 命名空间或 Java 配置时)一起注册。下面列出了此解析程序处理的一些 exceptions 以及相应的状态代码:

例外HTTP 状态 Code
BindException400 (错误请求)
ConversionNotSupportedException500 (内部服务器错误)
HttpMediaTypeNotAcceptableException406 (不可接受)
HttpMediaTypeNotSupportedException415 (不支持的媒体类型)
HttpMessageNotReadableException400 (错误请求)
HttpMessageNotWritableException500 (内部服务器错误)
HttpRequestMethodNotSupportedException405 (方法不允许)
MethodArgumentNotValidException400 (错误请求)
MissingPathVariableException500 (内部服务器错误)
MissingServletRequestParameterException400 (错误请求)
MissingServletRequestPartException400 (错误请求)
NoHandlerFoundException404 (未找到)
NoSuchRequestHandlingMethodException404 (未找到)
TypeMismatchException400 (错误请求)

DefaultHandlerExceptionResolver通过设置响应的状态透明地工作。但是,当您的 application 可能需要在提供 REST API 时为 example 的每个错误响应添加 developer-friendly 内容时,它不会将任何错误内容写入响应正文。您可以通过视图分辨率准备ModelAndView并渲染错误内容。通过配置ContentNegotiatingViewResolverMappingJackson2JsonView等。但是,您可能更喜欢使用@ExceptionHandler方法。

如果您更喜欢通过@ExceptionHandler方法编写错误内容,则可以扩展ResponseEntityExceptionHandler。这是@ControllerAdvice classes 提供@ExceptionHandler方法来处理标准 Spring MVC exceptions 和 return ResponseEntity的方便基础。这允许您使用消息转换器自定义响应并写入错误内容。有关更多详细信息,请参阅ResponseEntityExceptionHandler javadocs。

22.11.4 使用 @ResponseStatus 注释业务 Exceptions

可以使用@ResponseStatus注释业务 exception。引发 exception 时,ResponseStatusExceptionResolver通过相应地设置响应的状态来处理它。默认情况下,DispatcherServlet注册ResponseStatusExceptionResolver并且可以使用。

22.11.5 自定义默认 Servlet 容器错误页面

当响应的状态设置为错误状态 code 并且响应的主体为空时,Servlet 容器通常呈现 HTML 格式的错误页面。要自定义容器的默认错误页面,可以在web.xml中声明<error-page>元素。直到 Servlet 3,该元素必须映射到特定状态 code 或 exception 类型。从 Servlet 3 开始,不需要映射错误页面,这实际上意味着指定的位置自定义默认的 Servlet 容器错误页面。

<error-page>
    <location>/error</location>
</error-page>

请注意,错误页面的实际位置可以是 JSP 页面或容器中的其他 URL,包括通过@Controller方法处理的 URL:

写入错误信息时,可以通过控制器中的请求属性访问HttpServletResponse上设置的状态 code 和错误消息:

@Controller
public class ErrorController {

    @RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public Map<String, Object> handle(HttpServletRequest request) {

        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));

        return map;
    }

}

或者在 JSP 中:

<%@ page contentType="application/json" pageEncoding="UTF-8"%>
{
    status:<%=request.getAttribute("javax.servlet.error.status_code") %>,
    reason:<%=request.getAttribute("javax.servlet.error.message") %>
}

22.12 Web 安全

Spring Security项目提供 features 以保护 web applications 免受恶意攻击。查看“CSRF 保护”,“安全响应 Headers”和“Spring MVC Integration”部分中的 reference 文档。请注意,并非所有 features 都必须使用 Spring Security 来保护 application。对于 example,只需在 configuration 中添加CsrfFilterCsrfRequestDataValueProcessor即可添加 CSRF 保护。请参阅Spring MVC Showcase以获取 example。

另一种选择是使用专用于 Web Security 的 framework。 HDIV是一个这样的 framework 并与 Spring MVC 集成。

22.13 _Convention over configuration 支持

对于很多项目来说,坚持已建立的约定并且具有合理的默认值正是它们(项目)所需要的,而 Spring Web MVC 现在明确支持约定优于 configuration。这意味着如果你建立了一组命名约定等,你可以大大减少设置处理程序映射,查看解析器,ModelAndView实例等所需的 configuration 数量。这对于问题是一个很大的好处。快速原型设计,如果您选择将其推进到 production,还可以在代码库中提供一定程度的(始终 good-to-have)一致性。

Convention-over-configuration 支持解决了 MVC 的三个核心领域:模型,视图和控制器。

22.13.1 Controller ControllerClassNameHandlerMapping

ControllerClassNameHandlerMapping class 是一个HandlerMapping implementation,它使用约定来确定请求 URL 和处理这些请求的Controller实例之间的映射。

请考虑以下简单的Controller implementation。特别注意 class 的 name。

public class ViewShoppingCartController implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
        // the implementation is not hugely important for this example...
    }

}

以下是相应的 Spring Web MVC configuration 文件的片段:

<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>

<bean id="viewShoppingCart" class="x.y.z.ViewShoppingCartController">
    <!-- inject dependencies as required... -->
</bean>

ControllerClassNameHandlerMapping查找其 application context 中定义的所有各种处理程序(或Controller)beans,并从 name 中删除Controller以定义其处理程序映射。因此,ViewShoppingCartController maps 到/viewshoppingcart*请求 URL。

让我们看一些更多的例子,以便中心 idea 立刻变得熟悉。 (注意 URL 中的全部小写,与 camel-cased Controller class names.)相反

  • WelcomeController maps 到/welcome*请求网址

  • HomeController maps 到/home*请求网址

  • IndexController maps 到/index*请求网址

  • RegisterController maps 到/register*请求网址

对于MultiActionController handler classes,生成的映射稍微复杂一些。以下示例中的Controller名称假定为MultiActionController __mplement:

  • AdminController maps 到/admin/*请求网址

  • CatalogController maps 到/catalog/*请求网址

如果遵循将Controller __mplementations 命名为xxxController的惯例,ControllerClassNameHandlerMapping将为您节省定义和维护潜在的 looooong SimpleUrlHandlerMapping(或类似)的乏味。

ControllerClassNameHandlerMapping class 扩展了AbstractHandlerMapping base class,因此您可以像许多其他HandlerMapping implementations 一样定义HandlerInterceptor实例和其他所有内容。

22.13.2 Model ModelMap(ModelAndView)

ModelMap class 本质上是一个美化的Map,它可以使添加 objects 显示在View(或上)View遵循 common 命名约定。考虑以下Controller implementation;注意 object 被添加到ModelAndView而没有指定任何关联的 name。

public class DisplayShoppingCartController implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {

        List cartItems = // get a List of CartItem objects
        User user = // get the User doing the shopping

        ModelAndView mav = new ModelAndView("displayShoppingCart"); <-- the logical view name

        mav.addObject(cartItems); <-- look ma, no name, just the object
        mav.addObject(user); <-- and again ma!

        return mav;
    }
}

ModelAndView class 使用ModelMap class,它是一个自定义Map implementation,当 object 添加到 object 时会自动为 object 生成 key。在标量 object(例如User)的情况下,确定添加的 object 的 name 的策略是使用 object 的 class 的 short class name。以下示例是为放入ModelMap实例的标量 objects 生成的名称。

  • 添加的x.y.User实例将生成 name user

  • 添加的x.y.Registration实例将生成 name registration

  • 添加的x.y.Foo实例将生成 name foo

  • 添加的java.util.HashMap实例将生成 name hashMap。在这种情况下,您可能希望明确 name,因为hashMap不是直观的。

  • 添加null将导致抛出IllegalArgumentException。如果要添加的 object(或 objects)可能是null,那么您还需要明确 name。


什么,没有自动复数?

Spring Web MVC 的 convention-over-configuration 支持不支持自动复数。也就是说,您无法将List Person objects 添加到ModelAndView并使生成的 name 为people

这个决定是经过一番辩论后做出的,最后的“最小惊喜原则”获胜。


在添加SetList之后生成 name 的策略是查看集合,获取集合中第一个 object 的 short class name,并使用List附加到 name。这同样适用于数组,尽管使用数组时不必查看 array 内容。一些示例将使集合的 name 生成的语义更清晰:

  • 添加了零个或多个x.y.User元素的x.y.User[] array 将生成 name userList

  • 添加了零个或多个x.y.User元素的x.y.Foo[] array 将生成 name fooList

  • 添加了一个或多个x.y.User元素的java.util.ArrayList将生成 name userList

  • 添加了一个或多个x.y.Foo元素的java.util.HashSet将生成 name fooList

  • 根本不会添加空java.util.ArrayList(实际上,addObject(..)调用基本上是 no-op)。

22.13.3 默认视图 name

当没有显式提供这样的逻辑视图 name 时,RequestToViewNameTranslator接口确定逻辑View name。它只有一个 implementation,即DefaultRequestToViewNameTranslator class。

DefaultRequestToViewNameTranslator maps 请求逻辑视图名称的 URL,与此 example 一样:

public class RegistrationController implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
        // process the request...
        ModelAndView mav = new ModelAndView();
        // add data as necessary to the model...
        return mav;
        // notice that no View or logical view name has been set
    }

}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- this bean with the well known name generates view names for us -->
    <bean id="viewNameTranslator"
            class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator"/>

    <bean class="x.y.RegistrationController">
        <!-- inject dependencies as necessary -->
    </bean>

    <!-- maps request URLs to Controller names -->
    <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

请注意,在handleRequest(..)方法的 implementation 中,如何在返回的ModelAndView上设置没有View或逻辑视图 name。 DefaultRequestToViewNameTranslator的任务是从请求的 URL 生成逻辑视图 name。在上述RegistrationController(与ControllerClassNameHandlerMapping一起使用)的情况下,http://localhost/registration.html的请求 URL 导致DefaultRequestToViewNameTranslator生成registration的逻辑视图 name。然后,InternalResourceViewResolver bean 将此逻辑视图 name 解析为/WEB-INF/jsp/registration.jsp视图。

您不需要显式定义DefaultRequestToViewNameTranslator bean。如果您喜欢DefaultRequestToViewNameTranslator的默认设置,则可以依赖 Spring Web MVC DispatcherServlet来实例化此 class 的实例(如果未明确配置)。

当然,如果您需要更改默认设置,那么您需要明确配置自己的DefaultRequestToViewNameTranslator bean。有关可配置的各种 properties 的详细信息,请参阅全面的DefaultRequestToViewNameTranslator javadocs。

22.14 HTTP 缓存支持

一个好的 HTTP 缓存策略可以显着改善 web application 的 performance 及其 clients 的体验。 'Cache-Control' HTTP 响应头主要负责这一点,以及'Last-Modified''ETag'等条件头。

'Cache-Control' HTTP 响应头建议私有缓存(e.g. 浏览器)和公共缓存(e.g. 代理)如何缓存 HTTP 响应以便进一步重用。

ETag(实体标记)是由 HTTP/1.1 兼容的 web 服务器返回的 HTTP 响应头,用于确定给定 URL 的内容更改。它可以被认为是Last-Modified标头的更复杂的后继者。当服务器返回带有 ETag 标头的表示时,client 可以在If-None-Match标头中的后续 GET 中使用此标头。如果内容未更改,则服务器返回304: Not Modified

本节介绍在 Spring Web MVC application 中配置 HTTP 缓存的不同选择。

22.14.1 Cache-Control HTTP 标头

Spring Web MVC 支持许多用例和方法来为 application 配置“Cache-Control”headers。虽然RFC 7234 第 5.2.2 节完全描述了该标题及其可能的指令,但有几种方法可以解决最常见的情况。

Spring Web MVC 在其几个 API 中使用 configuration 约定:setCachePeriod(int seconds)

  • -1 value 不会生成'Cache-Control'响应头。

  • 0 value 将使用'Cache-Control: no-store'指令阻止缓存。

  • n > 0 value 将使用'Cache-Control: max-age=n'指令缓存给定的响应n秒。

CacheControl builder class 简单地描述了可用的“Cache-Control”指令,并使得构建自己的 HTTP 缓存策略变得更容易。构建之后,可以在几个 Spring Web MVC API 中接受CacheControl实例作为参数。

// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS)
                                    .noTransform().cachePublic();

22.14.2 HTTP 缓存支持静态资源

静态资源应该与适当的'Cache-Control'和条件 headers 一起提供,以获得最佳的 performance。 配置 ResourceHttpRequestHandler用于提供静态资源不仅通过读取文件的元数据本地写入'Last-Modified' headers,而且如果配置正确则'Cache-Control' headers。

您可以在ResourceHttpRequestHandler上设置cachePeriod属性或使用CacheControl实例,该实例支持更具体的指令:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public-resources/")
                .setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());
    }

}

在 XML 中:

<mvc:resources mapping="/resources/**" location="/public-resources/">
    <mvc:cache-control max-age="3600" cache-public="true"/>
</mvc:resources>

22.14.3 支持控制器中的 Cache-Control,ETag 和 Last-Modified 响应 headers

控制器可以支持'Cache-Control''ETag',and/or 'If-Modified-Since' HTTP 请求;如果要在响应上设置'Cache-Control'标头,确实建议这样做。这涉及为给定请求计算 lastModified long and/or Etag value,将其与'If-Modified-Since'请求标头 value 进行比较,并可能返回状态为 code 304(未修改)的响应。

如名为“使用 HttpEntity”的部分中所述,控制器可以使用HttpEntity类型与 request/response 进行交互。返回ResponseEntity的控制器可以在响应中包含 HTTP 缓存信息,如下所示:

@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {

    Book book = findBook(id);
    String version = book.getVersion();

    return ResponseEntity
                .ok()
                .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
                .eTag(version) // lastModified is also available
                .body(book);
}

这样做不仅会在响应中包含'ETag''Cache-Control' headers,它还会将响应转换为带有空体的 HTTP 304 Not Modified 响应如果 client match 发送的条件_header 由此设置的缓存信息控制器。

@RequestMapping方法也可能希望支持相同的行为。这可以通过以下方式实现:

@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {

    long lastModified = // 1. application-specific calculation

    if (request.checkNotModified(lastModified)) {
        // 2. shortcut exit - no further processing necessary
        return null;
    }

    // 3. or otherwise further request processing, actually preparing content
    model.addAttribute(...);
    return "myViewName";
}

这里有两个 key 元素:调用request.checkNotModified(lastModified)并返回null。前者在返回true之前设置适当的响应状态和 headers。后者与前者相结合,导致 Spring MVC 不再对请求进行处理。

请注意,有 3 种变体:

  • request.checkNotModified(lastModified)将 lastModified 与'If-Modified-Since''If-Unmodified-Since'请求标头进行比较

  • request.checkNotModified(eTag)将 eTag 与'If-None-Match'请求标头进行比较

  • request.checkNotModified(eTag, lastModified)同时执行,意味着两个条件都应该有效

当收到条件'GET'/'HEAD'请求时,checkNotModified将检查资源是否未被修改,如果是,则将导致HTTP 304 Not Modified响应。在条件'POST'/'PUT'/'DELETE'请求的情况下,checkNotModified将检查资源是否未被修改,如果已经修改,则将导致HTTP 409 Precondition Failed响应以防止并发修改。

22.14.4 浅 ETag 支持

Servlet 过滤器ShallowEtagHeaderFilter提供对 ETag 的支持。它是一个普通的 Servlet 过滤器,因此可以与任何 web framework 结合使用。 ShallowEtagHeaderFilter过滤器创建 so-called 浅 ETag(与深 ETag 相反,更多关于 later).The 过滤器缓存渲染的 JSP(或其他内容)的内容,生成 MD5 哈希,并将其作为响应中的 ETag 头返回。 __ient 发送对同一资源的请求的下一个 time,它使用该散列作为If-None-Match value。过滤器检测到这一点,再次呈现视图,并比较两个哈希值。如果它们相等,则返回304

请注意,此策略可以节省网络带宽,但不能节省 CPU,因为必须为每个请求计算完整响应。控制器 level(如上所述)的其他策略可以节省网络带宽并避免计算。

此过滤器有一个writeWeakETag参数,用于将过滤器配置为写入弱 ETag,如下所示:W/"02a2d595e6ed9a0b24f027f2b63b134d6",如RFC 7232 第 2.3 节中所定义。

您在web.xml中配置ShallowEtagHeaderFilter

<filter>
    <filter-name>etagFilter</filter-name>
    <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
    <!-- Optional parameter that configures the filter to write weak ETags
    <init-param>
        <param-name>writeWeakETag</param-name>
        <param-value>true</param-value>
    </init-param>
    -->
</filter>

<filter-mapping>
    <filter-name>etagFilter</filter-name>
    <servlet-name>petclinic</servlet-name>
</filter-mapping>

或者在 Servlet 3.0 环境中,

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new ShallowEtagHeaderFilter() };
    }

}

有关详细信息,请参阅第 22.15 节,“Code-based Servlet 容器初始化”。

22.15 Code-based Servlet 容器初始化

在 Servlet 3.0 环境中,您可以选择以编程方式配置 Servlet 容器作为替代方法或与web.xml文件组合使用。下面是注册DispatcherServlet的示例:

import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }

}

WebApplicationInitializer是 Spring MVC 提供的接口,可确保检测到 implementation 并自动用于初始化任何 Servlet 3 容器。名为AbstractDispatcherServletInitializerWebApplicationInitializer的抽象 base class implementation 使得通过简单地重写方法来指定 servlet 映射和DispatcherServlet configuration 的位置来更容易注册DispatcherServlet

对于使用 Java-based Spring configuration 的 applications,建议使用此方法:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { MyWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

如果使用 XML-based Spring configuration,则应直接从AbstractDispatcherServletInitializer扩展:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

AbstractDispatcherServletInitializer还提供了一种方便的方法来添加Filter实例并让它们自动映射到DispatcherServlet

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }

}

每个过滤器根据其具体类型添加默认 name 并自动映射到DispatcherServlet

AbstractDispatcherServletInitializerisAsyncSupported protected 方法提供了一个单独的位置来启用DispatcherServlet上的异步支持以及映射到它的所有过滤器。默认情况下,此 flag 设置为true

最后,如果您需要进一步自定义DispatcherServlet本身,则可以覆盖createDispatcherServlet方法。

22.16 配置 Spring MVC

Section 22.2.1,“WebApplicationContext 中的特殊 Bean 类型”和Section 22.2.2,“Default DispatcherServlet Configuration”解释了 Spring MVC 的特殊 beans 和DispatcherServlet使用的默认 implementations。在本节中,您将了解配置 Spring MVC 的另外两种方法。即 MVC Java 配置和 MVC XML 命名空间。

MVC Java 配置和 MVC 命名空间提供类似的默认 configuration,它会覆盖DispatcherServlet默认值。目标是使大多数应用程序不必创建相同的 configuration,并且还提供用于配置 Spring MVC 的 higher-level 构造,这些构造用作一个简单的起点,并且需要很少或根本不需要基础 configuration 的知识。

您可以根据自己的喜好选择 MVC Java 配置或 MVC 命名空间。另外,正如您将在下面看到的,使用 MVC Java 配置,更容易看到底层的 configuration 以及直接对创建的 Spring MVC beans 进行 fine-grained 自定义。但是,让我们从头开始。

22.16.1 启用 MVC Java Config 或 MVC XML 命名空间

要启用 MVC Java 配置,请将 annotation @EnableWebMvc添加到@Configuration classes 中的一个:

@Configuration
@EnableWebMvc
public class WebConfig {
}

要在 XML 中实现相同的功能,请使用 DispatcherServlet context 中的mvc:annotation-driven元素(如果未定义 DispatcherServlet context,则使用 root context 中的mvc:annotation-driven元素):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven/>

</beans>

上面注册了一个RequestMappingHandlerMapping,一个RequestMappingHandlerAdapter和一个ExceptionHandlerExceptionResolver(以及其他),以支持使用带注释的控制器方法处理请求,使用注释,如@RequestMapping@ExceptionHandler等。

它还支持以下功能:

  • 除了用于 Data Binding 的 JavaBeans PropertyEditors 之外,Spring 3 样式类型转换通过ConversionService实例。

  • 通过ConversionService使用@NumberFormat annotation 支持格式化 Number 字段。

  • 使用@DateTimeFormat annotation 支持格式化 DateCalendarLong和 Joda-Time 字段。

  • 如果 classpath 上存在 JSR-303 Provider,则使用@Valid支持证实 @Controller输入。

  • HttpMessageConverter支持@RequestBody方法参数和@ResponseBody方法 return 值来自@RequestMapping@ExceptionHandler方法。

这是由 mvc:annotation-driven 设置的 HttpMessageConverters 的完整列表:

  • ByteArrayHttpMessageConverter转换字节数组。

  • StringHttpMessageConverter转换 strings。

  • ResourceHttpMessageConverter为所有媒体类型转换 to/from org.springframework.core.io.Resource

  • SourceHttpMessageConverter将 to/from 转换为javax.xml.transform.Source

  • FormHttpMessageConverter转换表单数据 to/from a MultiValueMap<String, String>

  • Jaxb2RootElementHttpMessageConverter转换 Java objects to/from XML - 如果存在 JAXB2 并且 classpath 上不存在 Jackson 2 XML 扩展,则添加。

  • MappingJackson2HttpMessageConverter转换 to/from JSON - 如果 classpath 上存在 Jackson 2 则添加。

  • MappingJackson2XmlHttpMessageConverter转换 to/from XML - 如果 class 路径上存在Jackson 2 XML 扩展则添加。

  • AtomFeedHttpMessageConverter转换 Atom 提要 - 如果 classpath 上存在 Rome,则添加。

  • RssChannelHttpMessageConverter转换 RSS 提要 - 如果 classpath 上存在 Rome,则添加。

有关如何自定义这些默认转换器的详细信息,请参阅第 22.16.12 节,“消息转换器”。

Jackson JSON 和 XML 转换器是使用Jackson2ObjectMapperBuilder in order 创建的ObjectMapper实例创建的,以提供更好的默认配置。

此构建器使用以下内容自定义 Jackson 的默认 properties:

  • DeserializationFeature.FAIL_ON_UNKNOWNPROPERTIES已禁用。

  • MapperFeature.DEFAULT_VIEW_INCLUSION已禁用。

如果在 classpath 上检测到以下 well-known 模块,它还会自动注册:

  • jackson-datatype-jdk7:支持 Java 7 类型,如java.nio.file.Path

  • jackson-datatype-joda:支持 Joda-Time 类型。

  • jackson-datatype-jsr310:支持 Java 8 Date 和 Time API 类型。

  • jackson-datatype-jdk8:支持其他 Java 8 类型,如Optional

22.16.2 自定义提供的 Configuration

要在 Java 中自定义默认的 configuration,您只需实现WebMvcConfigurer接口,或者更可能扩展 class WebMvcConfigurerAdapter并覆盖您需要的方法:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    // Override configuration methods...
}

要自定义<mvc:annotation-driven/>的默认 configuration,请检查它支持的属性和 sub-elements。您可以查看Spring MVC XML schema或使用 IDE 的 code completion feature 来发现可用的属性和 sub-elements。

22.16.3 转换和格式化

默认情况下,会安装NumberDate类型的格式化程序,包括对@NumberFormat@DateTimeFormat 注释的支持。如果 class 路径上存在 Joda-Time,则还会安装对 Joda-Time 格式 library 的完全支持。要注册自定义格式化程序和转换器,请覆盖addFormatters方法:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // ...
    }
}

在 MVC 命名空间中,添加<mvc:annotation-driven>时应用相同的默认值。要注册自定义格式化程序和转换器,只需提供ConversionService

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven conversion-service="conversionService"/>

    <bean id="conversionService"
            class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="org.example.MyConverter"/>
            </set>
        </property>
        <property name="formatters">
            <set>
                <bean class="org.example.MyFormatter"/>
                <bean class="org.example.MyAnnotationFormatterFactory"/>
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.example.MyFormatterRegistrar"/>
            </set>
        </property>
    </bean>

</beans>

有关何时使用 FormatterRegistrars 的更多信息,请参阅第 9.6.4 节,“FormatterRegistrar SPI”和FormattingConversionServiceFactoryBean

22.16.4 验证

Spring 提供验证器接口,可用于 application 的所有层中的验证。在 Spring MVC 中,您可以将其配置为 global Validator实例,以便在遇到@Valid@Validated控制器方法参数时使用,and/or 作为控制器中的本地Validator通过@InitBinder方法。 Global 和 local 验证器实例可以组合在一起以提供复合验证。

Spring 还通过LocalValidatorFactoryBean进行支持 JSR-303/JSR-349 Bean 验证,使 Spring org.springframework.validation.Validator接口适应 Bean 验证javax.validation.Validator contract。这个 class 可以作为 global 验证器插入 Spring MVC,如下所述。

默认情况下,当在 classpath 上检测到 Bean 验证提供程序(如 Hibernate Validator)时,使用@EnableWebMvc<mvc:annotation-driven>会自动在 Spring MVC 中通过LocalValidatorFactoryBean注册 Bean 验证支持。

有时将LocalValidatorFactoryBean注入控制器或另一个 class 是很方便的。最简单的方法是声明自己的@Bean并在 order 中使用@Primary标记它,以避免与 MVC Java 配置提供的冲突。

如果您更喜欢使用 MVC Java 配置中的那个,则需要从WebMvcConfigurationSupport覆盖mvcValidator方法并声明该方法显式 return LocalValidatorFactory而不是Validator。有关如何切换以扩展提供的 configuration 的信息,请参阅部分 22.16.13,“使用 MVC Java Config 进行高级自定义”。

或者,您可以配置自己的 global Validator实例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public Validator getValidator(); {
        // return "global" validator
    }
}

并在 XML 中:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven validator="globalValidator"/>

</beans>

要将 global 与本地验证相结合,只需添加一个或多个本地 validator(s):

@Controller
public class MyController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(new FooValidator());
    }

}

使用此最小配置任何 time 遇到@Valid@Validated方法参数时,它将由配置的验证器验证。任何验证违规都将自动作为方法参数在BindingResult中作为错误公开,并且在 Spring MVC HTML 视图中也可以呈现。

22.16.5 拦截器

您可以将HandlerInterceptorsWebRequestInterceptors配置为应用于所有传入请求或限制为特定 URL 路径模式。

Java 中注册拦截器的示例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleInterceptor());
        registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
        registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
    }

}

在 XML 中使用<mvc:interceptors>元素:

<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/admin/**"/>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/secure/*"/>
        <bean class="org.example.SecurityInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

22.16.6 内容谈判

您可以配置 Spring MVC 如何根据请求确定所请求的媒体类型。可用选项是检查文件扩展名的 URL 路径,检查“Accept”标头,特定查询参数,或在没有请求任何内容时回退到默认 content type。默认情况下,首先检查请求 URI 中的路径扩展,然后选中“Accept”标头。

如果相应的依赖项在 classpath 上,则 MVC Java 配置和 MVC 命名空间默认注册jsonxmlrssatom。其他路径 extension-to-media 类型映射也可以明确注册,并且还具有将它们列为安全 extensions 以便进行 RFD 攻击检测的效果(有关详细信息,请参阅“Suffix Pattern Matching and RFD”一节)。

下面是通过 MVC Java 配置自定义内容 negotiation 选项的示例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.mediaType("json", MediaType.APPLICATION_JSON);
    }
}

在 MVC 命名空间中,<mvc:annotation-driven>元素具有content-negotiation-manager属性,该属性需要ContentNegotiationManager,而ContentNegotiationManager又可以使用ContentNegotiationManagerFactoryBean创建:

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="mediaTypes">
        <value>
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

如果不使用 MVC Java 配置或 MVC 命名空间,则需要创建ContentNegotiationManager的实例并使用它来配置RequestMappingHandlerMapping以进行请求映射,并使用RequestMappingHandlerAdapterExceptionHandlerExceptionResolver来进行内容协商。

注意,ContentNegotiatingViewResolver现在也可以配置ContentNegotiationManager,因此您可以在整个 Spring MVC 中使用一个共享实例。

在更高级的情况下,配置多个ContentNegotiationManager实例可能会很有用,这些实例又可能包含自定义ContentNegotiationStrategy __mplement。对于 example,您可以使用ContentNegotiationManager配置ExceptionHandlerExceptionResolver,始终将请求的媒体类型解析为"application/json"。或者,如果没有请求内容类型,您可能希望插入具有某种逻辑的自定义策略来选择默认 content type(e.g. XML 或 JSON)。

22.16.7 查看控制器

这是一个快捷方式,用于定义在调用时立即转发到视图的ParameterizableViewController。在视图生成响应之前,如果没有要执行的 Java 控制器逻辑,请在静态情况下使用它。

"/"请求转发到 Java 中名为"home"的视图的示例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");
    }
}

在 XML 中使用<mvc:view-controller>元素:

<mvc:view-controller path="/" view-name="home"/>

22.16.8 查看解析器

MVC 配置简化了视图解析器的注册。

以下是 Java config example,它使用 FreeMarker HTML 模板配置内容协商视图解析,并将 Jackson 配置为 JSON 渲染的默认View

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.jsp();
    }
}

在 XML 中也一样:

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:jsp/>
</mvc:view-resolvers>

但请注意,FreeMarker,Velocity,Tiles,Groovy Markup 和脚本模板也需要 configuration 底层视图技术。

MVC 名称空间提供专用元素。对于使用 FreeMarker 的 example:

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:freemarker cache="false"/>
</mvc:view-resolvers>

<mvc:freemarker-configurer>
    <mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>

在 Java 配置中只需添加相应的“Configurer”bean:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.freeMarker().cache(false);
    }

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("/WEB-INF/");
        return configurer;
    }
}

22.16.9 资源服务

此选项允许特定 URL pattern 之后的静态资源请求由Resource位置中的任何一个ResourceHttpRequestHandler提供。这提供了一种方便的方法来从 web application 根目录以外的位置提供静态资源,包括 classpath 上的位置。 cache-period property 可用于设置远期未来的到期日期(1 年是 Page Speed 和 YSlow 等优化工具的推荐),以便 client 可以更有效地利用它们。处理程序还可以正确评估Last-Modified标头(如果存在),以便在适当时返回304 status code,从而避免对已经由 client 缓存的资源进行不必要的开销。例如,要使用 web application 根目录中public-resources目录的

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/");
    }

}

在 XML 中也一样:

<mvc:resources mapping="/resources/**" location="/public-resources/"/>

要在 1-year 将来到期时提供这些资源,以确保最大限度地使用浏览器缓存并减少浏览器发出的 HTTP 请求:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/").setCachePeriod(31556926);
    }

}

在 XML 中:

<mvc:resources mapping="/resources/**" location="/public-resources/" cache-period="31556926"/>

有关更多详细信息,请参阅HTTP 缓存支持静态资源。

mapping属性必须是SimpleUrlHandlerMapping可以使用的 Ant pattern,location属性必须指定一个或多个有效的资源目录位置。可以使用 comma-separated 值列表指定多个资源位置。指定的位置将在指定的 order 中检查是否存在任何给定请求的资源。例如,要在 classpath 上的任何 jar 中启用 web application 根目录和/META-INF/public-web-resources/的已知路径的资源,请使用:

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/", "classpath:/META-INF/public-web-resources/");
    }

}

在 XML 中:

<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/public-web-resources/"/>

当提供可能在部署 version 的新 version 时可能发生更改的资源时,建议您将 version string 合并到用于请求资源的映射 pattern 中,以便强制 clients 请求 application 资源的新部署 version。版本化 URL 的支持内置于 framework 中,可以通过在资源处理程序上配置资源链来启用。该链包含一个ResourceResolver实例,后跟一个或多个ResourceTransformer实例。它们可以一起提供任意分辨率和资源转换。

built-in VersionResourceResolver可以配置不同的策略。对于 example,FixedVersionStrategy可以使用 property,date 或其他作为 version。 ContentVersionStrategy使用根据资源内容计算的 MD5 哈希(称为“指纹识别”URL)。请注意,VersionResourceResolver将在提供资源时自动将已解析的 version strings 用作 HTTP ETag 标头值。

ContentVersionStrategy是一个很好的默认选择,除非它无法使用(e.g. 使用 JavaScript 模块加载器)。您可以针对不同的模式配置不同的 version 策略,如下所示。还要记住,计算 content-based 版本很昂贵,因此应该在 production 中启用资源链缓存。

Java config example;

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public-resources/")
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
    }
}

XML example:

<mvc:resources mapping="/resources/**" location="/public-resources/">
	<mvc:resource-chain>
		<mvc:resource-cache/>
		<mvc:resolvers>
			<mvc:version-resolver>
				<mvc:content-version-strategy patterns="/**"/>
			</mvc:version-resolver>
		</mvc:resolvers>
	</mvc:resource-chain>
</mvc:resources>

在上述工作中,application 还必须使用版本呈现 URL。最简单的方法是配置ResourceUrlEncodingFilter,它包装响应并覆盖其encodeURL方法。这将适用于 JSP,FreeMarker,Velocity 以及 calls 响应encodeURL方法的任何其他视图技术。或者,application 也可以 inject 并直接使用ResourceUrlProvider bean,它是使用 MVC Java 配置和 MVC 命名空间自动声明的。

_j也支持 Webjars,当"org.webjars:webjars-locator" library 位于 classpath 时会自动注册。此解析器允许资源链从 HTTP GET 请求解析 version 不可知 libraries "GET /jquery/jquery.min.js"将 return 资源"/jquery/1.2.0/jquery.min.js"。它还可以通过重写模板<script src="/jquery/jquery.min.js"/> → <script src="/jquery/1.2.0/jquery.min.js"/>中的资源 URL 来工作。

22.16.10 默认 Servlet

这允许将DispatcherServlet映射到“/”(从而覆盖容器的默认 Servlet 的映射),同时仍允许容器的默认 Servlet 处理静态资源请求。它使用“/ **”的 URL 映射配置DefaultServletHttpRequestHandler,并且相对于其他 URL 映射具有最低优先级。

此处理程序将所有请求转发到默认的 Servlet。因此,它必须保持在所有其他 URL HandlerMappings的 order 中的最后一个。如果您使用<mvc:annotation-driven>,或者如果您正在设置自己的自定义HandlerMapping实例,请确保将order property 设置为低于DefaultServletHttpRequestHandler的 value,即Integer.MAX_VALUE

要使用默认设置启用 feature,请使用:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

或者用 XML:

<mvc:default-servlet-handler/>

覆盖“/”Servlet 映射的警告是,必须通过 name 而不是路径来检索默认 Servlet 的RequestDispatcherDefaultServletHttpRequestHandler将使用大多数主要 Servlet 容器(包括 Tomcat,Jetty,GlassFish,JBoss,Resin,WebLogic 和 WebSphere)的已知名称列表,在启动 time 时尝试 auto-detect 容器的默认 Servlet。如果默认 Servlet 已使用不同的 name 进行自定义配置,或者在默认 Servlet name 未知的情况下使用了不同的 Servlet 容器,则必须显式提供默认的 Servlet 的 name,如下面的 example:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable("myCustomDefaultServlet");
    }

}

或者用 XML:

<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>

22.16.11 路径匹配

这允许自定义与 URL 映射和路径匹配相关的各种设置。有关各个选项的详细信息,请查看PathMatchConfigurer API。

下面是 Java 配置中的一个 example:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer
            .setUseSuffixPatternMatch(true)
            .setUseTrailingSlashMatch(false)
            .setUseRegisteredSuffixPatternMatch(true)
            .setPathMatcher(antPathMatcher())
            .setUrlPathHelper(urlPathHelper());
    }

    @Bean
    public UrlPathHelper urlPathHelper() {
        //...
    }

    @Bean
    public PathMatcher antPathMatcher() {
        //...
    }

}

在 XML 中也一样,使用<mvc:path-matching>元素:

<mvc:annotation-driven>
    <mvc:path-matching
        suffix-pattern="true"
        trailing-slash="false"
        registered-suffixes-only="true"
        path-helper="pathHelper"
        path-matcher="pathMatcher"/>
</mvc:annotation-driven>

<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>

22.16.12 消息转换器

如果要替换 Spring MVC 创建的默认转换器,或者如果您只想自定义它们或者将其他转换器添加到默认转换器,则可以通过覆盖configureMessageConverters()来实现HttpMessageConverter的自定义。

下面是一个 example,它将 Jackson JSON 和 XML 转换器添加为自定义ObjectMapper而不是默认值:

@Configuration
@EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
                .modulesToInstall(new ParameterNamesModule());
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
        converters.add(new MappingJackson2XmlHttpMessageConverter(builder.xml().build()));
    }

}

在此 example 中,Jackson2ObjectMapperBuilder用于为启用缩进的MappingJackson2HttpMessageConverterMappingJackson2XmlHttpMessageConverter创建 common configuration,自定义 date 格式以及jackson-module-parameter-names的注册,这增加了对访问参数名称的支持(在 Java 8 中添加了 feature)。

使用 Jackson XML 支持启用缩进除了jackson-dataformat-xml之外还需要woodstox-core-asl依赖。

其他有趣的 Jackson 模块可用:

  • jackson-datatype-money:支持javax.money类型(非官方模块)

  • jackson-datatype-hibernate:支持 Hibernate 特定类型和 properties(包括 lazy-loading 方面)

也可以在 XML 中执行相同的操作:

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper"/>
        </bean>
        <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
            <property name="objectMapper" ref="xmlMapper"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
      p:indentOutput="true"
      p:simpleDateFormat="yyyy-MM-dd"
      p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>

<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>

22.16.13 使用 MVC Java 配置进行高级自定义

从上面的示例中可以看出,MVC Java 配置和 MVC 命名空间提供了更高级别的构造,这些构造不需要深入了解为您创建的基础 beans。相反,它可以帮助您专注于您的 application 需求。但是,在某些时候您可能需要更多的 fine-grained 控制,或者您可能只是想了解底层的 configuration。

朝向更多 fine-grained 控制的第一个步骤是查看为您创建的基础 beans。在 MVC Java 配置中,您可以在WebMvcConfigurationSupport中看到 javadoc 和@Bean方法。此 class 中的 configuration 将通过@EnableWebMvc annotation 自动导入。事实上,如果你打开@EnableWebMvc,你可以看到@Import语句。

更多 fine-grained 控制的下一个步骤是在WebMvcConfigurationSupport中创建的 beans 之一上自定义 property,或者可能提供自己的实例。这需要两件事 - 删除 order 中的@EnableWebMvc annotation 以防止 import,然后从DelegatingWebMvcConfiguration(WebMvcConfigurationSupport的子类)扩展。这是一个 example:

@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {

    @Override
    public void addInterceptors(InterceptorRegistry registry){
        // ...
    }

    @Override
    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        // Create or let "super" create the adapter
        // Then customize one of its properties
    }

}

一个 application 应该只有一个 configuration 扩展DelegatingWebMvcConfiguration或一个@EnableWebMvc注释 class,因为它们都注册了相同的底层 beans。

以这种方式修改 beans 并不会阻止您使用本节前面所示的任何 higher-level 构造。 WebMvcConfigurerAdapter子类和WebMvcConfigurer __mplement 仍在使用中。

22.16.14 使用 MVC 命名空间进行高级自定义

Fine-grained 控制为您创建的 configuration 对于 MVC 命名空间来说有点难度。

如果您确实需要这样做,而不是复制它提供的 configuration,请考虑配置一个BeanPostProcessor来检测您想要按类型自定义的 bean,然后根据需要修改其 properties。例如:

@Component
public class MyPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
        if (bean instanceof RequestMappingHandlerAdapter) {
            // Modify properties of the adapter
        }
    }

}

请注意,MyPostProcessor需要包含在<component scan/> in order 中以便检测它,或者如果您愿意,可以使用 XML bean 声明显式声明它。