21.3.3 定义@RequestMapping注解的处理方法(handler method)

使用@RequestMapping注解的处理方法可以拥有非常灵活的方法签名,它支持的方法参数及返回值类型将在接下来的小节讲述。大多数参数都可以任意的次序出现,除了唯一的一个例外:BindingResult参数。这在下节也会详细描述。

Spring 3.1中新增了一些类,用以增强注解了@RequestMapping的处理方法,分别是RequestMappingHandlerMapping类和RequestMappingHandlerAdapter类。我们鼓励使用这组新的类,如果要使用Spring 3.1及以后版本的新特性,这组类甚至是必须使用的。这些增强类在MVC的命名空间配置和MVC的Java编程方式配置中都是默认开启的,如果不是使用这两种方法,那么就需要显式地配置。

支持的方法参数类型

下面列出所有支持的方法参数类型:

  • 请求或响应对象(Servlet API)。可以是任何具体的请求或响应类型的对象,比如,ServletRequestHttpServletRequest对象等。
  • HttpSession类型的会话对象(Servlet API)。使用该类型的参数将要求这样一个session的存在,因此这样的参数永不为null

存取session可能不是线程安全的,特别是在一个Servlet的运行环境中。如果应用可能有多个请求同时并发存取一个session场景,请考虑将RequestMappingHandlerAdapter类中的"synchronizeOnSession"标志设置为"true"。

  • org.springframework.web.context.request.WebRequestorg.springframework.web.context.request.NativeWebRequest。允许存取一般的请求参数和请求/会话范围的属性(attribute),同时无需绑定使用Servlet/Portlet的API
  • 当前请求的地区信息java.util.Locale,由已配置的最相关的地区解析器解析得到。在MVC的环境下,就是应用中配置的LocaleResolverLocaleContextResolver
  • 与当前请求绑定的时区信息java.util.TimeZone(java 6以上的版本)/java.time.ZoneId(java 8),由LocaleContextResolver解析得到
  • 用于存取请求正文的java.io.InputStreamjava.io.Reader。该对象与通过Servlet API拿到的输入流/Reader是一样的
  • 用于生成响应正文的java.io.OutputStreamjava.io.Writer。该对象与通过Servlet API拿到的输出流/Writer是一样的
  • org.springframework.http.HttpMethod。可以拿到HTTP请求方法
  • 包装了当前被认证用户信息的java.security.Principal
  • @PathVariable注解的方法参数,其存放了URI模板变量中的值。详见“URI模板变量”一节
  • @MatrixVariable注解的方法参数,其存放了URI路径段中的键值对。详见“矩阵变量”一节
  • @RequestParam注解的方法参数,其存放了Servlet请求中所指定的参数。参数的值会被转换成方法参数所声明的类型。详见“使用@RequestParam注解绑定请求参数至方法参数”一节
  • @RequestHeader注解的方法参数,其存放了Servlet请求中所指定的HTTP请求头的值。参数的值会被转换成方法参数所声明的类型。详见“使用@RequestHeader注解映射请求头属性”一节.
  • @RequestBody注解的参数,提供了对HTTP请求体的存取。参数的值通过HttpMessageConverter被转换成方法参数所声明的类型。详见“使用@RequestBody注解映射请求体”一节"
  • @RequestPart注解的参数,提供了对一个"multipart/form-data请求块(request part)内容的存取。更多的信息请参考21.10.5 “处理客户端文件上传的请求”一节21.10 “Spring对多部分文件上传的支持”一节
  • HttpEntity<?>类型的参数,其提供了对HTTP请求头和请求内容的存取。请求流是通过HttpMessageConverter被转换成entity对象的。详见“HttpEntity”一节
  • java.util.Map/org.springframework.io.Model/org.springframework.ui.ModelMap类型的参数,用以增强默认暴露给视图层的模型(model)的功能
  • org.springframework.web.servlet.mvc.support.RedirectAttributes类型的参数,用以指定重定向下要使用到的属性集以及添加flash属性(暂存在服务端的属性,它们会在下次重定向请求的范围中有效)。详见“向重定向请求传递参数”一节
  • 命令或表单对象,它们用于将请求参数直接绑定到bean字段(可能是通过setter方法)。你可以通过@InitBinder注解和/或HanderAdapter的配置来定制这个过程的类型转换。具体请参考RequestMappingHandlerAdapterwebBindingInitializer属性的文档。这样的命令对象,以及其上的验证结果,默认会被添加到模型model中,键名默认是该命令对象类的类名——比如,some.package.OrderAddress类型的命令对象就使用属性名orderAddress类获取。ModelAttribute注解可以应用在方法参数上,用以指定该模型所用的属性名
  • org.springframework.validation.Errors / org.springframework.validation.BindingResult验证结果对象,用于存储前面的命令或表单对象的验证结果(紧接其前的第一个方法参数)。
  • org.springframework.web.bind.support.SessionStatus对象,用以标记当前的表单处理已结束。这将触发一些清理操作:@SessionAttributes在类级别注解的属性将被移除
  • org.springframework.web.util.UriComponentsBuilder构造器对象,用于构造当前请求URL相关的信息,比如主机名、端口号、资源类型(scheme)、上下文路径、servlet映射中的相对部分(literal part)等

在参数列表中,ErrorsBindingResult参数必须紧跟在其所绑定的验证对象后面。这是因为,在参数列表中允许有多于一个的模型对象,Spring会为它们创建不同的BindingResult实例。因此,下面这样的代码是不能工作的:

BindingResult与@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) { ... }

对于一些带有required属性的注解(比如@RequestParam@RequestHeader等),JDK 1.8的java.util.Optional可以作为被它们注解的方法参数。在这种情况下,使用java.util.Optionalrequired=false的作用是相同的。

支持的方法返回类型

以下是handler方法允许的所有返回类型:

  • ModelAndView对象,其中model隐含填充了命令对象,以及注解了@ModelAttribute字段的存取器被调用所返回的值。
  • Model对象,其中视图名称默认由RequestToViewNameTranslator决定,model隐含填充了命令对象以及注解了@ModelAttribute字段的存取器被调用所返回的值
  • Map对象,用于暴露model,其中视图名称默认由RequestToViewNameTranslator决定,model隐含填充了命令对象以及注解了@ModelAttribute字段的存取器被调用所返回的值
  • View对象。其中model隐含填充了命令对象,以及注解了@ModelAttribute字段的存取器被调用所返回的值。handler方法也可以增加一个Model类型的方法参数来增强model
  • String对象,其值会被解析成一个逻辑视图名。其中,model将默认填充了命令对象以及注解了@ModelAttribute字段的存取器被调用所返回的值。handler方法也可以增加一个Model类型的方法参数来增强model
  • void。如果处理器方法中已经对response响应数据进行了处理(比如在方法参数中定义一个ServletResponseHttpServletResponse类型的参数并直接向其响应体中写东西),那么方法可以返回void。handler方法也可以增加一个Model类型的方法参数来增强model
  • 如果处理器方法注解了ResponseBody,那么返回类型将被写到HTTP的响应体中,而返回值会被HttpMessageConverters转换成所方法声明的参数类型。详见使用"@ResponseBody注解映射响应体"一节
  • HttpEntity<?>ResponseEntity<?>对象,用于提供对Servlet HTTP响应头和响应内容的存取。对象体会被HttpMessageConverters转换成响应流。详见使用HttpEntity一节
  • HttpHeaders对象,返回一个不含响应体的response
  • Callable<?>对象。当应用希望异步地返回方法值时使用,这个过程由Spring MVC自身的线程来管理
  • DeferredResult<?>对象。当应用希望方法的返回值交由线程自身决定时使用
  • ListenableFuture<?>对象。当应用希望方法的返回值交由线程自身决定时使用
  • ResponseBodyEmitter对象,可用它异步地向响应体中同时写多个对象,also supported as the body within a ResponseEntity
  • SseEmitter对象,可用它异步地向响应体中写服务器端事件(Server-Sent Events),also supported as the body within a ResponseEntity
  • StreamingResponseBody对象,可用它异步地向响应对象的输出流中写东西。also supported as the body within a ResponseEntity
  • 其他任何返回类型,都会被处理成model的一个属性并返回给视图,该属性的名称为方法级的@ModelAttribute所注解的字段名(或者以返回类型的类名作为默认的属性名)。model隐含填充了命令对象以及注解了@ModelAttribute字段的存取器被调用所返回的值

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

你可以使用@RequestParam注解将请求参数绑定到你控制器的方法参数上。

下面这段代码展示了它的用法:

@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {
    // ...
    @RequestMapping(method = RequestMapping.GET)
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ,..
}

若参数使用了该注解,则该参数默认是必须提供的,但你也可以把该参数标注为非必须的:只需要将@RequestParam注解的required属性设置为false即可(比如,@RequestParam(path="id", required=false))。

若所注解的方法参数类型不是String,则类型转换会自动地发生。详见"方法参数与类型转换"一节

@RequestParam注解的参数类型是Map<String, String>或者MultiValueMap<String, String>,则该Map中会自动填充所有的请求参数。

使用@RequestBody注解映射请求体

方法参数中的@RequestBody注解暗示了方法参数应该被绑定了HTTP请求体的值。举个例子:

@RequestMapping(path = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}

请求体到方法参数的转换是由HttpMessageConverter完成的。HttpMessageConverter负责将HTTP请求信息转换成对象,以及将对象转换回一个HTTP响应体。对于@RequestBody注解,RequestMappingHandlerAdapter提供了以下几种默认的HttpMessageConverter支持:

  • ByteArrayHttpMessageConverter用以转换字节数组
  • StringHttpMessageConverter用以转换字符串
  • FormHttpMessageConverter用以将表格数据转换成MultiValueMap<String, String>或从MultiValueMap<String, String>中转换出表格数据
  • SourceHttpMessageConverter用于javax.xml.transform.Source类的互相转换

关于这些转换器的更多信息,请参考"HTTP信息转换器"一节。另外,如果使用的是MVC命名空间或Java编程的配置方式,会有更多默认注册的消息转换器。更多信息,請參考"启用MVC Java编程配置或MVC XML命令空间配置"一节

若你更倾向于阅读和编写XML文件,那么你需要配置一个MarshallingHttpMessageConverter并为其提供org.springframework.oxm包下的一个MarshallerUnmarshaller实现。下面的示例就为你展示如何直接在配置文件中配置它。但如果你的应用是使用MVC命令空间或MVC Java编程的方式进行配置的,则请参考"启用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编程的方式配置的,框架会假设在classpath路径下存在一个符合JSR-303规范的验证器,并自动将其作为默认配置。

@ModelAttribute注解的参数一样,Errors也可以被传入为方法参数,用于检查错误。如果没有声明这样一个参数,那么程序会抛出一个MethodArgumentNotValidException异常。该异常默认由DefaultHandlerExceptionResolver处理,处理程序会返回一个400错误给客户端。

关于如何通过MVC命令空间或MVC Java编程的方式配置消息转换器和验证器,也请参考"启用MVC Java编程配置或MVC XML命令空间配置"一节

使用@ResponseBody注解映射响应体

@ResponseBody注解与@RequestBody注解类似。@ResponseBody注解可被应用于方法上,标志该方法的返回值(更正,原文是return type,看起来应该是返回值)应该被直接写回到HTTP响应体中去(而不会被被放置到Model中或被解释为一个视图名)。举个例子:

@RequestMapping(path = "/something", method = RequestMethod.PUT)
@ResponseBody
public String helloWorld() {
    return "Hello World"
}

上面的代码结果是文本Hello World将被写入HTTP的响应流中。

@RequestBody注解类似,Spring使用了一个HttpMessageConverter来将返回对象转换到响应体中。关于这些转换器的更多信息,请参考"HTTP信息转换器"一节

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

当今让控制器实现一个REST API是非常常见的,这种场景下控制器只需要提供JSON、XML或其他自定义的媒体类型内容即可。你不需要在每个@RequestMapping方法上都增加一个@ResponseBody注解,更简明的做法是,给你的控制器加上一个@RestController的注解。

@RestController是一个原生内置的注解,它结合了@ResponseBody@Controller注解的功能。不仅如此,它也让你的控制器更表义,而且在框架未来的发布版本中,它也可能承载更多的意义。

与普通的@Controller无异,@RestController也可以与@ControllerAdvicebean配合使用。更多细节,请见使用@ControllerAdvice辅助控制器

使用HTTP实体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来对请求流和响应流进行转换。关于这些转换器的更多信息,请阅读上一小节以及"HTTP信息转换器"这一节

对方法使用@ModelAttribute注解

@ModelAttribute注解可被应用在方法或方法参数上。本节将介绍其被注解于方法上时的用法,下节会介绍其被用于注解方法参数的用法。

注解在方法上的@ModelAttribute说明了方法的作用是用于添加一个或多个属性到model上。这样的方法能接受与@RequestMapping注解相同的参数类型,只不过不能直接被映射到具体的请求上。在同一个控制器中,注解了@ModelAttribute的方法实际上会在@RequestMapping方法之前被调用。以下是几个例子:

// 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方法通常被用来填充一些公共需要的属性或数据,比如一个下拉列表所预设的几种状态,或者宠物的几种类型,或者去取得一个HTML表单渲染所需要的命令对象,比如Account等。

留意@ModelAttribute方法的两种风格。在第一种写法中,方法通过返回值的方式默认地将添加一个属性;在第二种写法中,方法接收一个Model对象,然后可以向其中添加任意数量的属性。你可以在根据需要,在两种风格中选择合适的一种。

一个控制器可以拥有数量不限的@ModelAttribute方法。同个控制器内的所有这些方法,都会在@RequestMapping方法之前被调用。

@ModelAttribute方法也可以定义在@ControllerAdvice注解的类中,并且这些@ModelAttribute可以同时对许多控制器生效。具体的信息可以参考使用@ControllerAdvice辅助控制器

属性名没有被显式指定的时候又当如何呢?在这种情况下,框架将根据属性的类型给予一个默认名称。举个例子,若方法返回一个Account类型的对象,则默认的属性名为"account"。你可以通过设置@ModelAttribute注解的值来改变默认值。当向Model中直接添加属性时,请使用合适的重载方法addAttribute(..)-即,带或不带属性名的方法。

@ModelAttribute注解也可以被用在@RequestMapping方法上。这种情况下,@RequestMapping方法的返回值将会被解释为model的一个属性,而非一个视图名。此时视图名将以视图命名约定来方式来决议,与返回值为void的方法所采用的处理方法类似——请见视图:请求与视图名的对应

在方法参数上使用@ModelAttribute注解

如上一小节所解释,@ModelAttribute注解既可以被用在方法上,也可以被用在方法参数上。这一小节将介绍它注解在方法参数上时的用法。

注解在方法参数上的@ModelAttribute说明了该方法参数的值将由model中取得。如果model中找不到,那么该参数会先被实例化,然后被添加到model中。在model中存在以后,请求中所有名称匹配的参数都会填充到该参数中。这在Spring MVC中被称为数据绑定,一个非常有用的特性,节约了你每次都需要手动从表格数据中转换这些字段数据的时间。

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute Pet pet) { }

以上面的代码为例,这个Pet类型的实例可能来自哪里呢?有几种可能:

  • 它可能因为@SessionAttributes注解的使用已经存在于model中——详见"在请求之间使用@SessionAttributes注解,使用HTTP会话保存模型数据"一节
  • 它可能因为在同个控制器中使用了@ModelAttribute方法已经存在于model中——正如上一小节所叙述的
  • 它可能是由URI模板变量和类型转换中取得的(下面会详细讲解)
  • 它可能是调用了自身的默认构造器被实例化出来的

@ModelAttribute方法常用于从数据库中取一个属性值,该值可能通过@SessionAttributes注解在请求中间传递。在一些情况下,使用URI模板变量和类型转换的方式来取得一个属性是更方便的方式。这里有个例子:

@RequestMapping(path = "/accounts/{account}", method = RequestMethod.PUT)
public String save(@ModelAttribute("account") Account account) {

}

上面这个例子中,model属性的名称("account")与URI模板变量的名称相匹配。如果你配置了一个可以将String类型的账户值转换成Account类型实例的转换器Converter<String, Account>,那么上面这段代码就可以工作的很好,而不需要再额外写一个@ModelAttribute方法。

下一步就是数据的绑定。WebDataBinder类能将请求参数——包括字符串的查询参数和表单字段等——通过名称匹配到model的属性上。成功匹配的字段在需要的时候会进行一次类型转换(从String类型到目标字段的类型),然后被填充到model对应的属性中。数据绑定和数据验证的问题在第8章 验证,数据绑定和类型转换中提到。如何在控制器层来定制数据绑定的过程,在这一节 "定制WebDataBinder的初始化"中提及。

进行了数据绑定后,则可能会出现一些错误,比如没有提供必须的字段、类型转换过程的错误等。若想检查这些错误,可以在注解了@ModelAttribute的参数紧跟着声明一个BindingResult参数:

@RequestMapping(path = "/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被用于记录数据绑定过程的错误,因此除了数据绑定外,你还可以把该对象传给自己定制的验证器来调用验证。这使得数据绑定过程和验证过程出现的错误可以被搜集到一处,然后一并返回给用户:

@RequestMapping(path = "/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(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {

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

    // ...

}

关于如何配置并使用验证,可以参考第8.8小节 "Spring验证"第8章 验证,数据绑定和类型转换

在请求之间使用@SessionAttributes注解,使用HTTP会话保存模型数据

类型级别的@SessionAttributes注解声明了某个特定处理器所使用的会话属性。通常它会列出该类型希望存储到session或converstaion中的model属性名或model的类型名,一般是用于在请求之间保存一些表单数据的bean。

以下的代码段演示了该注解的用法,它指定了模型属性的名称

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

使用"application/x-www-form-urlencoded"数据

上一小节讲述了如何使用@ModelAttribute支持客户端浏览器的多次表单提交请求。对于不是使用的浏览器的客户端,我们也推荐使用这个注解来处理请求。但当请求是一个HTTP PUT方法的请求时,有一个事情需要注意。浏览器可以通过HTTP的GET方法或POST方法来提交表单数据,非浏览器的客户端还可以通过HTTP的PUT方法来提交表单。这就设计是个挑战,因为在Servlet规范中明确规定,ServletRequest.getParameter*()系列的方法只能支持通过HTTP POST方法的方式提交表单,而不支持HTTP PUT的方式。

为了支持HTTP的PUT类型和PATCH类型的请求,Spring的spring-web模块提供了一个过滤器HttpPutFormContentFilter。你可以在web.xml文件中配置它:

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

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

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

上面的过滤器将会拦截内容类型(content type)为application/x-www-form-urlencoded、HTTP方法为PUT或PATCH类型的请求,然后从请求体中读取表单数据,把它们包装在ServletRequest中。这是为了使表单数据能够通过ServletRequest.getParameter*()系列的方法来拿到。

因为HttpPutFormContentFilter会消费请求体的内容,因此,它不应该用于处理那些依赖于其他application/x-www-form-urlencoded转换器的PUT和PATCH请求,这包括了@RequestBodyMultiValueMap<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类型,则类型转换会自动进行。详见"方法参数与类型转换"一节。

这个注解可以注解到处理器方法上,在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-Encoding请求头和Keep-Alive请求头的值:

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

若注解的目标方法参数不是String类型,则类型转换会自动进行。"方法参数与类型转换"一节。

如果@RequestHeader注解应用在Map<String, String>MultiValueMap<String, String>HttpHeaders类型的参数上,那么所有的请求头属性值都会被填充到map中。

Spring内置支持将一个逗号分隔的字符串(或其他类型转换系统所能识别的类型)转换成一个String类型的列表/集合。举个例子,一个注解了@RequestHeader("Accept")的方法参数可以是一个String类型,但也可以是String[]List<String>类型的。

这个注解可以注解到处理器方法上,在Servlet环境和Portlet环境都能使用。

方法参数与类型转换

从请求参数、路径变量、请求头属性或者cookie中抽取出来的String类型的值,可能需要被转换成其所绑定的目标方法参数或字段的类型(比如,通过@ModelAttribute将请求参数绑定到方法参数上)。如果目标类型不是String,Spring会自动进行类型转换。所有的简单类型诸如int、long、Date都有内置的支持。如果想进一步定制这个转换过程,你可以通过WebDataBinder(详见"定制WebDataBinder的初始化"一节),或者为Formatters配置一个FormattingConversionService(详见8.6节 "Spring字段格式化"一节)来做到。

定制WebDataBinder的初始化

如果想通过Spring的WebDataBinder在属性编辑器中做请求参数的绑定,你可以使用在控制器内使用@InitBinder注解的方法、在注解了@ControllerAdvice的类中使用@InitBinder注解的方法,或者提供一个定制的WebBindingInitializer。更多的细节,请参考使用@ControllerAdvice辅助控制器一节。

数据绑定的定制:使用@InitBinder

使用@InitBinder注解控制器的方法,你可以直接在你的控制器类中定制应用的数据绑定。@InitBinder用来标记一些方法,这些方法会初始化一个WebDataBinder并用以为处理器方法填充命令对象和表单对象的参数。

除了命令/表单对象以及相应的验证结果对象,这样的“绑定器初始化”方法能够接收@RequestMapping所支持的所有参数类型。“绑定器初始化”方法不能有返回值,因此,一般将它们声明为void返回类型。特别地,当WebDataBinderWebRequestjava.util.Locale一起作为方法参数时,你可以在代码中注册上下文相关的编辑器。

下面的代码示例演示了如何使用@InitBinder来配置一个CustomerDateEditor,后者会对所有java.util.Date类型的表单字段进行操作:

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

    // ...
}

或者,你可以使用Spring 4.2提供的addCustomFormatter来指定Formatter的实现,而非通过PropertyEditor实例。这在你拥有一个需要Formatter的setup方法,并且该方法位于一个共享的FormattingConversionService中时非常有用。这样对于控制器级别的绑定规则的定制,代码更容易被复用。

@Controller
public class MyFormController {

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

    // ...
}

配置定制的WebBindingInitializer

为了externalize数据绑定的初始化过程,你可以为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外的另外一种方法。更多细节请参考使用@ControllerAdvice辅助控制器一节。

使用@ControllerAdvice辅助控制器

@ControllerAdvice是一个组件注解,它使得其实现类能够被classpath扫描自动发现。若应用是通过MVC命令空间或MVC Java编程方式配置,那么该特性默认是自动开启的。

注解@ControllerAdvice的类可以拥有@ExceptionHandler@InitBinder@ModelAttribute注解的方法,并且这些方法会被应用至控制器类层次??的所有@RequestMapping方法上。

你也可以通过@ControllerAdvice的属性来指定其只对一个子集的控制器生效:

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

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

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

更多的细节,请查阅@ControllerAdvice的文档

下面两节,还看不太懂,待译。

Jackson Serialization View Support

It can sometimes be useful to filter contextually the object that will be serialized to the HTTP response body. In order to provide such capability, Spring MVC has built-in support for rendering with Jackson's Serialization Views.

To use it with an @ResponseBody controller method or controller methods that return ResponseEntity, simply add the @JsonView annotation with a class argument specifying the view class or interface to be used:

_@RestController_
public class UserController {

    _@RequestMapping(path = "/user", method = RequestMethod.GET)_
    _@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;
    }
}
[Note] Note

Note that despite @JsonView allowing for more than one class to be specified, the use on a controller method is only supported with exactly one class argument. Consider the use of a composite interface if you need to enable multiple views.

For controllers relying on view resolution, simply add the serialization view class to the model:

_@Controller_
public class UserController extends AbstractController {

    _@RequestMapping(path = "/user", method = RequestMethod.GET)_
    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 Support

In order to enable JSONP support for @ResponseBody and ResponseEntity methods, declare an @ControllerAdvice bean that extends AbstractJsonpResponseBodyAdvice as shown below where the constructor argument indicates the JSONP query parameter name(s):

_@ControllerAdvice_
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

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

For controllers relying on view resolution, JSONP is automatically enabled when the request has a query parameter named jsonp or callback. Those names can be customized through jsonpParameterNames property.