JAX-RS:@PathVariable @RequestParam @ModelAttribute等参数绑定注解详解

  • Post author:
  • Post category:其他


引言:

接上一篇文章,对@RequestMapping进行地址映射讲解之后,该篇主要讲解request 数据到handler method 参数数据的绑定所用到的注解和什么情形下使用;



简介:

handler method 参数绑定常用的注解,我们根据他们处理的Request的不同内容部分分为四类:(主要讲解常用类型)

A、处理requet uri 部分(这里指uri template中variable,不含queryString部分)的注解:   @PathVariable;

B、处理request header部分的注解:   @RequestHeader, @CookieValue;

C、处理request body部分的注解:@RequestParam,  @RequestBody;

D、处理attribute类型是注解: @SessionAttributes, @ModelAttribute;



1、 @PathVariable

当使用@RequestMapping URI template 样式映射时, 即 someUrl/{paramId}, 这时的paramId可通过 @Pathvariable注解绑定它传过来的值到方法的参数上。

示例代码:



  1. @Controller





  2. @RequestMapping


    (


    “/owners/{ownerId}”


    )



  3. public




    class


    RelativePathUriTemplateController {




  4. @RequestMapping


    (


    “/pets/{petId}”


    )



  5. public




    void


    findPet(


    @PathVariable


    String ownerId,


    @PathVariable


    String petId, Model model) {



  6. // implementation omitted




  7. }

  8. }


上面代码把URI template 中变量 ownerId的值和petId的值,绑定到方法的参数上。若方法参数名称和需要绑定的uri template中变量名称不一致,需要在@PathVariable(“name”)指定uri template中的名称。






2、 @RequestHeader、@CookieValue

@RequestHeader 注解,可以把Request请求header部分的值绑定到方法的参数上。

示例代码:

这是一个Request 的header部分:



  1. Host                    localhost:8080


  2. Accept                  text/html,application/xhtml+xml,application/xml;q=0.9

  3. Accept-Language         fr,en-gb;q=0.7,en;q=0.3

  4. Accept-Encoding         gzip,deflate

  5. Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7

  6. Keep-Alive              300




  1. @RequestMapping


    (


    “/displayHeaderInfo.do”


    )



  2. public




    void


    displayHeaderInfo(


    @RequestHeader


    (


    “Accept-Encoding”


    ) String encoding,



  3. @RequestHeader


    (


    “Keep-Alive”


    )


    long


    keepAlive)  {




  4. //…





  5. }


上面的代码,把request header部分的 Accept-Encoding的值,绑定到参数encoding上了, Keep-Alive header的值绑定到参数keepAlive上。


@CookieValue 可以把Request header中关于cookie的值绑定到方法的参数上。

例如有如下Cookie值:



  1. JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84


参数绑定的代码:



  1. @RequestMapping


    (


    “/displayHeaderInfo.do”


    )



  2. public




    void


    displayHeaderInfo(


    @CookieValue


    (


    “JSESSIONID”


    ) String cookie)  {




  3. //…





  4. }


即把JSESSIONID的值绑定到参数cookie上。



3、@RequestParam, @RequestBody

@RequestParam

A) 常

用来处理简单类型的绑定

,通过Request.getParameter() 获取的String可直接转换为简单类型的情况( String–> 简单类型的转换操作由ConversionService配置的转换器来完成);因为使用request.getParameter()方式获取参数,所以可以处理get 方式中queryString的值,也可以处理post方式中 body data的值;

B)用来处理Content-Type: 为

application/x-www-form-urlencoded

编码的内容,提交方式GET、POST;

C) 该注解有两个属性: value、required; value用来指定要传入值的id名称,required用来指示参数是否必须绑定;

示例代码:



  1. @Controller





  2. @RequestMapping


    (


    “/pets”


    )



  3. @SessionAttributes


    (


    “pet”


    )



  4. public




    class


    EditPetForm {




  5. // …






  6. @RequestMapping


    (method = RequestMethod.GET)



  7. public


    String setupForm(


    @RequestParam


    (


    “petId”


    )


    int


    petId, ModelMap model) {


  8. Pet pet =

    this


    .clinic.loadPet(petId);


  9. model.addAttribute(

    “pet”


    , pet);



  10. return




    “petForm”


    ;


  11. }



  12. // …






@RequestBody

该注解常用来处理Content-Type: 不是

application/x-www-form-urlencoded

编码的内容,例如application/json, application/xml等;

它是通过使用HandlerAdapter 配置的

HttpMessageConverters

来解析post data body,然后绑定到相应的bean上的。

因为配置有FormHttpMessageConverter,所以也可以用来处理

application/x-www-form-urlencoded

的内容,处理完的结果放在一个MultiValueMap<String, String>里,这种情况在某些特殊需求下使用,详情查看FormHttpMessageConverter api;

示例代码:



  1. @RequestMapping


    (value =


    “/something”


    , method = RequestMethod.PUT)



  2. public




    void


    handle(


    @RequestBody


    String body, Writer writer)


    throws


    IOException {


  3. writer.write(body);

  4. }




4、@SessionAttributes, @ModelAttribute

@SessionAttributes:

该注解用来绑定HttpSession中的attribute对象的值,便于在方法中的参数里使用。

该注解有value、types两个属性,可以通过名字和类型指定要使用的attribute 对象;

示例代码:



  1. @Controller





  2. @RequestMapping


    (


    “/editPet.do”


    )



  3. @SessionAttributes


    (


    “pet”


    )



  4. public




    class


    EditPetForm {



  5. // …




  6. }




@ModelAttribute

该注解有两个用法,一个是用于方法上,一个是用于参数上;

用于方法上时:  通常用来在处理@RequestMapping之前,为请求绑定需要从后台查询的model;

用于参数上时: 用来通过名称对应,把相应名称的值绑定到注解的参数bean上;要绑定的值来源于:

A) @SessionAttributes 启用的attribute 对象上;

B) @ModelAttribute 用于方法上时指定的model对象;

C) 上述两种情况都没有时,new一个需要绑定的bean对象,然后把request中按名称对应的方式把值绑定到bean中。

用到方法上@ModelAttribute的示例代码:



  1. // Add one attribute





  2. // The return value of the method is added to the model under the name “account”





  3. // You can customize the name via @ModelAttribute(“myAccount”)






  4. @ModelAttribute





  5. public


    Account addAccount(


    @RequestParam


    String number) {



  6. return


    accountManager.findAccount(number);


  7. }




这种方式实际的效果就是在调用@RequestMapping的方法之前,为request对象的model里put(“account”, Account);

用在参数上的@ModelAttribute示例代码:



  1. @RequestMapping


    (value=


    “/owners/{ownerId}/pets/{petId}/edit”


    , method = RequestMethod.POST)



  2. public


    String processSubmit(


    @ModelAttribute


    Pet pet) {



  3. }


首先查询 @SessionAttributes有无绑定的Pet对象,若没有则查询@ModelAttribute方法层面上是否绑定了Pet对象,若没有则将URI template中的值按对应的名称绑定到Pet对象的各属性上。






补充讲解:



问题: 在不给定注解的情况下,参数是怎样绑定的?

通过分析AnnotationMethodHandlerAdapter和RequestMappingHandlerAdapter的源代码发现,方法的参数在不给定参数的情况下:

若要绑定的对象时简单类型:  调用@RequestParam来处理的。

若要绑定的对象时复杂类型:  调用@ModelAttribute来处理的。

这里的简单类型指java的原始类型(boolean, int 等)、原始类型对象(Boolean, Int等)、String、Date等ConversionService里可以直接String转换成目标对象的类型;

下面贴出AnnotationMethodHandlerAdapter中绑定参数的部分源代码:



  1. private


    Object[] resolveHandlerArguments(Method handlerMethod, Object handler,


  2. NativeWebRequest webRequest, ExtendedModelMap implicitModel)

    throws


    Exception {



  3. Class[] paramTypes = handlerMethod.getParameterTypes();

  4. Object[] args =

    new


    Object[paramTypes.length];




  5. for


    (


    int


    i =


    0


    ; i < args.length; i++) {


  6. MethodParameter methodParam =

    new


    MethodParameter(handlerMethod, i);


  7. methodParam.initParameterNameDiscovery(

    this


    .parameterNameDiscoverer);


  8. GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());

  9. String paramName =

    null


    ;


  10. String headerName =

    null


    ;



  11. boolean


    requestBodyFound =


    false


    ;


  12. String cookieName =

    null


    ;


  13. String pathVarName =

    null


    ;


  14. String attrName =

    null


    ;



  15. boolean


    required =


    false


    ;


  16. String defaultValue =

    null


    ;



  17. boolean


    validate =


    false


    ;


  18. Object[] validationHints =

    null


    ;



  19. int


    annotationsFound =


    0


    ;


  20. Annotation[] paramAnns = methodParam.getParameterAnnotations();



  21. for


    (Annotation paramAnn : paramAnns) {



  22. if


    (RequestParam.


    class


    .isInstance(paramAnn)) {


  23. RequestParam requestParam = (RequestParam) paramAnn;

  24. paramName = requestParam.value();

  25. required = requestParam.required();

  26. defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());

  27. annotationsFound++;

  28. }


  29. else




    if


    (RequestHeader.


    class


    .isInstance(paramAnn)) {


  30. RequestHeader requestHeader = (RequestHeader) paramAnn;

  31. headerName = requestHeader.value();

  32. required = requestHeader.required();

  33. defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());

  34. annotationsFound++;

  35. }


  36. else




    if


    (RequestBody.


    class


    .isInstance(paramAnn)) {


  37. requestBodyFound =

    true


    ;


  38. annotationsFound++;

  39. }


  40. else




    if


    (CookieValue.


    class


    .isInstance(paramAnn)) {


  41. CookieValue cookieValue = (CookieValue) paramAnn;

  42. cookieName = cookieValue.value();

  43. required = cookieValue.required();

  44. defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());

  45. annotationsFound++;

  46. }


  47. else




    if


    (PathVariable.


    class


    .isInstance(paramAnn)) {


  48. PathVariable pathVar = (PathVariable) paramAnn;

  49. pathVarName = pathVar.value();

  50. annotationsFound++;

  51. }


  52. else




    if


    (ModelAttribute.


    class


    .isInstance(paramAnn)) {


  53. ModelAttribute attr = (ModelAttribute) paramAnn;

  54. attrName = attr.value();

  55. annotationsFound++;

  56. }


  57. else




    if


    (Value.


    class


    .isInstance(paramAnn)) {


  58. defaultValue = ((Value) paramAnn).value();

  59. }


  60. else




    if


    (paramAnn.annotationType().getSimpleName().startsWith(


    “Valid”


    )) {


  61. validate =

    true


    ;


  62. Object value = AnnotationUtils.getValue(paramAnn);

  63. validationHints = (value

    instanceof


    Object[] ? (Object[]) value :


    new


    Object[] {value});


  64. }

  65. }



  66. if


    (annotationsFound >


    1


    ) {



  67. throw




    new


    IllegalStateException(


    “Handler parameter annotations are exclusive choices – ”


    +



  68. “do not specify more than one such annotation on the same parameter: ”


    + handlerMethod);


  69. }



  70. if


    (annotationsFound ==


    0


    ) {



    // 若没有发现注解




  71. Object argValue = resolveCommonArgument(methodParam, webRequest);

    //判断WebRquest是否可赋值给参数





  72. if


    (argValue != WebArgumentResolver.UNRESOLVED) {


  73. args[i] = argValue;

  74. }


  75. else




    if


    (defaultValue !=


    null


    ) {


  76. args[i] = resolveDefaultValue(defaultValue);

  77. }


  78. else


    {


  79. Class<?> paramType = methodParam.getParameterType();


  80. if


    (Model.


    class


    .isAssignableFrom(paramType) || Map.


    class


    .isAssignableFrom(paramType)) {



  81. if


    (!paramType.isAssignableFrom(implicitModel.getClass())) {



  82. throw




    new


    IllegalStateException(


    “Argument [”


    + paramType.getSimpleName() +


    “] is of type ”


    +



  83. “Model or Map but is not assignable from the actual model. You may need to switch ”


    +



  84. “newer MVC infrastructure classes to use this argument.”


    );


  85. }

  86. args[i] = implicitModel;

  87. }


  88. else




    if


    (SessionStatus.


    class


    .isAssignableFrom(paramType)) {


  89. args[i] =

    this


    .sessionStatus;


  90. }


  91. else




    if


    (HttpEntity.


    class


    .isAssignableFrom(paramType)) {


  92. args[i] = resolveHttpEntityRequest(methodParam, webRequest);

  93. }


  94. else




    if


    (Errors.


    class


    .isAssignableFrom(paramType)) {



  95. throw




    new


    IllegalStateException(


    “Errors/BindingResult argument declared ”


    +



  96. “without preceding model attribute. Check your handler method signature!”


    );


  97. }


  98. else




    if


    (BeanUtils.isSimpleProperty(paramType)) {



    // 判断是否参数类型是否是简单类型,若是在使用@RequestParam方式来处理,否则使用@ModelAttribute方式处理




  99. paramName =

    “”


    ;


  100. }


  101. else


    {


  102. attrName =

    “”


    ;


  103. }

  104. }

  105. }



  106. if


    (paramName !=


    null


    ) {


  107. args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);

  108. }


  109. else




    if


    (headerName !=


    null


    ) {


  110. args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);

  111. }


  112. else




    if


    (requestBodyFound) {


  113. args[i] = resolveRequestBody(methodParam, webRequest, handler);

  114. }


  115. else




    if


    (cookieName !=


    null


    ) {


  116. args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);

  117. }


  118. else




    if


    (pathVarName !=


    null


    ) {


  119. args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);

  120. }


  121. else




    if


    (attrName !=


    null


    ) {


  122. WebDataBinder binder =

  123. resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);


  124. boolean


    assignBindingResult = (args.length > i +


    1


    && Errors.


    class


    .isAssignableFrom(paramTypes[i +


    1


    ]));



  125. if


    (binder.getTarget() !=


    null


    ) {


  126. doBind(binder, webRequest, validate, validationHints, !assignBindingResult);

  127. }

  128. args[i] = binder.getTarget();


  129. if


    (assignBindingResult) {


  130. args[i +

    1


    ] = binder.getBindingResult();


  131. i++;

  132. }

  133. implicitModel.putAll(binder.getBindingResult().getModel());

  134. }

  135. }



  136. return


    args;


  137. }

RequestMappingHandlerAdapter中使用的参数绑定,代码稍微有些不同,有兴趣的同仁可以分析下,最后处理的结果都是一样的。

示例:



  1. @RequestMapping


    ({



    “/”


    ,


    “/home”


    })



  2. public


    String showHomePage(String key){



  3. logger.debug(

    “key=”


    +key);




  4. return




    “home”


    ;


  5. }


这种情况下,就调用默认的@RequestParam来处理。



  1. @RequestMapping


    (method = RequestMethod.POST)



  2. public


    String doRegister(User user){



  3. if


    (logger.isDebugEnabled()){


  4. logger.debug(

    “process url[/user], method[post] in ”


    +getClass());


  5. logger.debug(user);

  6. }



  7. return




    “user”


    ;


  8. }




这种情况下,就调用@ModelAttribute来处理。



参考文档:

1、 Spring Web Doc:

spring-3.1.0/docs/spring-framework-reference/html/mvc.html