Spring 框架参考文档(五)-The Web之Web MVC 框架
Part V. The Web
这部分参考文档将介绍 Spring 框架 对表现层的支持(特别是对 web 表现层的支持),和支持 web socket 形式式的web应用。
Spring 框架拥有自己的 web 框架 —— Spring Web MVC,这部分将会在前两章介绍。后续章节将介绍 Spring 框架与其他 web 技术的集成,如 JSF 等。
紧跟着介绍 Spring MVC 框架,portlet 框架。 Following that is coverage of Spring Framework’s MVC portlet framework.
结尾部分将全面覆盖 Spring 框架 Chapter 20, WebSocket 支持 (包括 Section 20.4, “STOMP Over WebSocket Messaging Architecture”)。
- Chapter 16, Web MVC 框架
- Chapter 17, 视图技术
- Chapter 18, 同其他web框架集成
- Chapter 19, Portlet MVC Framework
- Chapter 20, WebSocket 支持
16. Web MVC 框架
16.1 Spring Web MVC 框架介绍
Spring Web 模型-视图-控制器(MVC) 框架是围绕 DispatcherServlet
而设计的,其支持可配置的 handler 映射,视图解析,本地化、时区和主题的解析以及文件上传的功能。DispatcherServlet
负责将请求分发到不同的 handler。默认的 handler 通过@Controller
和 @RequestMapping
注解,提供多种灵活的处理方法。若加上@PathVariable
注解和其他辅助功能,你也可用使用 @Controller
机制来创建 RESTful web 站点和应用程序。
使用 Spring Web MVC ,你不需要实现框架指定的任何接口或继承任意基类,就可以使用任意对象作为命令对象(或表单对象)。Spring 的数据绑定相当之灵活,比如,Spring可以将不匹配的类型作为应用可识别的验证错误,而不是系统错误,所以,你不需要去重复定义一套属性一致而类型是原始字符串的业务逻辑对象,去处理错误的提交或对字符串进行类型转换。反过来说就是,spring 允许你直接将正确类型的参数绑定到业务逻辑对象。
Spring 的视图解析也相当之灵活。完成一个请求,Controller
通常是负责准备一个数据模型 Map
和选择一个指定的视图,当然,也支持直接将数据写到响应流里。视图名称的解析是高度可配置的,可以通过文件扩展名、accept header 的 Content-Type、bean 的名称、属性文件或自定义的 ViewResolver
实现来解析。模型(Model,MVC 中的 M),是一个 Map
接口,提供对视图数据的完全抽象,可直接与渲染模版集成,如 JSP,Veloctiy,Freemarker;或直接生成原始数据,或xml、json等其他类型的响应内容。模型 Map
接口只是负责将数据转换为合适格式,如 jsp 请求属性,velocity 的 model 等。
16.1.1 Spring Web MVC 的特点
Spring’s web module includes many unique web support features:
- Clear separation of roles. Each role — controller, validator, command object, form object, model object,
DispatcherServlet
, handler mapping, view resolver, and so on — can be fulfilled by a specialized object. - Powerful and straightforward configuration of both framework and application classes as JavaBeans. This configuration capability includes easy referencing across contexts, such as from web controllers to business objects and validators.
- Adaptability, non-intrusiveness, and flexibility. Define any controller method signature you need, possibly using one of the parameter annotations (such as @RequestParam, @RequestHeader, @PathVariable, and more) for a given scenario.
- Reusable business code, no need for duplication. Use existing business objects as command or form objects instead of mirroring them to extend a particular framework base class.
- Customizable binding and validation. Type mismatches as application-level validation errors that keep the offending value, localized date and number binding, and so on instead of String-only form objects with manual parsing and conversion to business objects.
- Customizable handler mapping and view resolution. Handler mapping and view resolution strategies range from simple URL-based configuration, to sophisticated, purpose-built resolution strategies. Spring is more flexible than web MVC frameworks that mandate a particular technique.
- Flexible model transfer. Model transfer with a name/value
Map
supports easy integration with any view technology. - Customizable locale, time zone and theme resolution, support for JSPs with or without Spring tag library, support for JSTL, support for Velocity without the need for extra bridges, and so on.
- A simple yet powerful JSP tag library known as the Spring tag library that provides support for features such as data binding and themes. The custom tags allow for maximum flexibility in terms of markup code. For information on the tag library descriptor, see the appendix entitled Chapter 35, spring.tld
- A JSP form tag library, introduced in Spring 2.0, that makes writing forms in JSP pages much easier. For information on the tag library descriptor, see the appendix entitled Chapter 36, spring-form.tld
- Beans whose lifecycle is scoped to the current HTTP request or HTTP
Session
. This is not a specific feature of Spring MVC itself, but rather of theWebApplicationContext
container(s) that Spring MVC uses. These bean scopes are described in Section 5.5.4, “请求作用域、会话作用域和全局会话作用域”
16.1.2 其他 MVC 实现的可插入性
Non-Spring MVC implementations are preferable for some projects. Many teams expect to leverage their existing investment in skills and tools, for example with JSF.
If you do not want to use Spring’s Web MVC, but intend to leverage other solutions that Spring offers, you can integrate the web MVC framework of your choice with Spring easily. Simply start up a Spring root application context through its ContextLoaderListener
, and access it through its ServletContext
attribute (or Spring’s respective helper method) from within any action object. No "plug-ins" are involved, so no dedicated integration is necessary. From the web layer’s point of view, you simply use Spring as a library, with the root application context instance as the entry point.
Your registered beans and Spring’s services can be at your fingertips even without Spring’s Web MVC. Spring does not compete with other web frameworks in this scenario. It simply addresses the many areas that the pure web MVC frameworks do not, from bean configuration to data access and transaction handling. So you can enrich your application with a Spring middle tier and/or data access tier, even if you just want to use, for example, the transaction abstraction with JDBC or Hibernate.
16.2 The DispatcherServlet
像其他 web MVC 框架一样, Spring web MVC 框架也是基于请求驱动,围绕一个核心 Servlet 转发请求到对应的 Controller 而设计的,提供对web 程序开发的基础的支持。然而 Spring 的 DispatcherServlet
并不仅仅拥有这些,因为 Spring MVC 框架集成了 Spring IOC 容器,因此,Spring MVC 可以使用 Spring 提供的其他功能。
如图,展示了 Spring web MVC DispatcherServlet
处理请求的流程。 The request processing workflow of the Spring Web MVC DispatcherServlet
is illustrated in the following diagram. The pattern-savvy reader will recognize that the DispatcherServlet
is an expression of the "Front Controller" design pattern (this is a pattern that Spring Web MVC shares with many other leading web frameworks).
Spring Web MVC 请求处理的宏观图
DispatcherServlet
继承了 HttpServlet
,是一个真实的 Servlet,因此可以在 web.xml
文件声明。另外你需要使用 url 匹配元件指定 DispatcherServlet
处理的请求。如下例子,使用了标准 java EE Servlet 配置,配置了一个 DispatcherServlet
的声明和匹配 url 元件:
<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>
在刚才配置的例子中,所有以 /example
开始的请求都会被名为 example
的 DispatcherServlet
所处理。在 Servlet 3.0+ 环境,也可以以编程方式配置上述DispatcherServlet
。如下代码与上述 web.xml
配置例子等效:
public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext container) { ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet()); registration.setLoadOnStartup(1); registration.addMapping("/example/*"); } }
Spring MVC 提供的 WebApplicationInitializer
接口,可以确保检测到基于代码的配置和用来自动初始化基于 Servlet 3 的容器。它有一个抽象的实现类AbstractDispatcherServletInitializer
,简化了DispatcherServlet
的注册,只需指定 servlet mapping 就可以了,详情请参考 Code-based Servlet container initialization。
上述的操作仅仅是开启了 Spring Web MVC 之旅的第一步,现在你需要配置 Spring Web MVC 所使用到的各种 bean(这不在本节讨论范围)。
如 Section 5.15, “ApplicationContext 的附加功能” 所描述那样,在Spring里可以获取到 ApplicationContext
实例。在 web MVC 框架,每一个 DispatcherServlet
都拥有自己的 WebApplicationContext
,这个 WebApplicationContext
继承了根 WebApplicationContext
定义的所有 bean.
As detailed in Section 5.15, “ApplicationContext 的附加功能”, ApplicationContext
instances in Spring can be scoped. In the Web MVC framework, eachDispatcherServlet
has its own WebApplicationContext
, which inherits all the beans already defined in the root WebApplicationContext
. These inherited beans can be overridden in the servlet-specific scope, and you can define new scope-specific beans local to a given Servlet instance.
- Spring Web MVC 上下文的层次结构 image::images/mvc-contexts.gif[width=400]
DispatcherServlet
在初始化时,Spring MVC 会查找 web 应用 WEB_INF
目录下的[servlet-name]-servlet.xml 并创建在此文件定义的 bean,若在全局范围里有一个名称相同的 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>
上述配置,要求应用程序在 WEB-INF
目录下有一个 golfing-servlet.xml
文件,在这个文件里,会包含 Spring MVC 的所有组件(beans)。你可以通过定义 servlet 初始化参数来改变[servlet-name]-servlet.xml 文件的路径,如下:
<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
不同的是,WebApplicationContext
支持主题解析(查看 Section 16.9, “主题的使用”),并且知道其所关联的的 Servlet(通过一个指向 ServletContext
的引用)。WebApplicationContext
绑定在ServletContext
里,在需要访问的时候,可以使用 RequestContextUtils
提供的静态方法获取 WebApplicationContext
。
16.2.1 WebApplicationContext 的专用 bean
DispatcherServlet
使用了其专用的 bean 来处理请求和渲染视图。这些 bean 是 Spring 的组成部分之一,你可以选择在 WebApplicationContext
配置所使用一个或多个专用的bean。当然,比并不需要一开始就去配置这些专用的 bean,因为在你不配置这些 bean时,Spring 会维护一系列默认的 bean。首先我们看一下DispatcherServlet
依赖了哪些专用的 bean,后续再作详解。
Table 16.1. WebApplicationContext 专用 bean 列表
Bean 类型 | 解释 |
---|---|
将传入的请求映射到处理器,与一系列基于各种条件的 pre- 和 post- 处理器,这些处理器根据 | |
HandlerAdapter | 帮助 |
将异常映射到指定视图,也支持自定义更加复杂的异常处理流程 | |
将合理的视图名称解释为真实的视图类型 | |
解释客户端所在地区和其时区(本地化),以便提供国际化的视图 | |
解释 web 程序可用的主题,比如,提供个性化的布局 | |
解释 multi-part 请求,比如,在 html form 里支持文件上传 | |
Stores and retrieves the "input" and the "output" |
16.2.2 默认的 DispatcherServlet 配置
如上一节所说,每一个 DispatcherServlet
都维持了一系列默认的实现。这些默认实现的信息保存在 org.springframework.web.servlet
包里的DispatcherServlet.properties
文件。
尽管所有专用的 bean 都有其合理的默认值。迟早你也需要根据实际去自定义这些 bean 的中一个或多个属性值。例如一种很常见的自定义应用,配置一个InternalResourceViewResolver
,其 prefix
为视图文件的父文件夹。
不管这些默认细节如何实现,在这里都需要清楚一个概念——一旦在 WebApplicationContext
配置自己专用的 bean,就有效覆盖了原有一系列默认的实现,至少也会作为这个专用 bean 的一个实例。比如,配置了 InternalResourceViewResolver
,默认的一系列 ViewResolver
实现都会被覆盖。
在 Section 16.16, “Configuring Spring MVC” 章节,你可以知道 Spring MVC 的其他配置选项,包括 MVC java 配置和 MVC XML 的命名空间。不管你选择如何去配置应用,这一节解释的理念基本都可以帮到你。
In Section 16.16, “Configuring Spring MVC” you’ll learn about other options for configuring Spring MVC including MVC Java config and the MVC XML namespace both of which provide a simple starting point and assume little knowledge of how Spring MVC works. Regardless of how you choose to configure your application, the concepts explained in this section are fundamental should be of help to you.
16.2.3 DispatcherServlet 处理顺序
在你建立一个 DispatcherServlet
之后,并处理一个传进来的请求时,DispatcherServlet
会按照以下顺序年来处理这个请求:
- 寻找
WebApplicationContext
,并将WebApplicationContext
作为一个属性绑定到请求里,以便控制器或其他原件在后续中使用。默认会以DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
键绑定到请求里。 - 将本地化解析器绑定到请求里,以便在处理这个请求时,原件可以解析到客户端的地区(为了渲染视图,准备日期等)。如果你不需要本地化解析器,可以忽略这个步骤。
- 将主题解析其绑定到请求里,让原件(如视图)决定去使用哪一种主题。如果你不需要使用主题,可以忽略这个步骤。
- 如果你指定一个 multipart file 解析器,会检查这个请求包含 multiparts 请求。当发现了 multiparts,这个请求会被封装为
MultipartHttpServletRequest
对象,提供给后续原件处理。详细信息,清参考 Section 16.10, “Spring’s multipart (文件上传) 支持”。 - 寻找合适的 handler。如何找到这个 handler,执行与这个 handler 关联的执行链,目的是准备一个 model 或 渲染。
- 如果返回一个 model,渲染相对应的视图。反之(可能是因为 pre- 或 post- 处理器拦截了这个请求,也可能是权限问题),便不渲染任何视图,因为这个请求可能已执行完成。
handler 异常解析是在 WebApplicationContext
声明的,接收在上述处理过程抛出的异常。使用异常解析器,你可以根据异常信息自定义其处理方式。
DispatcherServlet
也支持返回 last-modification-date( 在 Servlet API 里有相应的接口)。让指定请求返回 last-modification-date 的处理方式非常简单:DispatcherServlet
寻找是否有匹配的 handler,并检查这个 handler 是否实现了 LastModified接口,如果实现了该接口,方法long getLastModified(request)
的返回值会返回到客户端。
你可以通过在 web.xml 里设置 Servlet 的初始化参数,来自定义一个 DispatcherServlet
实例。下表列出了 DispatcherServlet
支持的参数:
Table 16.2. DispatcherServlet initialization parameters
参数 | 说明 |
---|---|
|
|
| 传给 上下文( |
|
|
16.3 Implementing Controllers
Controllers provide access to the application behavior that you typically define through a service interface. Controllers interpret user input and transform it into a model that is represented to the user by the view. Spring implements a controller in a very abstract way, which enables you to create a wide variety of controllers.
Spring 2.5 introduced an annotation-based programming model for MVC controllers that uses annotations such as @RequestMapping
, @RequestParam
,@ModelAttribute
, and so on. This annotation support is available for both Servlet MVC and Portlet MVC. Controllers implemented in this style do not have to extend specific base classes or implement specific interfaces. Furthermore, they do not usually have direct dependencies on Servlet or Portlet APIs, although you can easily configure access to Servlet or Portlet facilities.
在 spring-projects Org on Github 上,有好几个项目都使用这一节谈到的注解,有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
允许灵活的配置方法签名。在上述例子中,helloWorld 方法接受一个 Model
参数,并返回一个视图名称,当然也允许添加方法入参和返回不同类型的值,这些内容将会在后面解释。@Controller
、@RequestMapping
和其他一些功能注解组成了 Spring MVC 实现的基础,这一节将会谈到这些组成的注解和在 Servlet 环境的普遍用法。
16.3.1 使用 @Controller 定义控制器
@Controller
表明了被注解类的服务角色——控制器。Spring 不需要去继承任何 Controller 的基类或引用任意的 Servlet API。当然了,如何你需要的, 你仍然可以引用 Servlet API。
@Controller
注解定义了被注解类的原型,表明了注解类的服务角色。dispatcher 会扫描这些被 @Controller
标记的类并检测 @RequestMapping
标记的方法(见下一节)。
你可以在 dispatcher 上下文显式定义控制器 bean,不过,为了与 Spring 支持在类路径上检测 bean 并自动注册这些 bean 定义 保持一致,@Controller
也许允许自动检测。
要开启注解控制器的扫描功能,需要在你的配置里添加组件扫描元件。如下 xml 所示,可以使用 spring-context 模式开启此扫描功能:
<?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>
16.3.2 使用 @RequestMapping 映射请求
你可以在类或指定 handler 方法上,使用 @RequestMapping
注解来映射 URL,如 /appointments
。
You use the @RequestMapping
annotation to map URLs such as /appointments
onto an entire class or a particular handler method. Typically the class-level annotation maps a specific request path (or path pattern) onto a form controller, with additional method-level annotations narrowing the primary mapping for a specific HTTP method request method ("GET", "POST", etc.) or an HTTP request parameter condition.
如下例子来自于 Petcare 项目,展示了如何使用 @RequestMapping 注解。
@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(value="/{day}", method = RequestMethod.GET) public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) { return appointmentBook.getAppointmentsForDay(day); } @RequestMapping(value="/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
。第一个用在了类上,表示@RequestMapping
这个控制器下的所有 handler 方法都是相对 /appointments
路径而言的。get() 方法对 @RequestMapping
做了进一步的细化 —— 此方法只接收 GET 请求方式,换句话说就是 /appointments
的GET 请求会调用这个方法; add() 方法也做一个类似的细化; getNewForm() 方法在 RequestMapping 上组合定义了 http 方法和路径,因此此方法会处理 appointments/new
的 GET 请求。
getForDay() 方法演示了 @RequestMapping
的另外一种使用方式 —— URI 模版(查看 the next section)。
在类上添加 @RequestMapping
不是必须的,如果没有,所有的路径都是绝对路径,而不是相对路径。来自 PetClinic 项目的例子展示如何使用 @RequestMapping
来定义一个 multi-action 控制器:
@Controller public class ClinicController { private final Clinic clinic; @Autowired public ClinicController(Clinic clinic) { this.clinic = clinic; } @RequestMapping("/") public void welcomeHandler() { } @RequestMapping("/vets") public ModelMap vetsHandler() { return new ModelMap(this.clinic.getVets()); } }
上面的例子没有指定 GET、PUT、POST 等方法,因为 @RequestMapping
默认匹配所有的 http 方法。使用 @RequestMapping(method=GET)
可以缩小其映射范围。
@Controller
' 和 AOP 代理
某些时候,控制器需要在运行时 AOP 代理修饰一番。
In some cases a controller may need to be decorated with an AOP proxy at runtime. One example is if you choose to have @Transactional
annotations directly on the controller. When this is the case, for controllers specifically, we recommend using class-based proxying. This is typically the default choice with controllers. However if a controller must implement an interface that is not a Spring Context callback (e.g. InitializingBean
, *Aware
, etc), you may need to explicitly configure class-based proxying. For example with <tx:annotation-driven />
, change to <tx:annotation-driven proxy-target-class="true" />
.
New Support Classes for @RequestMapping methods in Spring MVC 3.1
Spring 3.1 introduced a new set of support classes for @RequestMapping
methods called RequestMappingHandlerMapping
and RequestMappingHandlerAdapter
respectively. They are recommended for use and even required to take advantage of new features in Spring MVC 3.1 and going forward. The new support classes are enabled by default by the MVC namespace and the MVC Java config but must be configured explicitly if using neither. This section describes a few important differences between the old and the new support classes.
Prior to Spring 3.1, type and method-level request mappings were examined in two separate stages — a controller was selected first by theDefaultAnnotationHandlerMapping
and the actual method to invoke was narrowed down second by the AnnotationMethodHandlerAdapter
.
With the new support classes in Spring 3.1, the RequestMappingHandlerMapping
is the only place where a decision is made about which method should process the request. Think of controller methods as a collection of unique endpoints with mappings for each method derived from type and method-level @RequestMapping
information.
This enables some new possibilities. For once a HandlerInterceptor
or a HandlerExceptionResolver
can now expect the Object-based handler to be aHandlerMethod
, which allows them to examine the exact method, its parameters and associated annotations. The processing for a URL no longer needs to be split across different controllers.
There are also several things no longer possible:
- Select a controller first with a
SimpleUrlHandlerMapping
orBeanNameUrlHandlerMapping
and then narrow the method based on@RequestMapping
annotations. - Rely on method names as a fall-back mechanism to disambiguate between two
@RequestMapping
methods that don’t have an explicit path mapping URL path but otherwise match equally, e.g. by HTTP method. In the new support classes@RequestMapping
methods have to be mapped uniquely. - Have a single default method (without an explicit path mapping) with which requests are processed if no other controller method matches more concretely. In the new support classes if a matching method is not found a 404 error is raised.
The above features are still supported with the existing support classes. However to take advantage of new Spring MVC 3.1 features you’ll need to use the new support classes.
URI 模版模式
URI 模版能便捷访问 @RequestMapping
方法中 URL 的某些部分。
URI 模版是一个类似于 URI 的字符串,其中包含了一个或多个变量。当你将这些变量替换掉市,就变回了 URI。 URI 模版格式 proposed RFC 定义了如何参数化 URI。比如,URI 模版 http://www.example.com/users/{userId}
,包含了变量 userId,设置此变量为 __fred,就会变成http://www.example.com/users/fred
。
可在方法入参上使用注解 @PathVariable
绑定 URI 的模版参数:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET) public String findOwner(@PathVariable String ownerId, Model model) { Owner owner = ownerService.findOwner(ownerId); model.addAttribute("owner", owner); return "displayOwner"; }
URI 模版 " /owners/{ownerId}
" 指定了参数 owernId
。当控制器处理这个请求时,会将 URI 中匹配的部分赋值给 owernId
变量。如,当传入 /owners/fred
请求时,owernId
的值就是 fred
。
[提示]
在处理 @PathVariable 注解时,Srping MVC 是根据名称来匹配 URI 模版变量的。你可以在注解里指定这个名称:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET) public String findOwner(@PathVariable("ownerId") String theOwner, Model model) { // implementation omitted }
如果URI 模版变量名和入参名一致,可以省略这个细节。只要你的代码不是不带调试信息的编译,Spring MVC 将匹配入参名和 URI 变量名。
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET) public String findOwner(@PathVariable String ownerId, Model model) { // implementation omitted }
一个方法可以有任意个 @PathVariable
注解。
@RequestMapping(value="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET) 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
注解时,map 会填充所有 URI 模版变量。
URI 模版可以组合类型和参数路径的 @RequestMapping。因此,findPet
可以处理类似 /owners/42/pets/21
的URI 。
@Controller @RequestMapping("/owners/{ownerId}") public class RelativePathUriTemplateController { @RequestMapping("/pets/{petId}") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // implementation omitted } }
@PathVariable
参数可以是任意的简单类型(如 int,long,Date 等),Spring 会自动将其进行类型转换,转换出错会抛出 TypeMismatchException
。你可以注册支持解析其他数据类型,详情请看 the section called “方法参数和类型转换” 和 the section called “自定义 WebDataBinder 的初始化”.
在 URI 模版上使用正则表达式
偶尔,在URI 模版变量里,你会需要用到更加精确的控制。比如 "/spring-web/spring-web-3.0.5.jar"
这样的URI,该如何拆分成多个部分?
@RequestMapping
注解支持在 URI 模版变量里使用正则表达式。语法 {变量名:正则表达式}
,第一个部分定义变量的名称,第二部分是正则表达式。如
@RequestMapping("/spring-web/{symbolicName:[a-z-]}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]
}")
public void handle(@PathVariable String version, @PathVariable String extension) {
// ...
}
}
路径模式
@RequestMapping
注解处理支持 URI 模版,也支持使用 Ant 风格模式(如:/myPath/*.do)。同时支持组合使用 URI 模版和 Ant 风格模式(如:e.g. `/owners/*/pets/{petId}
)。
路径模式比较
当一个 URL 与多个模式匹配时,会设法找出最具体的那一个路径。
当模式中的 URI 模版变量和通配符的数量相对较少,会认为其相对具体。如:/hotels/{hotel}/*
相对 /hotels/{hotel}/**
更加合适,因为 /hotels/{hotel}/*
只有一个URI 模版变量和一个通配符,而 hotels/{hotel}/**` 有一个 URI 模版变量和两个通配符。
当两个模式中的 URI 模版变量和通配符数量相同时,更详细的那一个会认为相对适合。如 /foo/bar*
比 /foo/*
更为详细。
当两个模式中变量个数详细程度相同时,使用更少通配符的那一个会相对具体。如 /hotels/{hotel}
比 /hotels/{hotel}
更加具体。
一些额外的特别规定:
- 任意模式都比默认全匹配
/**
模式具体。如:/api/{a}/{b}/{c}
比/**
更加具体。 - 任意不包含两个通配符的模式都比前缀模式(如
/public/**
) 更加具体。/public/path3/{a}/{b}/{c}
比/public/**
更加具体。
For the full details see AntPatternComparator
in AntPathMatcher
. Note that the PathMatcher can be customized (see Section 16.16.9, “Path Matching” in the section on configuring Spring MVC).
全部细节信息可查看 AntPathMatcher
的 AntPatternComparator
。注意,PathMatcher 是可以自定义的。(可查看 Section 16.16.9, “Path Matching” 的 Spring MVC 配置)
路径模式与占位符
在 @RequestMapping
注解中,模式支持使用占位符来代替本地属性 和/或者 系统属性和环境变量。当路径需要通过配置文件来自定义时,适合使用占位符。占位符的详细信息,可以查看 PropertyPlaceholderConfigurer
的 javadoc。
路径模式的后缀匹配
Spring MVC 默认自动执行 ".*"
的后缀匹配,所以当一个控制器匹配 /person
时,其也隐式匹配 /person.*
。这样的设计允许通过文件扩展名来说明内容的类型名比如 /person.pdf
, /person.xml
等。然而,这里会有一个常犯的陷阱,当路径最后的片段是 URI 模版变量时(如 /person/{id}
),请求 /person/1.json
可以正确匹配路径,变量 id=1,拓展名为 json,可当 id 自身包含 . (如 /person/joe@email.com
),那匹配结果就不是我们所期望的,显然 ".com" 不是文件扩展名。
解决这个问题的正确方法是配置 Spring MVC 只对注册的文件扩展名做后缀匹配,这要求内容(扩展名)协商好。想做更多的了解,可以先看 Section 16.16.4, “Content Negotiation” 后看 Section 16.16.9, “Path Matching”,这里展示里如何启用后缀模式和如何只使用注册的后缀模式。
矩阵变量
The URI specification RFC 3986 defines the possibility of including name-value pairs within path segments. There is no specific term used in the spec. The general "URI path parameters" could be applied although the more unique "Matrix URIs", originating from an old post by Tim Berners-Lee, is also frequently used and fairly well known. Within Spring MVC these are referred to as matrix variables.
Matrix variables can appear in any path segment, each matrix variable separated with a ";" (semicolon). For example: "/cars;color=red;year=2012"
. Multiple values may be either "," (comma) separated "color=red,green,blue"
or the variable name may be repeated "color=red;color=green;color=blue"
.
If a URL is expected to contain matrix variables, the request mapping pattern must represent them with a URI template. This ensures the request can be matched correctly regardless of whether matrix variables are present or not and in what order they are provided.
Below is an example of extracting the matrix variable "q":
// GET /pets/42;q=11;r=22 @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET) public void findPet(@PathVariable String petId, @MatrixVariable int q) { // petId == 42 // q == 11 }
Since all path segments may contain matrix variables, in some cases you need to be more specific to identify where the variable is expected to be:
// GET /owners/42;q=11/pets/21;q=22 @RequestMapping(value = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET) public void findPet( @MatrixVariable(value="q", pathVar="ownerId") int q1, @MatrixVariable(value="q", pathVar="petId") int q2) { // q1 == 11 // q2 == 22 }
A matrix variable may be defined as optional and a default value specified:
// GET /pets/42 @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET) public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) { // q == 1 }
All matrix variables may be obtained in a Map:
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @RequestMapping(value = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET) public void findPet( @MatrixVariable Map<String, String> matrixVars, @MatrixVariable(pathVar="petId"") Map<String, String> petMatrixVars) { // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23] // petMatrixVars: ["q" : 11, "s" : 23] }
Note that to enable the use of matrix variables, you must set the removeSemicolonContent
property of RequestMappingHandlerMapping
to false
. By default it is set to true
.
The MVC Java config and the MVC namespace both provide options for enabling the use of matrix variables. If you are using Java config, The Advanced Customizations with MVC Java Config section describes how the In the MVC namespace, the <?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 请求头匹配可消费的媒体类型,才认为这个请求是可映射的。如:
@Controller @RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json") public void addPet(@RequestBody Pet pet, Model model) { // 实现省略 }
Consumable media type expressions can also be negated as in !text/plain to match to all requests other than those with Content-Type of text/plain.
consumes 条件支持在类和方法上使用。与大多数情况不一样的是,方法中 consumes 会覆盖类上使用 的consumes,而不是继承 类上使用 的consumes。 |
可生产的媒体类型
你可以指定一系列可消费的媒体类型来压缩主要映射。当 Accept 请求头匹配可消费的媒体类型,才认为这个请求是可映射的。此外,使用 produces 条件可以保证实际类容的类型,produces 用于形成 response 方面的媒体类型,如:
@Controller @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json") @ResponseBody public Pet getPet(@PathVariable String petId, Model model) { // 省略实现 }
Just like with consumes, producible media type expressions can be negated as in !text/plain to match to all requests other than those with an Accept header value oftext/plain.
produces 条件支持在类和方法上使用。与大多数情况不一样的是,方法中 produces 会覆盖类上使用 的produces ,而不是继承 类上使用 的produces 。 |
请求参数和头字段值
你可以通过请求参数条件来压缩请求匹配范围,如使用 "myParam"
, "!myParam"
, 或 "myParam=myValue"
。前两种情况表示 存在/不存在,第三种指定了参数值。如下给出指定参数值的例子:
@Controller @RequestMapping("/owners/{ownerId}") public class RelativePathUriTemplateController { @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // 省略实现 } }
类似的,头字段也支持 存在/不存在 和基于指定头字段值的匹配:
@Controller @RequestMapping("/owners/{ownerId}") public class RelativePathUriTemplateController { @RequestMapping(value = "/pets", method = RequestMethod.GET, headers="myHeader=myValue") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // 省略实现 } }
尽管你可以使用媒体类型通配符来匹配 Content-Type 和 Accept 头字段(如 "content-type=text/*" 可以匹配 "text/plain" 和 "text/html"),还是建议使用consumes 和 produces 条件,因为 consumes 和 produces 专门为此而使用的。 |
16.3.3 定义 @RequestMapping 处理方法
@RequestMapping 处理方法允许非常灵活的签名,其支持方法参数和返回值(在这一节谈到)。除了 BindingResult
参数,其他类型参数顺序随意(下一节会谈到)。
Spring 3.1 介绍了一套 |
可支持的方法参数类型
如下是可以支持的方法参数:
- Request 或 response 对象 (Servlet API). 选择任意指定的 request 或 response 类型,如
ServletRequest
o或HttpServletRequest
. - Session 对象 (Servlet API):需要是
HttpSession
类型. 这种类型的参数会强制合适 session 的存在。因此,这个参数永远不会为 null。
Session 访问可能是非线程安全的,特别是在 Servlet 环境。当允许多个请求并发访问 session 时,可以考虑设置 |
org.springframework.web.context.request.WebRequest
或org.springframework.web.context.request.NativeWebRequest
.允许通过请求参数访问和 request/session 属性访问,没有与原始的 Servlet/Portlet API 绑定。java.util.Locale
给当前请求本地化,取决于最具体的本地化解析器,实际上取决与是 Servlet 环境配置的LocaleResolver
。java.io.InputStream
/java.io.Reader
可访问请求的内容。这是 Servlet API 暴露的原生 InputStream/Reader 。java.io.OutputStream
/java.io.Writer
用于 产生 response 的内容。这是 Servlet API 暴露的原生 OutputStream/Writer.org.springframework.http.HttpMethod
可访问 HTTP 请求方法。java.security.Principal
包含了当前授权用户。@PathVariable
注解参数,可访问 URI 模版变量。可查看 the section called “URI 模版模式”。@MatrixVariable
annotated parameters for access to name-value pairs located in URI path segments. See the section called “矩阵变量”.@RequestParam
注解参数,可访问指定 Servlet request 参数。参数值会被转换为方法参数的类型。可查看the section called “使用 @RequestParam 将请求参数绑定到方法参数”.@RequestHeader
注解参数,可访问指定 Servlet request 的 HTTP 头字段。参数值会被转换为方法参数的类型。可查看 the section called “使用 @RequestHeader 映射请求头字段属性”@RequestBody
注解参数,可访问 HTTP 请求体。参数值使用HttpMessageConverter
转换为方法参数类型,可查看 the section called “使用 @RequestBody 映射请求体”@RequestPart
注解参数,可访问 "multipart/form-data" 请求的内容。可查看 Section 16.10.5, “Handling a file upload request from programmatic clients” 和Section 16.10, “Spring’s multipart (文件上传) 支持”HttpEntity<?>
参数,可访问 Servlet request 的HTTP 头和内容。请求流使用HttpMessageConverter
转换为 entity body,可查看 the section called “使用 HttpEntity”java.util.Map
/org.springframework.ui.Model
/org.springframework.ui.ModelMap
用来丰富隐式模型(暴露给 web 视图)org.springframework.web.servlet.mvc.support.RedirectAttributes
to specify the exact set of attributes to use in case of a redirect and also to add flash attributes (attributes stored temporarily on the server-side to make them available to the request after the redirect).RedirectAttributes
is used instead of the implicit model if the method returns a "redirect:" prefixed view name orRedirectView
.- Command or form objects to bind request parameters to bean properties (via setters) or directly to fields, with customizable type conversion, depending on
@InitBinder
methods and/or the HandlerAdapter configuration. See thewebBindingInitializer
property onRequestMappingHandlerAdapter
. Such command objects along with their validation results will be exposed as model attributes by default, using the command class class name - e.g. model attribute "orderAddress" for a command object of type "some.package.OrderAddress". TheModelAttribute
annotation can be used on a method argument to customize the model attribute name used. org.springframework.validation.Errors
/org.springframework.validation.BindingResult
已执行的命令或表单对象的验证错误(the immediately preceding method argument).org.springframework.web.bind.support.SessionStatus
status handle for marking form processing as complete, which triggers the cleanup of session attributes that have been indicated by the@SessionAttributes
annotation at the handler type level.org.springframework.web.util.UriComponentsBuilder
a builder for preparing a URL relative to the current request’s host, port, scheme, context path, and the literal part of the servlet mapping.
Errors
或 BindingResult
参数必须跟随所绑定的模型对象,如果方法签名有超过一个模型对象的,Spring 会为其分别创建 BindingResult
实例。因此,如下例子没有效果:
BindingResult and @ModelAttribute 的无效顺序.
@RequestMapping(method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }
注意,因为 Pet
和 BindingResult
之间存在参数 Model
。要其有效,你需要重新排序参数等我位置,如下:
@RequestMapping(method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }
JDK 1.8’s |
可支持的方法返回类型
以下是可支持的返回类型:
- A
ModelAndView
object, with the model implicitly enriched with command objects and the results of@ModelAttribute
annotated reference data accessor methods. - A
Model
object, with the view name implicitly determined through aRequestToViewNameTranslator
and the model implicitly enriched with command objects and the results of@ModelAttribute
annotated reference data accessor methods. - A
Map
object for exposing a model, with the view name implicitly determined through aRequestToViewNameTranslator
and the model implicitly enriched with command objects and the results of@ModelAttribute
annotated reference data accessor methods. - A
View
object, with the model implicitly determined through command objects and@ModelAttribute
annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring aModel
argument (see above). - A
String
value that is interpreted as the logical view name, with the model implicitly determined through command objects and@ModelAttribute
annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring aModel
argument (see above). void
if the method handles the response itself (by writing the response content directly, declaring an argument of typeServletResponse
/HttpServletResponse
for that purpose) or if the view name is supposed to be implicitly determined through aRequestToViewNameTranslator
(not declaring a response argument in the handler method signature).- If the method is annotated with
@ResponseBody
, the return type is written to the response HTTP body. The return value will be converted to the declared method argument type usingHttpMessageConverter
s. See the section called “使用 @ResponseBody 映射响应体”. - An
HttpEntity<?>
orResponseEntity<?>
object to provide access to the Servlet response HTTP headers and contents. The entity body will be converted to the response stream usingHttpMessageConverter
s. See the section called “使用 HttpEntity”. - An
HttpHeaders
object to return a response with no body. - A
Callable<?>
can be returned when the application wants to produce the return value asynchronously in a thread managed by Spring MVC. - A
DeferredResult<?>
can be returned when the application wants to produce the return value from a thread of its own choosing. - A
ListenableFuture<?>
can be returned when the application wants to produce the return value from a thread of its own choosing. - Any other return type is considered to be a single model attribute to be exposed to the view, using the attribute name specified through
@ModelAttribute
at the method level (or the default attribute name based on the return type class name). The model is implicitly enriched with command objects and the results of@ModelAttribute
annotated reference data accessor methods.
使用 @RequestParam 将请求参数绑定到方法参数
在控制器里,使用 @RequestParam
将请求参数绑定到方法参数。
如下代码片段演示了其用法:
@Controller @RequestMapping("/pets") @SessionAttributes("pet") public class EditPetForm { // ... @RequestMapping(method = RequestMethod.GET) public String setupForm(@RequestParam("petId") int petId, ModelMap model) { Pet pet = this.clinic.loadPet(petId); model.addAttribute("pet", pet); return "petForm"; } // ... }
使用 @RequestParam
的参数默认是必须提供的,当然,你可以指定其为可选的,将 @RequestParam
的 reqired
属性设置 false
即可。(如,@RequestParam(value="id", required=false)
).
如果方法参数的类型不是 String
,类型转换会自动执行,可查看 the section called “方法参数和类型转换”。
如果将 @RequestParam
用于 Map<String, String>
或 MultiValueMap<String, String>
参数,此参数 map 会填充所有的请求参数。
使用 @RequestBody 映射请求体
@RequestBody
注解参数表示该参数将与 HTTP 请求体绑定。例子:
@RequestMapping(value = "/something", method = RequestMethod.PUT) public void handle(@RequestBody String body, Writer writer) throws IOException { writer.write(body); }
你使用 HttpMessageConverter
将请求体转换为方法参数。HttpMessageConverter
负责将请求信息转换为指定对象和将指定对象转换为请求体。RequestMappingHandlerAdapter
默认支持 @RequestBody
使用以下 HttpMessageConverters
。
ByteArrayHttpMessageConverter
转换为字节数组。StringHttpMessageConverter
转换为字符串。FormHttpMessageConverter
表单数据与 MultiValueMap<String, String> 互转。SourceHttpMessageConverter
表单数据与 javax.xml.transform.Source 互转。
转换起的详细信息,可查看 Message Converters。也提醒一下,当使用 MVC 命名空间 或 MVC Java 配置时,默认注册了一系列转换器,详细信息可查看Section 16.16.1, “启用 MVC Java 配置或 MVC XML 命名空间”。
如果你想读写 XML 的,需要注册 MarshallingHttpMessageConverter
,MarshallingHttpMessageConverter
包含了 org.springframework.oxm
包中的Marshaller
和 Unmarshaller
实现。如下例子演示了直接在配置文件注册 MarshallingHttpMessageConverter
,如果你的应用通过 MVC 命名空间或 MVC Java 配置的,可查看 Section 16.16.1, “启用 MVC Java 配置或 MVC XML 命名空间”。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <util:list id="beanList"> <ref bean="stringHttpMessageConverter"/> <ref bean="marshallingHttpMessageConverter"/> </util:list> </property </bean> <bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter"/> <bean id="marshallingHttpMessageConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"> <property name="marshaller" ref="castorMarshaller" /> <property name="unmarshaller" ref="castorMarshaller" /> </bean> <bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>
@RequestBody
方法参数可添加 @Valid
注解,被注解的参数会使用配置的 Validator
来验证。当使用 MVC 命名空间或 mvc Java 配置时,应用会自动配置 JSR-303 验证器(前提是在类路径能找到 JSR-303 的实现)。
类似于 @ModelAttribute
参数,Errors
参数也可用来检测错误。当 Errore
参数没有声明时,或抛出 MethodArgumentNotValidException
。此异常会被DefaultHandlerExceptionResolver
处理 —— 向客户端发送 400
错误。
可以查看 Section 16.16.1, “启用 MVC Java 配置或 MVC XML 命名空间”,了解如何使用 MVC 命名空间和 MVC Java 配置来配置信息转换器和验证器。 |
使用 @ResponseBody 映射响应体
@ResponseBody
的使用类似于 @RequestBody
。此注解用在方法上,用来表示直接将返回数据写到 HTTP 响应体里。注意,不是将数据放到 Model 中,或解析为视图名称。例子:
@RequestMapping(value = "/something", method = RequestMethod.PUT) @ResponseBody public String helloWorld() { return "Hello World"; }
上述例子会将 Hello World
文本写到 HTTP 响应流中。
与 @RequestBody
一样,Spring 使用 HttpMessageConverter
将返回对象转换为响应体。转换的详细信息可以查看上一节信息转换器。
使用 @RestController 创建 REST 控制器
一种比较常见的场景,控制器实现 REST API,只会返回 JSON、XML 或其他自定义媒体类型。为了方便,你可以在控制器上添加 @RestController
注解,而不是在每一个 @RequestMapping
上使用 @ResponseBody
。
@RestController
是一个结合了 @ResponseBody
和 @Controller
的注解。不仅如此,@RestController
赋予了控制器更多的意义,在未来的版本中可能会携带额外的语义。。
与常规 @Controller
一样,@RestController
可以协助 @ControllerAdvice
bean。详细信息可查看the section called “使用 @ControllerAdvice
注解增强控制器”。
使用 HttpEntity
HttpEntity
的用法类似于 @RequestBody
和 @ResponseBody
注解。除了可以访问请求/响应体,HttpEntity
(和特用与响应的子类 ResponseEntity
) 还可以访问 request 和 response 的头字段。例子:
@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
头字段的值,以字节数组的形式读取了请求体,随后将 MyRequestHeader
添加到 response,将 Hello World
写到响应流和设置响应状态码为 201(Created).
与 @RequestBody
和 @ResponseBody
一样,Srping 使用 HttpMessageConverter
将数据与请求/响应流互转,可查看上一节信息转换器
在方法上使用 @ModelAttribute
@ModelAttribute
可用欲方法或方法参数中。这一部分将介绍 @ModelAttribute
在方法中的使用,下一部分介绍其在方法啊参数中的使用。
在方法上使用 @ModelAttribute
注解,表示此方法的目的在于添加一个或多个模型属性。这种方法所支持的参数类型与 @RequestMapping
一样,不同的是,其不能直接映射到 request。另外,在同一个控制器里,@ModelAttribute
会在 @RequestMapping
之前调用。举几个例子:
// 添加一个属性 // 方法的返回值会以 "account" 键添加到 model // 可通过 @ModelAttribute("myAccount") 自定义 @ModelAttribute public Account addAccount(@RequestParam String number) { return accountManager.findAccount(number); } // 添加多个属性 @ModelAttribute public void populateModel(@RequestParam String number, Model model) { model.addAttribute(accountManager.findAccount(number)); // 再添加多个…… }
@ModelAttribute
用来将常用属性填充到模型里。如:填充下拉框陈述、宠物类型、或命令对象(如 Account,用于表示 HTML 表单数据)。最后一种情况会在下一部分深入谈论。
留意一下 @ModelAttribute
方法的两种使用方式。第一种,在方法里隐式添加一个属性并返回;第二种,方法里接收 Model
参数,并将任意个属性添加到 Model
中。你可以根据自身需要选择其中一种使用方式。
一个控制器可以有多个 @ModelAttribute
方法。在同一个控制器中,所有 @ModelAttribute
方法都会在 @RequestMapping
方法之前调用。
@ModelAttribute
方法也可以在 @ControllerAdvice
注解的类中使用,类似的可应用于其他控制器,详细信息可查看 the section called “使用 @ControllerAdvice
注解增强控制器”。
当模型属性名称没有显式定义,会是一种什么样的情况?在这种情况下,默认使用参数类型名称作为模型属性名称。如,当方法返回 |
@ModelAttribute
注解也可用在 @RequestMapping
方法中。这种情况下,@RequestMapping
方法的返回值将解析为模型属性,而不是视图名称。相反,视图名称来源于视图名称的约定,就类似于方法返回 void,可查看Section 16.13.3, “The View - RequestToViewNameTranslator”。
在方法参数上使用 @ModelAttribute
如前文所述,@ModelAttribute
也可以用在方法或方法参数上。这一部分将介绍其在方法参数上的使用。
@ModelAttribute
注解的方法参数,表示该参数会从模型中检索。如果该参数不在模型中,此参数会先执行初始化或后添加到模型里;如果参数存在模型里,此参数字段会填充所有匹配其名称的请求参数。这就是大家熟知的 Spring MVC 数据绑定 —— 一个可以免除你逐个解析表单属性而又非常有用的机制。
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute Pet pet) { }
上述所给例子,Pet 实例可以在哪里获取?有以下几种选择:
- 可能已存到模型里,自然可从模型中获取,因为使用了
@SessionAttributes
— 可查看 the section called “使用 @SessionAttributes 将模型属性存储到 HTTP session”。 - 可能已存到模型里,自然可从模型中获取,因为同一个控制器里的
@ModelAttribute
方法 — 在上一部分有解释。 - 可以从 URI 模版变量和类型转换器获取(在下面详细描述会有所说明)
- 可以使用默认构造函器初始化
@ModelAttribute
方法是一种从数据库检索索性的常用方式,所检索到的属性可以使用 @SessionAttributes
在请求之间存储。在某些情况下,使用 URI 模版变量和类型转化器可能会方便一些,举个例子:
@RequestMapping(value="/accounts/{account}", method = RequestMethod.PUT) public String save(@ModelAttribute("account") Account account) { }
上面这个例子,模型属性名称(这里为 "account") 可以映射 URI 模版模版变量。如果你注册了 Converter<String, Account>
(一个可将 String
类型的 account 值转换为 Account
实例),那上述例子在没有 @ModelAttribute
方法下,也是可工作的。
下一步是数据绑定。WebDataBinder
类映射请求参数名 —— 包括查询字符参数和表单属性 —— 根据名称添加模型属性。在类型转换之后,如果需要,映射属性会被填充。数据绑定在验证在 Chapter 7, 数据校验、数据绑定和类型转换 有介绍,在控制器层自定义数据绑定的处理方法,在 the section called “自定义 WebDataBinder 的初始化” 有介绍。
在处理数据绑定中,可能会抛出错误(如缺失必要属性或类型转换错误)。如要检查这些错误,可在 @ModelAttribute
参数后添加 BindingResult
参数。
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { if (result.hasErrors()) { return "petForm"; } // ... }
使用 BindingResult
, 你可以判断是否存在数据绑定错误。当存在错误时,通常会渲染此表单 —— 通过 Spring 的 <errors>
表单标签展示这些错误。
除了数据绑定,你也可以通过自定以验证器传递 BindingResult
来执行验证。当中 BindingResult
保存了数据绑定的错误。这样可以允许数据绑定和验证错误统一收集,随后回馈给用户。
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { new PetValidator().validate(pet, result); if (result.hasErrors()) { return "petForm"; } // ... }
你也可以自动执行验证,配置 JSR-303 `@Valid 注解即可。
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { if (result.hasErrors()) { return "petForm"; } // ... }
至于如何配置和使用验证,可查看 Section 7.8, “Spring Validation” 和 Chapter 7, 数据校验、数据绑定和类型转换。
使用 @SessionAttributes 将模型属性存储到 HTTP session
类上的 @SessionAttributes
注解,声明了 session 属性可以被指定 hadler 使用。这通常会列出需要透明存储到 session 或其他会话存储的模型属性或模型属性的类型,作为后续请求的 form-backing beam。
如下代码片段展示了 @SessionAttributes 的用法,其指定了模型属性名称:
@Controller @RequestMapping("/editPet.do") @SessionAttributes("pet") public class EditPetForm { // ... }
指定 redirect 和 flash 属性
在重定向 URL 中,所有模型属性默认暴露给 URI 模版变量,剩下的属性(原始类型或原始类型集合/数组)会自动拼接到查询参数中。
然而,在一个带注解的控制器中,模型也许包含了额外的属性(用于渲染,如下拉框属性)。在重定向场景中,要准确控制这些属性,可在 @RequestMapping
方法中声明 RedirectAttributes
类型参数,并往其添加 RedirectView
使用的属性。如果这个控制方法没有重定向,将使用 RedirectAttributes
的内容,否则使用默认Model
的内容。
RequestMappingHandlerAdapter
提供了一个 "ignoreDefaultModelOnRedirect"
标志,用来设置在控制方法重定向时,默认Model
的内容是否从不使用。相反,控制器方法应该声明 RedirectAttributes
类型属性,否则会没有任何属性传递给 RedirectView
。为了向后兼容,MVC 命名空间和 MVC Java 配置都将"ignoreDefaultModelOnRedirect"
设置为 false。可我们还是建议你在新应用里将其设置为 true。
RedirectAttributes
接口也可以用来添加 flash 属性。与其他重定向属性(在重定向 URL 中销毁)不同的是,flash 属性会保存到 HTTP session(因此 flash 属性也不会在 URL 上出现)。作用于重定向 URL 的控制器里的模型会自动接收这些 flash 属性,之后,flash 属性会从 session 中移除。可查看 Section 16.6, “使用 flash 属性”,了解 Spring MVC 对 flash 属性的常规支持。
处理 "application/x-www-form-urlencoded" 数据
前一节节介绍了 @ModelAttribute
在客户端浏览器表单提交的使用。同时也推荐使用 @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>
HttpPutFormContentFilter
过滤器拦截了content 类型为 application/x-www-form-urlencoded
的 HTTP PUT 和 PATCH 请求,它从请求体中读取表单数据并将其封装为 ServletRequest
,以便后续使用 ServletRequest.getParameter*()
方法获取表单数据。
由于 |
使用 @CookieValue 映射 cookie 值
@CookieValue
注解允许将方法参数与HTTP cookie 值绑定。
假设下面这个 cookie 值已经从 http 请求获得。 Let us consider that the following cookie has been received with an http request:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
一下例子演示了如何获取 JSESSIONID
cookie 值。
@RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) { //... }
如果方法参数不是 String
类型,类型转换会自动执行,可查看 the section called “方法参数和类型转换”。
@CookieValue
注解支持在 Servlet 和 Portlet 环境中注解 handler 方法。
使用 @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-Encoding
和 Keep-Alive
头字段值:
@RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive) { //... }
如果方法参数不是 String
类型,类型转换会自动执行,可查看 the section called “方法参数和类型转换”。
将注解 @RequestHeader
用于 Map<String, String>
,MultiValueMap<String, String>
, 或 HttpHeaders
参数,map 会填充所有头字段值。
内置支持将逗号分隔的字符串转换为数组/集合 或 系统其他熟知的类型。比如,带有 |
@RequestHeader
注解支持在 Servlet 和 Portlet 环境中注解 handler 方法。
方法参数和类型转换
从请求(包括请求参数、路径参数、请求头和cookie)里提取的字符串,基本上都需要转换为方法参数或类属性的目标类型,如将请求参数绑定到 @ModelAttribute
参数属性。如果目标类型不是 String,Spring 自动将其转换为合适的类型。支持所有简单类型的转换(如 int、long、Date等)。你可以进一步通过 WebDataBinder
自定义处理过程(可查看the section called “自定义 WebDataBinder 的初始化”)或使用 FormattingConversionService
注册 Formatters
(可查看 Section 7.6, “Spring Field Formatting”)。
自定义 WebDataBinder 的初始化
To customize request parameter binding with PropertyEditors through Spring’s WebDataBinder
, you can use @InitBinder
-annotated methods within your controller,@InitBinder
methods within an @ControllerAdvice
class, or provide a custom WebBindingInitializer
. See the the section called “使用 @ControllerAdvice
注解增强控制器” section for more details.
使用 @InitBinder 自定义数据绑定
使用 @InitBinder 注解的控制器,可以直接配置绑定 web 数据。@InitBinder
表示那些用于初始化 WebDataBinder
的方法,将用于填充 注解 handler 方法的命名/表单对象参数。
除了命令/表单对象和类似验证结果对象,这些初始化绑定方法支持 @RequestMapping
所支持的所有参数。初始化绑定方法规定没有返回值。因此,这些方法常声明为void
,通常也会包含 WebDataBinder
参数,与 WebRequest
或 java.util.Locale
结合使用,可注册上下文特定的编辑器。
如下例子,演示了 @InitBinder
的使用 —— 配置一个解释所有表单 java.util.Date
属性的 CustomDateEditor
:
@Controller public class MyFormController { @InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); } // ... }
配置自定义的 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
- 注解的类中使用, 这种情况下,@InitBinder
用于映射控制器。这也提供另外一种选择 —— 使用WebBindingInitializer
,详情可查看 the section called “使用 @ControllerAdvice
注解增强控制器”。
支持 Last-Modified 响应头,方便缓存内容
依据 Servlet API 对 getLastModified
方法的定义,@RequestMapping
方法也可以支持 'Last-Modified'
HTTP 请求,以方便内容缓存,加快响应。getLastModified
方法的调用,需要计算给定请求的 lastModified 值(long 类型),并将其与请求头 'If-Modified-Since'
相互比较,若认为其没有更新,返回状态码为 304 的 response。注解控制器可如此实现这一需求:
@RequestMapping public String myHandleMethod(WebRequest webRequest, Model model) { long lastModified = // 1. 应用指定的计算值 if (request.checkNotModified(lastModified)) { // 2. 快速退出 —— 不作进一步处理 return null; } // 3. 否则,进一步处理请求,本质是准备内容 model.addAttribute(...); return "myViewName"; }
注意两个关键点:调用 request.checkNotModified(lastModified)
和 返回 null
。前者将 response 的状态设置为 304 后才返回 true
。后者,与前者的使用结合,告知 Spring MVC 不对请求作进一步的处理。
@ControllerAdvice
注解增强控制器
使用 @ControllerAdvice
注解可以让实现类通过类路径自动检测出来。当使用 MVC 命名空间或 MVC Java 配置时,此此功能是默认启动的。
带有 @ControllerAdvice
注解的类,可以包含 @ExceptionHandler
、@InitBinder
, 和 @ModelAttribute
注解的方法,并且这些注解的方法会通过控制器层次应用到所有 @RequestMapping
方法中,而不用一一在控制器内部声明。
Classes annotated with @ControllerAdvice
can contain @ExceptionHandler
, @InitBinder
, and @ModelAttribute
annotated methods, and these methods will apply to @RequestMapping
methods across all controller hierarchies as opposed to the controller hierarchy within which they are declared.
@ControllerAdvice
注解也可在属性中注明,将应用到哪些目标控制器:
// 应用到所有 @RestController 控制器 @ControllerAdvice(annotations = RestController.class) public class AnnotationAdvice {} // 应用到指定包下的控制器 @ControllerAdvice("org.example.controllers") public class BasePackageAdvice {} // 应用到指定类型的控制器 @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) public class AssignableTypesAdvice {}
希望加深了解的,可以 Check out @ControllerAdvice
文档。
支持 Jackson 序列化视图
在将对象序列化到响应体时,偶尔需要过滤敏感的数据。为了提供这一功能,Spring MVC 内置支持使用 Jackson 序列化视图