22. Web MVC框架

22.1 Spring Web MVC框架简介

Spring Web模型 - 视图 - 控制器(MVC)框架围绕 DispatcherServlet 设计,可以向处理程序发送请求,具有可配置的处理程序映射,视图解析,区域设置,时区和主题解析以及对上载文件的支持。默认处理程序基于 @Controller@RequestMapping 注释,提供各种灵活的处理方法。随着Spring 3.0的引入, @Controller 机制还允许您通过 @PathVariable 注释和其他功能创建RESTful网站和应用程序。


"Open for extension…" Spring Web MVC和Spring中的一个关键设计原则是“Open for extension,closed for modification”原则。

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

有关此原理的说明,请参阅Seth Ladd等人的Expert Spring Web MVC和Web Flow;具体请参见第一版第117页的 "A Look At Design," 部分。或者,请参阅

使用Spring MVC时,无法向最终方法添加建议。例如,您无法向 AbstractController.setSynchronizeOnSession() 方法添加建议。有关AOP代理的更多信息以及无法向最终方法添加建议的原因,请参阅 Section 11.6.1, “Understanding AOP proxies”


在Spring Web MVC中,您可以将任何对象用作命令或表单支持对象;您不需要实现特定于框架的接口或基类。 Spring的数据绑定非常灵活:例如,它将类型不匹配视为可由应用程序评估的验证错误,而不是系统错误。因此,您不需要将业务对象的属性复制为表单对象中的简单无类型字符串,只需处理无效提交或正确转换字符串。相反,通常最好直接绑定到业务对象。

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

22.1.1 Spring Web MVC的特性


Spring Web Flow

Spring Web Flow(SWF)旨在成为Web应用程序页面流管理的最佳解决方案。

SWF在Servlet和Portlet环境中与Spring MVC和JSF等现有框架集成。如果您的业务流程(或流程)可以从会话模型中受益而不是纯粹的请求模型,那么SWF可能就是解决方案。

SWF允许您将逻辑页面流捕获为可在不同情况下重用的自包含模块,因此非常适合构建Web应用程序模块,以引导用户完成驱动业务流程的受控导航。

有关SWF的更多信息,请参阅 Spring Web Flow website


Spring的Web模块包含许多独特的Web支持功能:

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

  • 将框架和应用程序类强大而直接地配置为JavaBeans。此配置功能包括跨上下文轻松引用,例如从Web控制器到业务对象和验证器。

  • 适应性,非侵入性和灵活性。定义您需要的任何控制器方法签名,可能使用给定方案之一的参数注释之一(例如@RequestParam,@ RequestHeader,@ PathVariable等)。

  • 可重复使用的业务代码,无需重复。将现有业务对象用作命令或表单对象,而不是镜像它们以扩展特定的框架基类。

  • 可自定义的绑定和验证。键入不匹配作为应用程序级验证错误,这些错误会保留违规值,本地化日期和数字绑定等,而不是通过手动解析和转换为业务对象的仅限字符串的表单对象。

  • 可自定义的处理程序映射和视图分辨率。处理程序映射和视图解析策略的范围从简单的基于URL的配置到复杂的,专用的解析策略。 Spring比授权特定技术的Web MVC框架更灵活。

  • 灵活的模型转移。具有名称/值的模型传输 Map 支持与任何视图技术的轻松集成。

  • 可自定义的语言环境,时区和主题解析,支持带或不带Spring标记库的JSP,支持JSTL,支持Velocity而无需额外的桥接,等等。

  • 一个简单而强大的JSP标记库,称为Spring标记库,它为数据绑定和主题等功能提供支持。自定义标记在标记代码方面具有最大的灵活性。有关标记库描述符的信息,请参阅 Headers 为 Chapter 43, spring JSP Tag Library 的附录。

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

  • 生命周期范围限定为当前HTTP请求或HTTP Session 的Bean。这不是Spring MVC本身的特定功能,而是Spring MVC使用的 WebApplicationContext 容器。这些bean作用域在 Section 7.5.4, “Request, session, global session, application, and WebSocket scopes” 中描述

22.1.2 其他MVC实现的可插入性

对于某些项目,非Spring MVC实现是更可取的。许多团队希望利用他们在技能和工具方面的现有投资,例如使用JSF。

如果您不想使用Spring的Web MVC,但打算利用Spring提供的其他解决方案,您可以轻松地将您选择的Web MVC框架与Spring集成。只需通过 ContextLoaderListener 启动Spring根应用程序上下文,然后从任何操作对象中通过其 ServletContext 属性(或Spring的相应帮助方法)访问它。不涉及 "plug-ins" ,因此不需要专门的集成。从Web层的角度来看,您只需将Spring用作库,将根应用程序上下文实例作为入口点。

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

22.2 DispatcherServlet

与许多其他Web MVC框架一样,Spring的Web MVC框架是围绕中央Servlet设计的请求驱动的,该Servlet向控制器发送请求并提供便于Web应用程序开发的其他功能。然而, Spring 的 DispatcherServlet 不仅如此。它与Spring IoC容器完全集成,因此允许您使用Spring拥有的所有其他功能。

Spring Web MVC DispatcherServlet 的请求处理工作流程如下图所示。精通模式的读者会认识到 DispatcherServlet 是 "Front Controller" 设计模式的表达(这是Spring Web MVC与许多其他领先的Web框架共享的模式)。

Figure 22.1. The request processing workflow in Spring Web MVC (high level)

mvc

DispatcherServlet 是一个实际的 Servlet (它继承自 HttpServlet 基类),因此在您的Web应用程序中声明。您需要使用URL映射映射您希望 DispatcherServlet 处理的请求。以下是Servlet 3.0环境中的标准Java EE Servlet配置:

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 开头的请求都将由名为 exampleDispatcherServlet 实例处理。

WebApplicationInitializer 是Spring MVC提供的接口,可确保检测到基于代码的配置并自动用于初始化任何Servlet 3容器。这个名为 AbstractAnnotationConfigDispatcherServletInitializer 的接口的抽象基类实现通过简单地指定其servlet映射和列出配置类来更容易注册 DispatcherServlet - 它甚至是建议设置Spring MVC应用程序的方法。有关详细信息,请参阅 Code-based Servlet container initialization

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

下面是上面基于代码的示例的 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>

Section 7.15, “Additional capabilities of the ApplicationContext” 中所述,Spring中的 ApplicationContext 实例可以作用域。在Web MVC框架中,每个_673017都有自己的 WebApplicationContext ,它继承了根 WebApplicationContext 中已定义的所有bean。根 WebApplicationContext 应包含应在其他上下文和Servlet实例之间共享的所有基础结构bean。可以在特定于servlet的作用域中重写这些继承的bean,并且可以为给定的Servlet实例定义新的作用域特定的bean。

Figure 22.2. Typical context hierarchy in Spring Web MVC

mvc context hierarchy

在初始化 DispatcherServlet 时,Spring MVC在Web应用程序的 WEB-INF 目录中查找名为[servlet-name] -servlet.xml的文件,并创建在那里定义的bean,覆盖在其中定义的任何bean的定义。全球范围。

考虑以下 DispatcherServlet Servlet配置(在 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配置,您需要在应用程序中有一个名为 /WEB-INF/golfing-servlet.xml 的文件;此文件将包含所有Spring Web MVC特定组件(bean)。您可以通过Servlet初始化参数更改此配置文件的确切位置(有关详细信息,请参阅下文)。

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

Figure 22.3. Single root context in Spring Web MVC

mvc root 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应用程序所需的一些额外功能。它与普通的 ApplicationContext 的不同之处在于它能够解析主题(参见 Section 22.9, “Using themes” ),并且它知道它与哪个Servlet相关联(通过链接到 ServletContext )。 WebApplicationContext 绑定在 ServletContext 中,并且通过在 RequestContextUtils 类上使用静态方法,如果需要访问它,可以始终查找 WebApplicationContext

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

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

Table 22.1. Special bean types in the WebApplicationContext

Bean类型说明
HandlerMapping将传入的请求映射到处理程序以及基于处理程序拦截器的预处理器和后处理器列表(处理程序拦截器)一些标准的细节因 HandlerMapping 实施而异。最流行的实现支持带注释的控制器,但也存在其他实现。
HandlerAdapter帮助 DispatcherServlet 调用映射到请求的处理程序,而不管实际调用了哪个处理程序。例如,调用带注释的控制器需要解析各种注释。因此 HandlerAdapter 的主要目的是保护 DispatcherServlet 免受这些细节的影响。
HandlerExceptionResolver将视图的异常映射到允许更复杂的异常处理代码。
ViewResolver将逻辑基于字符串的视图名称解析为实际的 View 类型。
LocaleResolverLocaleContextResolver解析客户端正在使用的区域设置以及可能的时区,以便能够提供国际化视图
ThemeResolver解析您的Web应用程序可以使用的主题,例如,提供个性化布局
MultipartResolver解析多部分请求,例如支持从HTML表单处理文件上载。
FlashMapManager存储并检索 "input" 和 "output" FlashMap ,可用于将属性从一个请求传递到另一个请求,通常是通过重定向。

22.2.2 默认DispatcherServlet配置

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

所有特殊bean都有一些合理的默认值。迟早虽然你需要定制这些bean提供的一个或多个属性。例如,将 InternalResourceViewResolver 属性的 InternalResourceViewResolver 属性配置为视图文件的父位置是很常见的。

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

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

22.2.3 DispatcherServlet处理序列

设置 DispatcherServlet 后,为特定的 DispatcherServlet 发出请求, DispatcherServlet 开始处理请求,如下所示:

  • WebApplicationContext 在请求中作为控制器和进程中的其他元素可以使用的属性进行搜索和绑定。默认情况下,它在键 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 下绑定。

  • 语言环境解析器绑定到请求,以使进程中的元素能够解析处理请求时使用的语言环境(呈现视图,准备数据等)。如果您不需要区域设置解析,则不需要它。

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

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

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

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

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

Spring DispatcherServlet 还支持返回最后修改日期,如Servlet API所指定。确定特定请求的最后修改日期的过程很简单: DispatcherServlet 查找适当的处理程序映射并测试找到的处理程序是否实现LastModified接口。如果是这样, LastModified 接口的 long getLastModified(request) 方法的值将返回给客户端。

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

Table 22.2. DispatcherServlet initialization parameters

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

22.3 实施控制器

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

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

在Github上的Spring项目Org中可用,许多Web应用程序利用本节中描述的注释支持,包括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 注释允许灵活的方法名称和签名。在此特定示例中,该方法接受 Model 并将视图名称作为 String 返回,但可以使用各种其他方法参数和返回值,如本节后面所述。 @Controller@RequestMapping 以及许多其他注释构成了Spring MVC实现的基础。本节介绍了这些注释以及它们在Servlet环境中最常用的方式。

22.3.1 使用@Controller定义控制器

@Controller 注释表示特定类用作控制器的角色。 Spring不要求您扩展任何控制器基类或引用Servlet API。但是,如果需要,您仍可以引用特定于Servlet的功能。

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

您可以使用调度程序上下文中的标准Spring bean定义显式定义带注释的控制器bean。但是, @Controller 构造型还允许自动检测,与Spring一致支持,以检测类路径中的组件类并自动注册它们的bean定义。

要启用此类带注释控制器的自动检测,请将组件扫描添加到配置中。使用spring-context架构,如以下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 注释将 /appointments 等URL映射到整个类或特定的处理程序方法。通常,类级注释将特定请求路径(或路径模式)映射到表单控制器上,其他方法级注释缩小了特定HTTP方法请求方法( "GET" , "POST" 等)或HTTP请求的主映射。参数条件。

Petcare示例中的以下示例显示了使用此批注的Spring MVC应用程序中的控制器:

@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 用于许多地方。第一种用法是在类型(类)级别,它指示此控制器中的所有处理程序方法都相对于 /appointments 路径。 get() 方法还有一个 @RequestMapping 细化:它只接受 GET 个请求,这意味着 /appointments 的HTTP GET 会调用此方法。 add() 具有类似的细化, getNewForm() 将HTTP方法和路径的定义合并为一个,因此 appointments/newGET 请求由该方法处理。

getForDay() 方法显示了 @RequestMapping :URI模板的另一种用法。 (见 the section called “URI Template Patterns” )。

不需要类级别的 @RequestMapping 。没有它,所有路径都是绝对的,而不是相对的。以下来自PetClinic示例应用程序的示例显示了使用 @RequestMapping 的多动作控制器:

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

}

以上示例未指定 GETPUTPOST 等,因为 @RequestMapping 默认映射所有HTTP方法。使用 @RequestMapping(method=GET)@GetMapping 缩小映射范围。

组成@RequestMapping变体

Spring Framework 4.3引入了以下方法级组合的 @RequestMapping 注释变体,这些变体有助于简化常见HTTP方法的映射,并更好地表达带注释的处理程序方法的语义。例如, @GetMapping 可以读作 GET @RequestMapping

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

以下示例显示了上一节中 AppointmentsController 的修改版本,该版本已使用撰写的 @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 Proxying

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

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

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

在Spring 3.1之前,类型和方法级请求映射在两个单独的阶段中进行检查 - 首先由 DefaultAnnotationHandlerMapping 选择控制器,并且实际调用的方法由 AnnotationMethodHandlerAdapter 缩小。

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

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

还有一些事情不再可能:

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

  • 依靠方法名称作为后退机制,消除两个没有显式路径映射URL路径但以其他方式相同匹配的 @RequestMapping 方法之间的歧义,例如通过HTTP方法。在新的支持类中,必须唯一地映射 @RequestMapping 方法。

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

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

URI模板模式

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

URI模板是类似URI的字符串,包含一个或多个变量名称。当您为这些变量替换值时,模板将成为URI。 proposed RFC for URI模板定义了URI的参数化方式。例如,URI模板 http://www.example.com/users/{userId} 包含变量userId。将值fred赋值给变量会产生 http://www.example.com/users/fred

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

@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}" specifies the variable name ownerId 。当控制器处理此请求时, ownerId 的值设置为在URI的相应部分中找到的值。例如,当 /owners/fred 的请求进入时, ownerId 的值为 fred

要处理@PathVariable批注,Spring MVC需要按名称查找匹配的URI模板变量。您可以在注释中指定它:@GetMapping(“/ owners / {ownerId}”)
public String findOwner(@PathVariable(“ownerId”)String theOwner,Model model){
//实现省略
或者,如果URI模板变量名称与方法参数名称匹配,则可以省略该详细信息。只要您的代码使用调试信息或Java 8上的-parameters编译器标志进行编译,Spring MVC就会匹配URI模板变量名称的方法参数名称:@GetMapping(“/ owners / {ownerId}”)
public String findOwner(@PathVariable String ownerId,Model model){
//实现省略
}

方法可以包含任意数量的 @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 注释时,将使用所有URI模板变量填充映射。

可以从类型和方法级别@RequestMapping注释组装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 。您还可以注册支持解析其他数据类型。见 the section called “Method Parameters And Type Conversion”the section called “Customizing WebDataBinder initialization”

具有正则表达式的

URI模板模式

有时您需要更精确地定义URI模板变量。考虑URL "/spring-web/spring-web-3.0.5.jar" 。你如何将其分解为多个部分?

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

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

路径模式

除了URI模板之外, @RequestMapping 注释和所有组合的 @RequestMapping 变体也支持Ant样式的路径模式(例如, /myPath/*.do )。还支持URI模板变量和Ant样式globs的组合(例如 /owners/*/pets/{petId} )。

路径模式比较

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

具有较低URI变量和通配符计数的模式被认为更具体。例如 /hotels/{hotel}/* 有1个URI变量和1个外卡,被认为比 /hotels/{hotel}/** 更具体,它是1个URI变量和2个外卡。

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

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

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

  • default mapping pattern /** 没有任何其他模式具体。例如 /api/{a}/{b}/{c} 更具体。

  • prefix pattern (如 /public/** )的特定性不如任何其他不包含双通配符的模式。例如 /public/path3/{a}/{b}/{c} 更具体。

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

带占位符的路径模式

@RequestMapping 注释中的模式支持针对本地属性和/或系统属性和环境变量的 ${…} 占位符。在控制器映射到的路径可能需要通过配置进行自定义的情况下,这可能很有用。有关占位符的更多信息,请参阅 PropertyPlaceholderConfigurer 类的javadoc。

后缀模式匹配

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

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

有关后缀模式匹配配置,请参阅 Section 22.16.11, “Path Matching” ,对于内容协商配置,请参阅 Section 22.16.6, “Content Negotiation”

后缀模式匹配和RFD

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

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

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

默认情况下,许多常见路径扩展名列入白名单。此外,REST API调用通常不能直接在浏览器中用作URL。但是,使用自定义 HttpMessageConverter 实现的应用程序可以显式注册文件扩展名以进行内容协商,并且不会为此类扩展添加Content-Disposition标头。见 Section 22.16.6, “Content Negotiation”

这最初是作为CVE-2015-5211工作的一部分而引入的。以下是报告中的其他建议:编码而不是转义JSON响应。这也是OWASP XSS的推荐。有关如何使用Spring执行此操作的示例,请参阅spring-jackson-owasp。将后缀模式匹配配置为仅关闭或仅限于显式注册的后缀。配置内容协商,将属性“useJaf”和“ignoreUnknownPathExtensions”设置为false,这将导致对未知扩展名的URL进行406响应。但是请注意,如果URL自然希望在末尾有一个点,则可能无法选择此选项。添加X-Content-Type-Options:响应的nosniff标头。 Spring Security 4默认执行此操作。

矩阵变量

URI规范 RFC 3986 定义了在路径段中包含名称 - 值对的可能性。规范中没有使用特定术语。一般的 "URI path parameters" 可以应用,虽然更独特的 "Matrix URIs" ,源自Tim Berners-Lee的旧帖子,也经常使用并且相当广为人知。在Spring MVC中,这些被称为矩阵变量。

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

如果URL预计包含矩阵变量,则请求映射模式必须使用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

}

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

// 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 属性设置为 false 。默认情况下,它设置为 true

MVC Java配置和MVC命名空间都提供了启用矩阵变量使用的选项。如果您使用的是Java配置,则“使用MVC Java配置进行高级自定义”部分介绍了如何自定义RequestMappingHandlerMapping。在MVC命名空间中,<mvc:annotation-driven>元素具有应该设置为true的enable-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 中被否定,以匹配除 Content-Type text/plain 之外的所有请求。还要考虑使用 MediaType 中提供的常量,例如 APPLICATION_JSON_VALUEAPPLICATION_JSON_UTF8_VALUE

类型和方法级别支持消耗条件。与大多数其他条件不同,在类型级别使用时,方法级消耗类型会覆盖而不是扩展类型级消耗类型。

可 生产环境 的媒体类型

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

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

请注意产品中指定的介质类型condition还可以选择指定字符集。例如,在上面的代码片段中,我们指定的媒体类型与MappingJackson2HttpMessageConverter中配置的默认媒体类型相同,包括UTF-8字符集。

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

类型和方法级别支持生成条件。与大多数其他条件不同,在类型级别使用时,方法级可生成类型会覆盖而不是扩展类型级可生成类型。

请求参数和 Headers 值

您可以通过请求参数条件缩小请求匹配,例如 "myParam""!myParam""myParam=myValue" 。前两个测试请求参数存在/不存在,第三个测试特定参数值。以下是请求参数值条件的示例:

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

}

可以执行相同的操作来测试请求标头的存在/不存在,或者根据特定的请求标头值进行匹配:

@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和Accept标头值(例如“content-type = text / *”将匹配“text / plain”和“text / html”),但建议分别使用消耗和生成条件。它们专门用于此目的。

HTTP HEAD和HTTP OPTIONS

映射到 "GET" 的 @RequestMapping 方法也隐式映射到 "HEAD" ,即不需要显式声明 "HEAD" 。处理HTTP HEAD请求就像它是HTTP GET一样,除了不写入正文,只计算字节数和 "Content-Length" 标头集。

@RequestMapping 方法内置了对HTTP OPTIONS的支持。默认情况下,通过将 "Allow" 响应头设置为在具有匹配URL模式的所有 @RequestMapping 方法上显式声明的HTTP方法来处理HTTP OPTIONS请求。如果未显式声明HTTP方法,则 "Allow" 标头设置为 "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS" 。理想情况下,始终声明 @RequestMapping 方法要处理的HTTP方法,或者使用专用的 @RequestMapping 变体之一(请参阅 the section called “Composed @RequestMapping Variants” )。

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

22.3.3 定义@RequestMapping处理程序方法

@RequestMapping 处理程序方法可以具有非常灵活的签名。支持的方法参数和返回值将在下一节中介绍。大多数参数可以按任意顺序使用,唯一的例外是 BindingResult 参数。这将在下一节中介绍。

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

支持的方法参数类型

以下是受支持的方法参数:

  • 请求或响应对象(Servlet API)。选择任何特定请求或响应类型,例如 ServletRequestHttpServletRequest

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

会话访问可能不是线程安全的,特别是在Servlet环境中。如果允许多个请求同时访问会话,请考虑将RequestMappingHandlerAdapter的“synchronizeOnSession”标志设置为“true”。

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

  • java.util.Locale 用于当前请求区域设置,由最可用的区域设置解析程序确定,实际上是在MVC环境中配置的 LocaleResolver / LocaleContextResolver
    999 java.util.TimeZone (Java 6)/ java.time.ZoneId (在Java 8上),用于与当前请求关联的时区,由 LocaleContextResolver 确定。

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

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

  • org.springframework.http.HttpMethod 用于HTTP请求方法。

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

  • @PathVariable 用于访问URI模板变量的带注释参数。见 the section called “URI Template Patterns”

  • @MatrixVariable 带注释的参数,用于访问位于URI路径段中的名称 - 值对。见 the section called “Matrix Variables”

  • @RequestParam 用于访问特定Servlet请求参数的带注释参数。参数值将转换为声明的方法参数类型。见 the section called “Binding request parameters to method parameters with @RequestParam”

  • @RequestHeader 用于访问特定Servlet请求HTTP标头的带注释参数。参数值将转换为声明的方法参数类型。见 the section called “Mapping request header attributes with the @RequestHeader annotation”

  • @RequestBody 用于访问HTTP请求正文的带注释参数。使用 HttpMessageConverter s将参数值转换为声明的方法参数类型。见 the section called “Mapping the request body with the @RequestBody annotation”

  • @RequestPart 用于访问 "multipart/form-data" 请求部分内容的带注释参数。见 Section 22.10.5, “Handling a file upload request from programmatic clients”Section 22.10, “Spring’s multipart (file upload) support”

  • @SessionAttribute 用于访问现有永久会话属性的注释参数(例如用户认证对象),而不是通过 @SessionAttributes 作为控制器工作流的一部分临时存储在会话中的模型属性。

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

  • HttpEntity<?> 用于访问Servlet请求HTTP标头和内容的参数。请求流将使用 HttpMessageConverter s转换为实体主体。见 the section called “Using HttpEntity”

  • java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap 用于丰富公开Web视图的隐式模型。

  • org.springframework.web.servlet.mvc.support.RedirectAttributes 指定在重定向的情况下要使用的确切属性集,并且还要添加flash属性(临时存储在服务器端的属性,以使其在重定向后可用于请求)。见 the section called “Passing Data To the Redirect Target”Section 22.6, “Using flash attributes”

  • 命令或表单对象,用于将请求参数绑定到bean属性(通过setter)或直接绑定到字段,具有可自定义的类型转换,具体取决于 @InitBinder 方法和/或HandlerAdapter配置。请参阅 RequestMappingHandlerAdapter 上的 webBindingInitializer 属性。默认情况下,使用命令类名称 - 例如,此类命令对象及其验证结果将作为模型属性公开。模型属性 "orderAddress" ,用于 "some.package.OrderAddress" 类型的命令对象。可以在方法参数上使用 ModelAttribute 注释来自定义使用的模型属性名称。

  • org.springframework.validation.Errors / org.springframework.validation.BindingResult 前面的命令或表单对象的验证结果(紧接在前的方法参数)。

  • org.springframework.web.bind.support.SessionStatus 状态句柄,用于将表单处理标记为完成,从而触发清理由处理程序类型级别的 @SessionAttributes 批注指示的会话属性。

  • org.springframework.web.util.UriComponentsBuilder 一个构建器,用于准备相对于当前请求的主机,端口,方案,上下文路径和servlet映射的文字部分的URL。

ErrorsBindingResult 参数必须遵循立即绑定的模型对象,因为方法签名可能具有多个模型对象,并且Spring将为每个模型对象创建单独的 BindingResult 实例,因此以下示例将不起作用:

Invalid ordering of BindingResult and @ModelAttribute.

@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作为方法参数类型受支持,注释具有必需属性(例如@RequestParam,@ RequestHeader等。在这些情况下使用java.util.Optional等同于拥有需要=假。

支持的方法返回类型

以下是支持的返回类型:

  • 一个 ModelAndView 对象,模型隐式地丰富了命令对象和 @ModelAttribute 带注释的引用数据访问器方法的结果。

  • 一个 Model 对象,其视图名称通过 RequestToViewNameTranslator 隐式确定,并且模型隐式地使用命令对象和 @ModelAttribute 带注释的引用数据访问器方法的结果。

  • 用于公开模型的 Map 对象,其中视图名称通过 RequestToViewNameTranslator 隐式确定,并且模型隐式地使用命令对象和 @ModelAttribute 带注释的引用数据访问器方法的结果。

  • 一个 View 对象,模型通过命令对象和 @ModelAttribute 带注释的引用数据访问器方法隐式确定。处理程序方法还可以通过声明 Model 参数(参见上文)以编程方式丰富模型。

  • 一个 String 值,被解释为逻辑视图名称,模型通过命令对象和 @ModelAttribute 带注释的引用数据访问器方法隐式确定。处理程序方法还可以通过声明 Model 参数(参见上文)以编程方式丰富模型。

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

  • 如果方法用 @ResponseBody 注释,则返回类型将写入响应HTTP正文。返回值将使用 HttpMessageConverter s转换为声明的方法参数类型。见 the section called “Mapping the response body with the @ResponseBody annotation”

  • 一个 HttpEntity<?>ResponseEntity<?> 对象,用于提供对Servlet响应HTTP标头和内容的访问。实体主体将使用 HttpMessageConverter s转换为响应流。见 the section called “Using HttpEntity”

  • HttpHeaders 对象返回没有正文的响应。

  • 当应用程序想要在Spring MVC管理的线程中异步生成返回值时,可以返回 Callable<?>

  • 当应用程序想要从自己选择的线程生成返回值时,可以返回 DeferredResult<?>

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

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

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

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

  • 任何其他返回类型都被视为要向视图公开的单个模型属性,使用在方法级别通过 @ModelAttribute 指定的属性名称(或基于返回类型类名称的默认属性名称)。该模型隐含地使用命令对象和 @ModelAttribute 带注释的参考数据访问器方法的结果进行了丰富。

使用@RequestParam将请求参数绑定到方法参数

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

以下代码段显示了用法:

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

    // ...

}

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

如果目标方法参数类型不是 String ,则会自动应用类型转换。见 the section called “Method Parameters And Type Conversion”

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

使用@RequestBody注释映射请求正文

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

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

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

  • ByteArrayHttpMessageConverter 转换字节数组。

  • StringHttpMessageConverter 转换字符串。

  • FormHttpMessageConverter 将表单数据转换为MultiValueMap <String,String>。

  • SourceHttpMessageConverter 转换为/从javax.xml.transform.Source转换。

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

如果您打算读写XML,则需要使用 org.springframework.oxm 包中的特定 MarshallerUnmarshaller 实现配置 MarshallingHttpMessageConverter 。下面的示例显示了如何在配置中直接执行此操作,但如果您的应用程序是通过MVC命名空间或MVC Java配置配置的,请参阅 Section 22.16.1, “Enabling the MVC Java Config or the MVC XML Namespace”

<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配置时,假设JSR-303实现在类路径上可用,则会自动配置JSR-303验证器。

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

另请参见第22.16.1节“启用MVC Java配置或MVC XML命名空间”,以获取有关通过MVC命名空间或MVC Java配置配置消息转换器和验证器的信息。

使用@ResponseBody注释映射响应正文

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

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

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

@RequestBody 一样,Spring使用 HttpMessageConverter 将返回的对象转换为响应主体。有关这些转换器的更多信息,请参阅上一节和 Message Converters

使用@RestController批注创建REST控制器

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

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

与常规 @Controller 一样, @ControllerAdvice@RestControllerAdvice 可以辅助 @RestController 。有关详细信息,请参阅 the section called “Advising controllers with @ControllerAdvice and @RestControllerAdvice” 部分。

使用HttpEntity

HttpEntity 类似于 @RequestBody@ResponseBody 。除了访问请求和响应主体外, HttpEntity (以及特定于响应的子类 ResponseEntity )还允许访问请求和响应头,如下所示:

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

上面的示例获取 MyRequestHeader 请求标头的值,并将主体作为字节数组读取。它将 MyResponseHeader 添加到响应中,将 Hello World 写入响应流,并将响应状态代码设置为201(已创建)。

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

在方法上使用@ModelAttribute

@ModelAttribute 注释可用于方法或方法参数。本节解释了它在方法上的用法,下一节解释了它在方法参数上的用法。

方法上的 @ModelAttribute 表示该方法的目的是添加一个或多个模型属性。此类方法支持与 @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 方法用于使用常用属性填充模型,例如填充状态或宠物类型的下拉列表,或者检索像Account这样的命令对象,以便使用它来表示HTML表单上的数据。后一种情况将在下一节进一步讨论。

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

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

@ModelAttribute 方法也可以在 @ControllerAdvice 注释类中定义,并且此类方法适用于许多控制器。有关详细信息,请参阅 the section called “Advising controllers with @ControllerAdvice and @RestControllerAdvice” 部分。

未明确指定模型属性名称时会发生什么?在这种情况下,会根据模型属性的类型为模型属性分配默认名称。例如,如果方法返回Account类型的对象,则使用的默认名称为“account”。您可以通过@ModelAttribute注释的值更改它。如果直接向Model添加属性,请使用适当的重载addAttribute(..)方法 - 即使用或不使用属性名称。

@ModelAttribute 注释也可用于 @RequestMapping 方法。在这种情况下, @RequestMapping 方法的返回值被解释为模型属性而不是视图名称。然后根据视图名称约定派生视图名称,就像返回 void 的方法一样 - 请参阅 Section 22.13.3, “Default view name”

在方法参数上使用@ModelAttribute

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

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

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

鉴于以上示例,Pet实例可以来自何处?有几种选择:

  • 由于使用 @SessionAttributes ,它可能已经在模型中 - 请参阅 the section called “Using @SessionAttributes to store model attributes in the HTTP session between requests”

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

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

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

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

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

在此示例中,模型属性的名称(即 "account" )与URI模板变量的名称匹配。如果您注册 Converter<String, Account> 可以将 String 帐户值转换为 Account 实例,那么上面的示例将无需 @ModelAttribute 方法即可运行。

下一步是数据绑定。 WebDataBinder 类将请求参数名称(包括查询字符串参数和表单字段)与按名称建模属性字段进行匹配。在必要时应用类型转换(从String到目标字段类型)后填充匹配字段。数据绑定和验证包含在 Chapter 9, Validation, Data Binding, and Type Conversion 中。自定义控制器级别的数据绑定过程包含在 the section called “Customizing WebDataBinder initialization” 中。

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

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

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

    // ...

}

使用 BindingResult ,您可以检查是否发现错误在哪种情况下,通常会在Spring的 <errors> 表单标记的帮助下呈现错误的相同表单。

请注意,在某些情况下,在没有数据绑定的情况下访问模型中的属性可能很有用。对于这种情况,您可以将 Model 注入控制器,或者在注释上使用 binding 标志:

@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) {

    // ...
}

除了数据绑定之外,您还可以使用自己的自定义验证程序调用验证,该验证程序传递用于记录数据绑定错误的相同 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 注释自动调用验证:

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

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

    // ...

}

有关如何配置和使用验证的详细信息,请参阅 Section 9.8, “Spring Validation”Chapter 9, Validation, Data Binding, and Type Conversion

使用@SessionAttributes在请求之间的HTTP会话中存储模型属性

类型级 @SessionAttributes 注释声明特定处理程序使用的会话属性。这通常会列出模型属性的名称或模型属性的类型,这些属性应该透明地存储在会话或某些会话存储中,作为后续请求之间的表单支持bean。

以下代码段显示了此批注的用法,指定了模型属性名称:

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

使用@SessionAttribute访问预先存在的全局会话属性

如果您需要访问全局管理的预先存在的会话属性(例如,通过过滤器),并且可能存在或不存在,请在方法参数上使用 @SessionAttribute 注释:

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

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

对于作为控制器工作流的一部分在会话中临时存储模型属性,请考虑使用 SessionAttributes ,如 the section called “Using @SessionAttributes to store model attributes in the HTTP session between requests” 中所述。

使用@RequestAttribute访问请求属性

@SessionAttribute 类似, @RequestAttribute 注释可用于访问由过滤器或拦截器创建的预先存在的请求属性:

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

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

前面几节介绍了如何使用 @ModelAttribute 来支持来自浏览器客户端的表单提交请求。建议将相同的注释用于非浏览器客户端的请求。但是,在处理HTTP PUT请求时,有一个显着的区别。浏览器可以通过HTTP GET或HTTP POST提交表单数据。非浏览器客户端也可以通过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>

上面的过滤器拦截内容类型为 application/x-www-form-urlencoded 的HTTP PUT和PATCH请求,从请求正文中读取表单数据,并包装 ServletRequest ,以便通过 ServletRequest.getParameter*() 方法系列提供表单数据。

作为HttpPutFormContentFilter消耗请求的主体中,它不应该被配置为依赖于其它转换器,用于应用程序/ x-WWW窗体-urlencoded PUT或PATCH网址。这包括@RequestBody MultiValueMap <String,String>和HttpEntity <MultiValueMap <String,String >>。

使用@CookieValue注释映射cookie值

@CookieValue 注释允许将方法参数绑定到HTTP cookie的值。

让我们考虑以下cookie已收到http请求:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

以下代码示例演示了如何获取 JSESSIONID cookie的值:

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

如果目标,则自动应用类型转换方法参数类型不是 String 。见 the section called “Method Parameters And Type Conversion”

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

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

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

这是一个示例请求标头:

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

以下代码示例演示了如何获取 Accept-EncodingKeep-Alive 标头的值:

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

如果方法参数不是 String ,则会自动应用类型转换。见 the section called “Method Parameters And Type Conversion”

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

内置支持可用于将逗号分隔的字符串转换为字符串数组/集合或类型转换系统已知的其他类型。例如,使用@RequestHeader(“Accept”)注释的方法参数可以是String类型,也可以是String []或List

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

方法参数和类型转换

从请求中提取的基于字符串的值(包括请求参数,路径变量,请求标头和cookie值)可能需要转换为方法参数或字段的目标类型(例如,将请求参数绑定到 @ModelAttribute 参数中的字段他们必然会。如果目标类型不是 String ,则Spring会自动转换为适当的类型。支持所有简单类型,如int,long,Date等。您可以通过 WebDataBinder (请参阅 the section called “Customizing WebDataBinder initialization” )或使用 FormattingConversionService (请参阅 Section 9.6, “Spring Field Formatting” )注册 Formatters 来进一步自定义转换过程。

自定义WebDataBinder初始化

要通过Spring的 WebDataBinder 自定义与PropertyEditors的请求参数绑定,您可以在控制器中使用 @InitBinder -annotated方法,在 @ControllerAdvice 类中使用 @InitBinder 方法,或者提供自定义 WebBindingInitializer 。有关详细信息,请参阅 the section called “Advising controllers with @ControllerAdvice and @RestControllerAdvice” 部分。

使用@InitBinder自定义数据绑定

使用 @InitBinder 注释控制器方法允许您直接在控制器类中配置Web数据绑定。 @InitBinder 标识初始化 WebDataBinder 的方法,该方法将用于填充带注释的处理程序方法的命令和表单对象参数。

这样的init-binder方法支持 @RequestMapping 方法支持的所有参数,但命令/表单对象和相应的验证结果对象除外。 Init-binder方法不能有返回值。因此,它们通常被声明为 void 。典型的参数包括 WebDataBinderWebRequestjava.util.Locale 的组合,允许代码注册特定于上下文的编辑器。

以下示例演示如何使用 @InitBinder 为所有 java.util.Date 表单属性配置 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 实现而不是 PropertyEditor 实例。如果您碰巧在共享 FormattingConversionService 中设置了基于 Formatter 的设置,则此功能特别有用,同样的方法可以重复用于特定于控制器的绑定规则调整。

@Controller
public class MyFormController {

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

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

要外部化数据绑定初始化,您可以提供 WebBindingInitializer 接口的自定义实现,然后通过为 AnnotationMethodHandlerAdapter 提供自定义Bean配置来启用该实现,从而覆盖默认配置。

以下来自PetClinic应用程序的示例显示了使用 WebBindingInitializer 接口 org.springframework.samples.petclinic.web.ClinicBindingInitializer 的自定义实现的配置,该实现配置了几个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 注释类中定义,在这种情况下,它们适用于匹配的控制器。这提供了使用 WebBindingInitializer 的替代方法。有关详细信息,请参阅 the section called “Advising controllers with @ControllerAdvice and @RestControllerAdvice” 部分。

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

@ControllerAdvice 注释是一个组件注释,允许通过类路径扫描自动检测实现类。使用MVC命名空间或MVC Java配置时会自动启用它。

使用 @ControllerAdvice 注释的类可以包含 @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 documentation 了解更多详情。

Jackson Serialization查看支持

有时可以将上下文中将序列化为HTTP响应主体的对象进行过滤。为了提供这样的能力,Spring MVC内置支持使用 Jackson’s Serialization Views 进行渲染。

要将其与 @ResponseBody 控制器方法或返回 ResponseEntity 的控制器方法一起使用,只需添加带有类参数的 @JsonView 注释,该类参数指定要使用的视图类或接口:

@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允许指定多个类,但控制器方法的使用仅支持一个类参数。如果需要启用多个视图,请考虑使用复合接口。

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

@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支持

为了启用对 @ResponseBodyResponseEntity 方法的 JSONP 支持,声明一个扩展 AbstractJsonpResponseBodyAdvice@ControllerAdvice bean,如下所示,其中构造函数参数指示JSONP查询参数名称:

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

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

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

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

22.3.4 异步请求处理

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

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

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

}

另一个选项是控制器方法返回 DeferredResult 的实例。在这种情况下,返回值也将从任何线程产生,即不由Spring MVC管理的线程。例如,可以响应于诸如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异步请求处理功能,这可能很难理解。阅读这一点肯定会有所帮助。以下是有关基础机制的一些基本事实:

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

  • request.startAsync() 的调用返回 AsyncContext ,可用于进一步控制异步处理。例如,它提供方法 dispatch ,类似于Servlet API的转发,但它允许应用程序在Servlet容器线程上恢复请求处理。

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

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

  • Controller返回 Callable

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

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

  • Callable 产生一个结果,Spring MVC将请求调度回Servlet容器以恢复处理。

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

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

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

  • Spring MVC启动异步处理。

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

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

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

有关异步请求处理的动机以及何时或为何使用它的更多背景知识,请阅读 this blog post series

异步请求的异常处理

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

拦截异步请求

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

HandlerInterceptor 还可以注册 CallableProcessingInterceptorDeferredResultProcessingInterceptor ,以便更深入地集成异步请求的生命周期,例如处理超时事件。有关详细信息,请参阅 AsyncHandlerInterceptor 的Javadoc。

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

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

HTTP Streaming

控制器方法可以使用 DeferredResultCallable 异步生成其返回值,并且可以用于实现诸如 long polling 之类的技术,其中服务器可以尽快将事件推送到客户端。

如果您想在单个HTTP响应上推送多个事件,该怎么办?这是一种与 "Long Polling" 相关的技术,称为 "HTTP Streaming" 。 Spring MVC通过 ResponseBodyEmitter 返回值类型实现了这一点,该类型可用于发送多个对象,而不是通常 @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 中的正文,以便自定义响应的状态和 Headers 。

使用服务器发送事件的HTTP流式传输

SseEmitterResponseBodyEmitter 的子类,提供对 Server-Sent Events 的支持。服务器发送的事件是同一 "HTTP Streaming" 技术的另一个变体,除了从服务器推送的事件是根据W3C服务器发送事件规范格式化的。

服务器发送事件可用于其预期目的,即将事件从服务器推送到客户端。在Spring MVC中很容易做到,只需要返回 SseEmitter 类型的值。

但请注意,Internet Explorer不支持服务器发送事件,对于更高级的Web应用程序消息传递方案(如在线游戏,协作,财务应用程序等),最好考虑Spring的WebSocket支持,包括SockJS样式的WebSocket仿真,可以追溯到各种各样的浏览器(包括Internet Explorer)以及更高级别的消息传递模式,用于通过更多以消息传递为中心的体系结构中的发布 - 订阅模型与客户端进行交互。有关详细信息,请参阅 the following blog post

HTTP直接流式传输到OutputStream

ResponseBodyEmitter 允许通过 HttpMessageConverter 将对象写入响应来发送事件。这可能是最常见的情况,例如在编写JSON数据时。但是,有时绕过消息转换并直接写入响应 OutputStream (例如文件下载)很有用。这可以在 StreamingResponseBody 返回值类型的帮助下完成。

这是一个例子:

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

请注意, StreamingResponseBody 也可以用作 ResponseEntity 中的正文,以便自定义响应的状态和 Headers 。

配置异步请求处理

Servlet容器配置

对于使用 web.xml 配置的应用程序,请确保更新到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> 子元素在 DispatcherServlet 上启用异步支持。此外,任何参与asyncrequest处理的 Filter 都必须配置为支持ASYNC调度程序类型。为Spring Framework提供的所有过滤器启用ASYNC调度程序类型应该是安全的,因为它们通常扩展 OncePerRequestFilter 并且运行时检查过滤器是否需要参与异步调度。

下面是一些web.xml配置示例:

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

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

Spring MVC配置

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

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

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

22.3.5 测试控制器

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

22.4 处理程序映射

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

  • interceptors 要使用的拦截器列表。 HandlerInterceptorSection 22.4.1, “Intercepting requests with a HandlerInterceptor” 中讨论过。

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

  • order 基于order属性的值(请参阅 org.springframework.core.Ordered 接口),Spring对上下文中可用的所有处理程序映射进行排序,并应用第一个匹配的处理程序。

  • alwaysUseFullPath 如果 true ,Spring使用当前Servlet上下文中的完整路径来查找适当的处理程序。如果 false (缺省值),则使用当前Servlet映射中的路径。例如,如果使用 /testing/* 映射Servlet并且 alwaysUseFullPath 属性设置为true,则使用 /testing/viewPage.html ,而如果该属性设置为false,则使用 /viewPage.html

  • urlDecode 默认为 true ,自Spring 2.5起。如果您希望比较编码路径,请将此标志设置为 false 。但是, HttpServletRequest 始终以解码形式公开Servlet路径。请注意,与编码路径相比,Servlet路径不匹配。

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

<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(..) 方法返回一个布尔值。您可以使用此方法来中断或继续执行链的处理。当此方法返回 true 时,处理程序执行链将继续;当它返回false时, DispatcherServlet 假定拦截器本身已处理请求(例如,呈现了适当的视图),并且不继续执行执行链中的其他拦截器和实际处理程序。

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

<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 拦截。如果当前时间不在办公时间,则会将用户重定向到静态HTML文件,例如,您只能在办公时间访问该网站。

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

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

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

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

22.5 解析观点

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

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

22.5.1 使用ViewResolver接口解析视图

Section 22.3, “Implementing Controllers” 中所讨论的,Spring Web MVC控制器中的所有处理程序方法必须明确地(例如,通过返回 StringViewModelAndView )或隐式地(即,基于约定)解析为逻辑视图名称。 Spring中的视图由逻辑视图名称处理,并由视图解析器解析。 Spring带有相当多的视图解析器。该表列出了其中大部分内容;下面是几个例子。

Table 22.3. View resolvers

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

例如,使用JSP作为视图技术,您可以使用 UrlBasedViewResolver 。此视图解析程序将视图名称转换为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 作为逻辑视图名称返回时,此视图解析程序会将请求转发给将将请求发送到 /WEB-INF/jsp/test.jspRequestDispatcher

在Web应用程序中组合不同的视图技术时,可以使用 ResourceBundleViewResolver

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

ResourceBundleViewResolver 检查由基本名称标识的 ResourceBundle ,并且对于它应该解析的每个视图,它使用属性 [viewname].(class) 的值作为视图类,并使用属性 [viewname].url 的值作为视图URL。示例可以在下一章中找到,其中包括视图技术。如您所见,您可以标识父视图,属性文件 "extend" 中的所有视图都可以从中查看。这样,您可以指定默认视图类。

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

22.5.2 链接ViewResolvers

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

在以下示例中,视图解析器链包含两个解析器, 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会检查其他视图解析器的上下文。如果存在其他视图解析器,Spring将继续检查它们,直到视图得到解决。如果没有视图解析器返回视图,Spring会抛出 ServletException

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

22.5.3 重定向到视图

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

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

RedirectView

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

如果您使用 RedirectView 并且视图是由控制器本身创建的,则建议您将重定向URL配置为注入控制器,以便它不会烘焙到控制器中,而是在上下文中与视图名称一起配置。 the section called “The redirect: prefix” 促进了这种解耦。

将数据传递给重定向目标

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

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

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

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

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

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

重定向:前缀

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

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

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

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

前锋:前缀

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

redirect: 前缀一样,如果将具有 forward: 前缀的视图名称注入控制器,则控制器不会检测到在处理响应方面发生了任何特殊情况。

22.5.4 ContentNegotiatingViewResolver

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

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

  • 使用相同的URI为客户端找到资源,但设置 Accept HTTP请求标头以列出它理解的 media types 。例如, http://www.example.com/users/fred 的HTTP请求( Accept 标头设置为 application/pdf )请求用户fred的PDF表示,而 http://www.example.com/users/fredAccept 标头设置为 text/xml 请求XML表示。这种策略称为 content negotiation

Accept标头的一个问题是无法在HTML中的Web浏览器中设置它。例如,在Firefox中,它被修复为:Accept:text / html,application / xhtml xml,application / xml; q = 0.9,* / *; q = 0.8因此,通常会看到使用不同的开发基于浏览器的Web应用程序时每个表示的URI。

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

ContentNegotiatingViewResolver 选择适当的 View 来处理请求,方法是将请求媒体类型与 View 所支持的媒体类型(也称为 Content-Type )进行比较。列表中具有兼容性 Content-Type 的第一个 View 将表示返回给客户端。如果 ViewResolver 链不能提供兼容视图,则将查阅通过 DefaultViews 属性指定的视图列表。后一个选项适用于单独的 Views ,它可以呈现当前资源的适当表示,而不管逻辑视图名称如何。 Accept Headers 可能包含wild卡,例如 text/* ,在这种情况下,其内容类型为 text/xmlView 是兼容匹配。

要支持基于文件扩展名的视图的自定义分辨率,请使用 ContentNegotiationManager :请参阅 Section 22.16.6, “Content Negotiation”

以下是 ContentNegotiatingViewResolver 的示例配置:

<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名称的视图。 (有关Spring如何查找并实例化视图的更多详细信息,请参阅“ Resolving views with the ViewResolver interface ”。)在此示例中, content bean是一个继承自 AbstractAtomFeedView 的类,它返回Atom RSS提要。有关创建Atom Feed表示的更多信息,请参阅Atom Views部分。

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

如果没有显式配置`ContentNegotiatingViewResolver的ViewResolvers列表,它会自动使用应用程序上下文中定义的任何ViewResolvers。

返回具有 http://localhost/content.atomhttp://localhost/content 形式的URI的Atom RSS提要的相应控制器代码,其中包含application / atom xml的 Accept 标头,如下所示。

@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模式。 Flash重定向(通常在会话中)之前临时保存Flash属性,以便在重定向后立即删除请求。

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

Flash属性支持始终为 "on" ,并且不需要显式启用,但如果不使用,它永远不会导致HTTP会话创建。在每个请求上都有一个 "input" FlashMap 具有从先前请求(如果有)传递的属性和 "output" FlashMap ,其中包含要为后续请求保存的属性。两个 FlashMap 实例都可以通过 RequestContextUtils 中的静态方法从Spring MVC中的任何位置访问。

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


Matching requests to flash attributes

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

为了减少此类问题的可能性, RedirectView 自动 "stamps" FlashMap 实例与目标重定向URL的路径和查询参数。反过来,默认 FlashMapManager 在查找 "input" FlashMap 时将该信息与传入请求进行匹配。

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


22.7 构建URI

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

例如,您可以展开和编码URI模板字符串:

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

或者,您可以选择复制可用信息的子集,包括上下文路径:

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

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

或者在 DispatcherServlet 按名称映射的情况(例如 /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 构建控制器和方法的URI

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

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

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

您可以通过按名称引用方法来准备链接:

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

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

在上面的例子中,我们提供了实际的方法参数值,在这种情况下是长值21,用作路径变量并插入到URL中。此外,我们提供值42以填充任何剩余的URI变量,例如从类型级请求映射继承的 "hotel" 变量。如果方法有更多参数,则可以为URL不需要的参数提供null。通常,只有 @PathVariable@RequestParam 参数与构造URL相关。

还有其他方法可以使用 MvcUriComponentsBuilder 。例如,您可以使用类似于通过代理进行模拟测试的技术,以避免按名称引用控制器方法(该示例假定 MvcUriComponentsBuilder.on 的静态导入):

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

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

控制器方法签名在设计时受限于可用于使用fromMethodCall创建链接。除了需要适当的参数签名之外,返回类型还存在技术限制:即为链接构建器调用生成运行时代理,因此返回类型不能是final。特别是,视图名称的常见String返回类型在此处不起作用;使用ModelAndView甚至是普通的Object(带有String返回值)。

以上示例在 MvcUriComponentsBuilder 中使用静态方法。在内部,它们依赖 ServletUriComponentsBuilder 从当前请求的方案,主机,端口,上下文路径和servlet路径准备基本URL。这在大多数情况下效果很好,但有时可能不够。例如,您可能在请求的上下文之外(例如,准备链接的批处理)或者您可能需要插入路径前缀(例如,从请求路径中删除并需要重新插入链接的区域设置前缀)。

对于这种情况,您可以使用接受 UriComponentsBuilder 的静态 "fromXxx" 重载方法来使用基本URL。或者,您可以使用基本URL创建 MvcUriComponentsBuilder 的实例,然后使用基于实例的 "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 使用“转发”和“X-Forwarded- *” Headers

当请求通过代理(例如负载均衡 器)时,主机,端口和方案可能会发生变化,这对需要创建资源链接的应用程序提出了挑战,因为链接应该反映原始请求的主机,端口和方案,如下所示。客户的观点。

RFC 7239 定义代理的 "Forwarded" HTTP标头,用于提供有关原始请求的信息。还有其他非标准标头正在使用,例如 "X-Forwarded-Host" , "X-Forwarded-Port" 和 "X-Forwarded-Proto" 。

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

ForwardedHeaderFilter 为整个应用程序提供了一次和全局的替代方法。过滤器包装请求以覆盖主机,端口和方案信息,以及 "hides" 任何转发的标头以供后续处理。

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

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

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

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

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

分配的请求映射名称在启动时以TRACE级别记录。

Spring JSP标记库提供了一个名为 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>

上面的示例依赖于Spring标记库(即META-INF / spring.tld)中声明的 mvcUrl JSP函数。对于更高级的情况(例如,如上一节中所述的自定义基本URL),可以轻松定义自己的函数或使用自定义标记文件,以便将 MvcUriComponentsBuilder 的特定实例与自定义基本URL一起使用。

22.8 使用区域设置

Spring的体系结构的大多数部分都支持国际化,就像Spring Web MVC框架一样。 DispatcherServlet 使您可以使用客户端的区域设置自动解析消息。这是通过 LocaleResolver 对象完成的。

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

除了自动语言环境解析之外,您还可以将拦截器附加到处理程序映射(有关处理程序映射拦截器的更多信息,请参阅 Section 22.4.1, “Intercepting requests with a HandlerInterceptor” )以在特定情况下更改语言环境,例如,根据请求中的参数。

区域设置解析器和拦截器在 org.springframework.web.servlet.i18n 包中定义,并以正常方式在应用程序上下文中进行配置。以下是Spring中包含的语言环境解析器的选择。

22.8.1 获取时区信息

除了获取客户端的区域设置之外,了解其时区通常也很有用。 LocaleContextResolver 接口提供了 LocaleResolver 的扩展,允许解析器提供更丰富的 LocaleContext ,其中可能包含时区信息。

可用时,可以使用 RequestContext.getTimeZone() 方法获取用户的 TimeZone 。时区信息将由Spring的 ConversionService 注册的日期/时间 ConverterFormatter 对象自动使用。

22.8.2 AcceptHeaderLocaleResolver

此区域设置解析程序检查客户端(例如,Web浏览器)发送的请求中的 accept-language 标头。通常,此标头字段包含客户端操作系统的区域设置。请注意,此解析程序不支持时区信息。

22.8.3 CookieLocaleResolver

此区域设置解析程序检查客户端上可能存在的 Cookie ,以查看是否指定了 LocaleTimeZone 。如果是,则使用指定的详细信息。使用此区域设置解析程序的属性,您可以指定cookie的名称以及最大年龄。在下面找到定义 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>

Table 22.4. CookieLocaleResolver properties

属性默认说明
cookieNameclassname LOCALECookie的名称
cookieMaxAgeServlet容器默认cookie在客户端上保持持久的最长时间。如果指定-1,则不会保留cookie;它只有在客户端关闭浏览器之后才可用。
cookiePath/限制cookie对您网站某个部分的可见性。指定cookiePath时,cookie只对该路径及其下方的路径可见。

22.8.4 SessionLocaleResolver

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

请注意,与外部会话管理机制(如Spring Session项目)没有直接关系。此 SessionLocaleResolver 将简单地针对当前 HttpServletRequest 评估和修改相应的 HttpSession 属性。

22.8.5 LocaleChangeInterceptor

您可以通过将 LocaleChangeInterceptor 添加到其中一个处理程序映射来启用语言环境的更改(请参阅 Section 22.4, “Handler mappings” )。它将检测请求中的参数并更改区域设置。它在 LocaleResolver 上调用 setLocale() ,它也存在于上下文中。以下示例显示对包含名为 siteLanguage 的参数的所有 *.view 资源的调用现在将更改区域设置。因此,例如,对以下URL的请求 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框架主题来设置应用程序的整体外观,从而增强用户体验。主题是静态资源的集合,通常是样式表和图像,它们会影响应用程序的视觉样式。

22.9.2 定义主题

要在Web应用程序中使用主题,必须设置 org.springframework.ui.context.ThemeSource 接口的实现。 WebApplicationContext 接口扩展 ThemeSource 但将其职责委托给专门的实施。默认情况下,委托将是一个 org.springframework.ui.context.support.ResourceBundleThemeSource 实现,它从类路径的根目录加载属性文件。要使用自定义 ThemeSource 实现或配置 ResourceBundleThemeSource 的基本名称前缀,可以在应用程序上下文中使用保留名称 themeSource 注册bean。 Web应用程序上下文自动检测具有该名称的bean并使用它。

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

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

属性的键是从视图代码引用主题元素的名称。对于JSP,通常使用 spring:theme 自定义标记执行此操作,该标记与 spring:message 标记非常相似。以下JSP片段使用上一示例中定义的主题来自定义外观:

<%@ 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 使用空的基本名称前缀。因此,属性文件从类路径的根加载。因此,您可以将 cool.properties 主题定义放在类路径根目录的目录中,例如,在 /WEB-INF/classes 中。 ResourceBundleThemeSource 使用标准的Java资源包加载机制,允许主题的完全国际化。例如,我们可以使用 /WEB-INF/classes/cool_nl.properties 引用带有荷兰文本的特殊背景图像。

22.9.3 主题解析器

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

Table 22.5. ThemeResolver implementations

说明
FixedThemeResolver选择使用 defaultThemeName 属性设置的固定主题。
SessionThemeResolver主题在用户的HTTP会话中维护。它只需要为每个会话设置一次,但不会在会话之间保留。
CookieThemeResolver所选主题存储在客户端的cookie中。

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

22.10 Spring的多部分(文件上传)支持

22.10.1 简介

Spring的内置多部分支持处理Web应用程序中的文件上载。您可以使用 org.springframework.web.multipart 包中定义的可插入 MultipartResolver 对象启用此多部分支持。 Spring提供了一个用于 Commons FileUploadMultipartResolver 实现,另一个用于Servlet 3.0多部分请求解析。

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

22.10.2 将MultipartResolver与Commons FileUpload配合使用

以下示例显示如何使用 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>

当然,您还需要在类路径中放置适当的jar,以使多部分解析器工作。对于 CommonsMultipartResolver ,您需要使用 commons-fileupload.jar

当Spring DispatcherServlet 检测到多部分请求时,它会激活已在您的上下文中声明的解析程序并移交请求。解析器然后将当前 HttpServletRequest 包装到支持多部分文件上载的 MultipartHttpServletRequest 中。使用 MultipartHttpServletRequest ,您可以获取有关此请求包含的多部分的信息,并实际访问控制器中的多部分文件。

22.10.3 将MultipartResolver与Servlet 3.0一起使用

为了使用基于Servlet 3.0的多部分解析,您需要在 web.xml 中使用 "multipart-config" 部分标记 DispatcherServlet ,或者在程序化Servlet注册中使用 javax.servlet.MultipartConfigElement 标记,或者在Servlet类中可能使用 javax.servlet.annotation.MultipartConfig 注释的自定义Servlet类。由于Servlet 3.0不允许从MultipartResolver完成这些设置,因此需要在该Servlet注册级别应用配置设置(如最大大小或存储位置)。

一旦使用上述方法之一启用了Servlet 3.0多部分解析,就可以将 StandardServletMultipartResolver 添加到Spring配置中:

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

22.10.4 处理表单中的文件上载

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

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

下一步是创建一个处理文件上载的控制器。该控制器非常类似于 normal annotated @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 方法参数如何映射到表单中声明的输入元素。在此示例中, byte[] 没有任何操作,但实际上您可以将其保存在数据库中,将其存储在文件系统上,等等。

使用Servlet 3.0多部分解析时,您还可以使用 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 处理程序客户端的文件上载请求

还可以在RESTful服务方案中从非浏览器客户端提交多部分请求。所有上述示例和配置也适用于此处。但是,与通常提交文件和简单表单字段的浏览器不同,编程客户端也可以发送特定内容类型的更复杂数据 - 例如带有文件的多部分请求和带有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 控制器方法参数访问名为 "meta-data" 的部件。但是,您可能更愿意接受从请求部分正文中的JSON格式数据初始化的强类型对象,这非常类似于 @RequestBody 在非多部分请求的主体的帮助下将其转换为目标对象的方式。 HttpMessageConverter

为此,您可以使用 @RequestPart 注释而不是 @RequestParam 注释。它允许您通过 HttpMessageConverter 传递特定多部分的内容,同时考虑到multipart的 'Content-Type' 标头:

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

    // ...

}

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

22.11 处理异常

22.11.1 HandlerExceptionResolver

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

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

22.11.2 @ExceptionHandler

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

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

@Controller
public class SimpleController {

    // ...

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

异常可能与传播的顶级异常(即直接抛出的异常)相匹配,或者与顶级包装异常中的直接原因相匹配(例如 IOException 包含在_674201中)。

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

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

对于@ExceptionHandler方法,根据特定控制器或通知bean的处理程序方法中的当前异常的原因,首选匹配根异常。但是,较高优先级的@ControllerAdvice上的原因匹配仍然优先于较低优先级的通知bean上的任何匹配(无论是根目录还是原因级别)。因此,在使用多建议安排时,请在具有相应顺序的优先级建议bean上声明主根异常映射!

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

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

22.11.3 处理标准Spring MVC异常

Spring MVC在处理请求时可能会引发许多异常。 SimpleMappingExceptionResolver 可以根据需要轻松地将任何异常映射到默认错误视图。但是,在与以自动方式解释响应的客户端合作时,您需要在响应上设置特定的状态代码。根据引发的异常,状态代码可能指示客户端错误(4xx)或服务器错误(5xx)。

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

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

DefaultHandlerExceptionResolver 通过设置响应的状态透明地工作。但是,如果您的应用程序可能需要在每个错误响应中添加开发人员友好的内容,例如在提供REST API时,它就不会将任何错误内容写入响应主体。您可以通过视图分辨率准备 ModelAndView 并渲染错误内容 - 即通过配置 ContentNegotiatingViewResolverMappingJackson2JsonView 等。但是,您可能更喜欢使用 @ExceptionHandler 方法。

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

22.11.4 使用@ResponseStatus注释业务异常

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

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

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

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

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

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

@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 网络安全

Spring Security 项目提供了保护Web应用程序免受恶意攻击的功能。查看 "CSRF protection""Security Response Headers""Spring MVC Integration" 部分中的参考文档。请注意,并非所有功能都必须使用Spring Security来保护应用程序。例如,可以通过将 CsrfFilterCsrfRequestDataValueProcessor 添加到配置中来添加CSRF保护。有关示例,请参阅 Spring MVC Showcase

另一种选择是使用专用于Web安全的框架。 HDIV 是一个这样的框架,并与Spring MVC集成。

22.13 约定优于配置支持

对于许多项目而言,坚持已 Build 的约定并具有合理的默认值正是它们(项目)所需要的,而Spring Web MVC现在明确支持约定优于配置。这意味着如果你 Build 了一组命名约定等,你可以大大减少设置处理程序映射,查看解析器, ModelAndView 实例等所需的配置量。这对于问题是一个很大的好处。快速原型设计,如果您选择将其推向 生产环境 阶段,还可以在代码库中提供一定程度的(始终是良好的)一致性。

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

22.13.1 Controller ControllerClassNameHandlerMapping

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

请考虑以下简单的 Controller 实现。特别注意 class 名称。

public class ViewShoppingCartController implements Controller {

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

}

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

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

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

ControllerClassNameHandlerMapping 找到在其应用程序上下文中定义的所有各种处理程序(或 Controller )bean,并从名称中删除 Controller 以定义其处理程序映射。因此, ViewShoppingCartController 映射到 /viewshoppingcart* 请求URL。

让我们看一些更多的例子,让中心思想立即变得熟悉。 (注意URL中的全部小写,与驼峰式的 Controller 类名相反。)

  • WelcomeController 映射到 /welcome* 请求URL

  • HomeController 映射到 /home* 请求URL

  • IndexController 映射到 /index* 请求URL

  • RegisterController 映射到 /register* 请求URL

对于 MultiActionController 处理程序类,生成的映射稍微复杂一些。以下示例中的 Controller 名称假定为 MultiActionController 实现:

  • AdminController 映射到 /admin/* 请求URL

  • CatalogController 映射到 /catalog/* 请求URL

如果您遵循将 Controller 实现命名为 xxxController 的约定, ControllerClassNameHandlerMapping 将为您节省定义和维护潜在looooong SimpleUrlHandlerMapping (或类似)的繁琐。

ControllerClassNameHandlerMapping 类扩展了 AbstractHandlerMapping 基类,因此您可以像使用许多其他 HandlerMapping 实现一样定义 HandlerInterceptor 实例和其他所有实例。

22.13.2 模型ModelMap(ModelAndView)

ModelMap 类本质上是一个美化的 Map ,它可以使添加的对象显示在 View 中(或在 View 上)符合通用的命名约定。考虑以下 Controller 实现;请注意,对象已添加到 ModelAndView ,未指定任何关联名称。

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 类使用 ModelMap 类,该类是自定义 Map 实现,在对象添加到对象时自动为对象生成键。在标量对象(例如 User )的情况下,确定添加对象的名称的策略是使用对象类的短类名。以下示例是为放入 ModelMap 实例的标量对象生成的名称。

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

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

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

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

  • 添加 null 将导致抛出 IllegalArgumentException 。如果要添加的对象(或多个对象)可能是 null ,那么您还需要明确该名称。


What, no automatic pluralization?

Spring Web MVC的配置约定支持不支持自动复数。也就是说,您无法将 ListPerson 对象添加到 ModelAndView ,并且生成的名称为 people

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


在添加 SetList 之后生成名称的策略是查看集合,获取集合中第一个对象的短类名,并使用 List 附加到名称。这同样适用于数组,但是对于数组,没有必要查看数组内容。一些示例将使集合的名称生成的语义更清晰:

  • 添加了零个或多个 x.y.User 元素的 x.y.User[] 数组将生成名称 userList

  • 添加了零个或多个 x.y.User 元素的 x.y.Foo[] 数组将生成名称 fooList

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

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

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

22.13.3 默认视图名称

当没有显式提供此类逻辑视图名称时, RequestToViewNameTranslator 接口确定逻辑 View 名称。它只有一个实现, DefaultRequestToViewNameTranslator 类。

DefaultRequestToViewNameTranslator 将请求URL映射到逻辑视图名称,如下例所示:

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(..) 方法的实现中,如何在返回的 ModelAndView 上设置 View 或逻辑视图名称。 DefaultRequestToViewNameTranslator 的任务是从请求的URL生成逻辑视图名称。在上述 RegistrationController (与 ControllerClassNameHandlerMapping 一起使用)的情况下, http://localhost/registration.html 的请求URL导致 DefaultRequestToViewNameTranslator 生成 registration 的逻辑视图名称。然后, InternalResourceViewResolver bean将此逻辑视图名称解析为 /WEB-INF/jsp/registration.jsp 视图。

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

当然,如果您需要更改默认设置,那么您需要显式配置自己的 DefaultRequestToViewNameTranslator bean。有关可配置的各种属性的详细信息,请参阅综合 DefaultRequestToViewNameTranslator javadocs。

22.14 HTTP缓存支持

良好的HTTP缓存策略可以显着提高Web应用程序的性能和客户端的体验。 'Cache-Control' HTTP响应标头主要负责此事件,以及 'Last-Modified''ETag' 等条件标头。

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

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

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

22.14.1 Cache-Control HTTP标头

Spring Web MVC支持许多用例和方法来为应用程序配置 "Cache-Control" 标头。虽然 RFC 7234 Section 5.2.2 完整地描述了该 Headers 及其可能的指令,但有几种方法可以解决最常见的情况。

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

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

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

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

CacheControl 构建器类简单地描述了可用的 "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' 和条件标头提供静态资源,以获得最佳性能。 Configuring a ResourceHttpRequestHandler 用于提供静态服务资源不仅通过读取文件的元数据本地写入 'Last-Modified' 标头,而且如果配置正确,还会 'Cache-Control' 标头。

您可以在 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响应头

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

the section called “Using HttpEntity” 中所述,控制器可以使用 HttpEntity 类型与请求/响应进行交互。返回 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' 标头,如果客户端发送的条件标头与Controller设置的缓存信息匹配,它将 also convert the response to an HTTP 304 Not Modified response with an empty body

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

这里有两个关键元素:调用 request.checkNotModified(lastModified) 并返回 null 。前者在返回 true 之前设置适当的响应状态和标头。后者与前者相结合,导致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框架结合使用。 ShallowEtagHeaderFilter 过滤器创建所谓的浅ETag(与深ETag相反,稍后将详细介绍)。过滤器缓存渲染的JSP(或其他内容)的内容,生成MD5哈希,并将其作为ETag返回响应中的标头。客户端下次发送对同一资源的请求时,会将该哈希值用作 If-None-Match 值。过滤器检测到此情况,再次呈现视图,并比较两个哈希值。如果它们相等,则返回 304

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

此过滤器有一个 writeWeakETag 参数,用于将过滤器配置为写入Weak ETag,如下所示: W/"02a2d595e6ed9a0b24f027f2b63b134d6" ,如 RFC 7232 Section 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() };
    }

}

有关详细信息,请参阅 Section 22.15, “Code-based Servlet container initialization”

22.15 基于代码的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提供的接口,可确保检测到您的实现并自动用于初始化任何Servlet 3容器。名为 AbstractDispatcherServletInitializerWebApplicationInitializer 的抽象基类实现通过简单地重写方法来指定servlet映射和 DispatcherServlet 配置的位置,从而更容易注册 DispatcherServlet

对于使用基于Java的Spring配置的应用程序,建议使用此方法:

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的Spring配置,则应直接从 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() };
    }

}

每个过滤器都会根据其具体类型添加默认名称,并自动映射到 DispatcherServlet

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

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

22.16 配置Spring MVC

Section 22.2.1, “Special Bean Types In the WebApplicationContext”Section 22.2.2, “Default DispatcherServlet Configuration” 解释了Spring MVC的特殊bean以及 DispatcherServlet 使用的默认实现。在在本节中,您将了解配置Spring MVC的另外两种方法。即MVC Java配置和MVC XML命名空间。

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

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

22.16.1 启用MVC Java配置或MVC XML命名空间

要启用MVC Java配置,请将注释 @EnableWebMvc 添加到您的 @Configuration 类之一:

@Configuration
@EnableWebMvc
public class WebConfig {
}

要在XML中实现相同的功能,请在DispatcherServlet上下文中使用 mvc:annotation-driven 元素(如果没有定义DispatcherServlet上下文,则使用根上下文中):

<?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 和其他注释。

它还支持以下功能:

  • 除了用于数据绑定的JavaBeans PropertyEditors外,还通过 ConversionService 实例进行Spring 3样式类型转换。

  • 通过 ConversionService 使用 @NumberFormat 注释支持 formatting 数字字段。

  • 使用 @DateTimeFormat 注释支持 formatting DateCalendarLong 和Joda-Time字段。

  • 如果类路径中存在JSR-303提供程序,则使用 @Valid 支持 validating @Controller 输入。

  • HttpMessageConverter 支持 @RequestBody 方法参数和 @ResponseBody 方法从 @RequestMapping@ExceptionHandler 方法返回值。

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

  • ByteArrayHttpMessageConverter 转换字节数组。

  • StringHttpMessageConverter 转换字符串。

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

  • SourceHttpMessageConverter 转换为 javax.xml.transform.Source

  • FormHttpMessageConverter 将表单数据转换为 MultiValueMap<String, String>

  • Jaxb2RootElementHttpMessageConverter 将Java对象转换为XML或从XML转换 - 如果存在JAXB2且类路径中不存在Jackson 2 XML扩展,则添加。

  • MappingJackson2HttpMessageConverter 转换为/从JSON转换 - 如果类路径中存在Jackson 2,则添加。

  • MappingJackson2XmlHttpMessageConverter 转换为XML或从XML转换 - 如果类路径中存在 Jackson 2 XML extension 则添加。

  • AtomFeedHttpMessageConverter 转换Atom提要 - 如果类路径中存在Rome,则添加。

  • RssChannelHttpMessageConverter 转换RSS提要 - 如果类路径中存在罗马则添加。

有关如何自定义这些默认转换器的详细信息,请参阅 Section 22.16.12, “Message Converters”

Jackson JSON和XML转换器是使用Jackson2ObjectMapperBuilder创建的ObjectMapper实例创建的,以便提供更好的默认配置。此构建器使用以下方法自定义Jackson的默认属性:DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES已禁用。 MapperFeature.DEFAULT_VIEW_INCLUSION已禁用。如果在类路径中检测到它们,它还会自动注册以下众所周知的模块: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 自定义提供的配置

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

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    // Override configuration methods...
}

要自定义 <mvc:annotation-driven/> 的默认配置,请检查它支持的属性和子元素。您可以查看 Spring MVC XML schema 或使用IDE的代码完成功能来发现可用的属性和子元素。

22.16.3 转换和格式化

默认情况下,会安装 NumberDate 类型的格式化程序,包括对 @NumberFormat@DateTimeFormat 注释的支持。如果类路径中存在Joda-Time,则还会安装对Joda-Time格式库的完全支持。要注册自定义格式化程序和转换器,请覆盖 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提供了一个 Validator interface ,可用于应用程序的所有层中的验证。在Spring MVC中您可以将其配置为用作全局 Validator 实例,以便在遇到 @Valid@Validated 控制器方法参数时使用,和/或通过 @InitBinder 方法在控制器中作为本地 Validator 使用。可以组合全局和本地验证器实例以提供复合验证。

Spring也通过 LocalValidatorFactoryBean 进行Bean验证,使Spring org.springframework.validation.Validator 接口适应Bean Validation javax.validation.Validator 合约。如下所述,此类可以作为全局验证器插入Spring MVC。

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

有时将LocalValidatorFactoryBean注入控制器或其他类是很方便的。最简单的方法是声明自己的@Bean并使用@Primary标记它,以避免与MVC Java配置提供的冲突。如果您更喜欢使用MVC Java配置中的那个,则需要从WebMvcConfigurationSupport覆盖mvcValidator方法,并声明该方法显式返回LocalValidatorFactory而不是Validator。有关如何切换以扩展所提供配置的信息,请参见第22.16.13节“使用MVC Java Config进行高级自定义”。

或者,您可以配置自己的全局 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>

要将全局验证与本地验证相结合,只需添加一个或多个本地验证器:

@Controller
public class MyController {

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

}

使用此最小配置,只要遇到 @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路径,检查“接受”标头,特定查询参数,或在没有请求任何内容时回退到默认内容类型。默认情况下,首先检查请求URI中的路径扩展,然后选中“Accept”标头。

如果相应的依赖项在类路径上,则默认情况下MVC Java配置和MVC命名空间注册 jsonxml ,_ 674568,_674569。其他路径扩展到媒体类型映射也可以明确注册,并且还具有将它们列为白名单作为安全扩展的效果,以便进行RFD攻击检测(有关详细信息,请参阅 the section called “Suffix Pattern Matching and RFD” )。

下面是通过MVC Java配置自定义内容协商选项的示例:

@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 实现。例如,您可以使用 ContentNegotiationManager 配置 ExceptionHandlerExceptionResolver ,该 ContentNegotiationManager 始终将请求的媒体类型解析为 "application/json" 。或者,如果没有请求内容类型,您可能希望插入具有某种逻辑的自定义策略来选择默认内容类型(例如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配置示例,它使用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和脚本模板也需要配置底层视图技术。

MVC名称空间提供专用元素。例如用FreeMarker的:

<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模式后的静态资源请求由 Resource 列表中的任何一个 Resource 提供。这提供了一种方便的方法来从Web应用程序根目录以外的位置提供静态资源,包括类路径上的位置。 cache-period 属性可用于设置远期未来到期标头(1年是优化工具(如Page Speed和YSlow)的推荐),以便客户端更有效地使用它们。处理程序还正确评估 Last-Modified 标头(如果存在),以便根据需要返回 304 状态代码,从而避免客户端已缓存的资源的不必要开销。例如,要从Web应用程序根目录中的 public-resources 目录提供URL模式为 /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年期限内提供这些资源,以确保最大限度地使用浏览器缓存并减少浏览器发出的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 caching support for static resources

mapping 属性必须是 SimpleUrlHandlerMapping 可以使用的Ant模式, location 属性必须指定一个或多个有效的资源目录位置。可以使用逗号分隔的值列表指定多个资源位置。将按指定的顺序检查指定的位置是否存在任何给定请求的资源。例如,要启用从Web应用程序根目录和类路径上任何jar中的已知路径 /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/"/>

在提供可能在部署新版本的应用程序时更改的资源时,建议您将版本字符串合并到用于请求资源的映射模式中,以便您可以强制客户端请求新部署的应用程序资源版本。对版本化URL的支持内置于框架中,可以通过在资源处理程序上配置资源链来启用。该链包含一个 ResourceResolver 实例,后跟一个或多个 ResourceTransformer 实例。它们可以一起提供任意分辨率和资源转换。

内置的 VersionResourceResolver 可以配置不同的策略。例如, FixedVersionStrategy 可以使用属性,日期或其他作为版本。 ContentVersionStrategy 使用根据资源内容计算的MD5哈希值(称为 "fingerprinting" URL)。请注意, VersionResourceResolver 将在提供资源时自动将已解析的版本字符串用作HTTP ETag标头值。

ContentVersionStrategy 是一个很好的默认选择,除非在无法使用的情况下(例如使用JavaScript模块加载器)。您可以针对不同的模式配置不同的版本策略,如下所示。请记住,计算基于内容的计算版本很昂贵,因此应在 生产环境 中启用资源链缓存。

Java配置示例;

@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示例:

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

为了使上述工作正常,应用程序还必须使用版本呈现URL。最简单的方法是配置 ResourceUrlEncodingFilter ,它包装响应并覆盖其 encodeURL 方法。这将适用于JSP,FreeMarker,Velocity以及调用响应 encodeURL 方法的任何其他视图技术。或者,应用程序也可以直接注入和使用 ResourceUrlProvider bean,它自动使用MVC Java配置和MVC命名空间声明。

WebJarsResourceResolver 也支持Webjars,当 "org.webjars:webjars-locator" 库在类路径上时会自动注册。此解析程序允许资源链从HTTP GET请求中解析与版本无关的库 "GET /jquery/jquery.min.js" 将返回资源 "/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处理静态资源请求。它配置 DefaultServletHttpRequestHandler ,其URL映射为 "/**" ,相对于其他URL映射的优先级最低。

此处理程序将所有请求转发到默认Servlet。因此,重要的是它按照所有其他URL HandlerMappings 的顺序保持最后。如果您使用 <mvc:annotation-driven> ,或者如果您要设置自己的自定义 HandlerMapping 实例,请务必将其 order 属性设置为低于 DefaultServletHttpRequestHandler 的值,即 Integer.MAX_VALUE

要使用默认设置启用该功能,请使用:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

或者用XML:

<mvc:default-servlet-handler/>

覆盖 "/" Servlet映射的警告是 RequestDispatcher 为默认Servlet必须通过名称而不是路径检索。 DefaultServletHttpRequestHandler 将尝试使用大多数主要Servlet容器(包括Tomcat,Jetty,GlassFish,JBoss,Resin,WebLogic和WebSphere)的已知名称列表,在启动时自动检测容器的默认Servlet。如果使用不同的名称自定义配置了默认Servlet,或者在默认Servlet名称未知的情况下使用了不同的Servlet容器,则必须显式提供默认的Servlet名称,如下例所示:

@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配置中的示例:

@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() 来在Java配置中实现 HttpMessageConverter 的自定义。

下面是一个示例,它使用自定义的 ObjectMapper 而不是默认值来添加Jackson JSON和XML转换器:

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

}

在此示例中, Jackson2ObjectMapperBuilder 用于为启用缩进的 MappingJackson2HttpMessageConverterMappingJackson2XmlHttpMessageConverter 创建公共配置,自定义日期格式和 jackson-module-parameter-names 的注册,这增加了对访问参数名称的支持(Java 8中添加的功能)。

使用Jackson XML支持启用缩进除了jackson-dataformat-xml之外还需要woodstox-core-asl依赖。

其他有趣的 Jackson 模块可用:

也可以在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命名空间提供了更高级别的构造,这些构造不需要深入了解为您创建的基础bean。相反,它可以帮助您专注于您的应用程序需求。但是,在某些时候,您可能需要更细粒度的控制,或者您可能只是希望了解底层配置。

实现更细粒度控制的第一步是查看为您创建的基础bean。在MVC Java配置中,您可以在 WebMvcConfigurationSupport 中看到javadoc和 @Bean 方法。此类中的配置将通过 @EnableWebMvc 注释自动导入。事实上,如果你打开 @EnableWebMvc ,你可以看到 @Import 语句。

更细粒度控制的下一步是在 WebMvcConfigurationSupport 中创建的一个bean上自定义属性,或者提供自己的实例。这需要两件事 - 删除 @EnableWebMvc 注释以防止导入,然后从 DelegatingWebMvcConfigurationWebMvcConfigurationSupport 的子类)扩展。这是一个例子:

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

}

应用程序应该只有一个配置扩展DelegatingWebMvcConfiguration或单个@EnableWebMvc带注释的类,因为它们都注册相同的底层bean。以这种方式修改bean不会阻止您使用本节前面所示的任何更高级别的构造。 WebMvcConfigurerAdapter子类和WebMvcConfigurer实现仍在使用中。

22.16.14 使用MVC命名空间进行高级自定义

使用MVC命名空间对为您创建的配置进行细粒度控制会有点困难。

如果确实需要这样做,而不是复制它提供的配置,请考虑配置一个 BeanPostProcessor ,它根据类型检测要自定义的bean,然后根据需要修改其属性。例如:

@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/> 中才能被检测到,或者如果您愿意,可以使用XML bean声明显式声明它。

Updated at: 5 months ago
VI. WebTable of content23. 查看技术
Comment
You are not logged in.

There are no comments.