Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)

Posted 说好不能打脸

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)相关的知识,希望对你有一定的参考价值。

(接上文《Spring/Boot/Cloud系列知识:SpringMVC 传参详解(上)》)

2.3、通过@PathVariable注解基于URL匹配符接收请求传参

为了便于开发人员实现更好的抽象性也便于简化取值过程,SpringMVC提供了一种基于URL匹配符的参数传递方式,示例代码如下所示:

@GetMapping("/find/param1/param2")
public void find(@PathVariable("param1") String param1 , 
                 @PathVariable("param2") String param2) 
  System.out.println("param1 = " + param1);
  System.out.println("param2 = " + param2);


// 如果URL请求地址为/v1/test11/find/xxxxxx/yyyyy,则param1的值为xxxxxx、param2的值为yyyyy
// 如果URL请求地址为/v1/test11/find/aaaaa/bbbbbb,则param1的值为aaaaa、param2的值为bbbbbb
// 以此类推

从示例代码可以看出这种方式主要基于@PathVariable注解。在书写时,方法中的每一个@PathVariable注解都需要匹配URL地址上的一个匹配符,如果没有匹配就不会有赋值,系统就会报错。如下写法是错误的,原因是param3并没有在URL中对应任何匹配符:

@GetMapping("/find/param1/param2")
public void find(@PathVariable("param1") String param1 , 
                 @PathVariable("param2") String param2 , 
                 @PathVariable("param3") String param3) 
    // ..... 

另外,URL上的每一个匹配符都必须得到匹配,否则也会报错(这个很好理解,如果没有匹配任何一个匹配符,就代表URL地址根本就是错误的)。例如如果URL请求地址为/v1/test11/find/xxxxxx/,则系统会报404错误。

2.4、通过非注解的对象方式接收请求传参

上文介绍的传参方式都没有将参数值对象化,开发人员得到的参数值都是一个独立的字符串值(通过getAttribute方法从Servlet容器获取值的方式除外)。而开发人员一般都需要将传递的参数值对象化以方便后续业务逻辑的处理过程,那么有没有将传递的参数直接对象化的方式呢?

当然有,而且视前端传递参数场景的不同后端将参数对象化的方式也是不一样的。这里我们先介绍如何将HTTP请求中以URL的Query部分进行对象化的参数传递场景,然后介绍以提交表单进行参数传递的对象化的场景。首先我们需要自定义一个类:

// 这是你的一个对象
public class YourBusiness 
  // 一般性的参数转换为对象属性
  private String param1;
  private String param2;
  // 一般性的参数转换为对象属性
  private Integer param3;
  // BigDecimal类型的参数
  private BigDecimal param4;
  // 注意@DateTimeFormat注解
  // 该注解可以将传参值转换为日期对象
  @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
  private Date param5;
  // ..... 后续无关紧要的代码省略

SpringMVC提供的特性可以在没有任何注解支持的情况下,直接匹配对象中的属性。针对以上开发人员定义的类结构,我们可以书写一个controller层的方法,如下:

// 注意,这里没有使用任何注解
@PostMapping("/findYourBusiness")
public void find(YourBusiness yourBusiness) 
  System.out.println("param1 = " + yourBusiness.getParam1());
  System.out.println("param2 = " + yourBusiness.getParam2());
  System.out.println("param3 = " + yourBusiness.getParam3());
  System.out.println("param4 = " + yourBusiness.getParam4());
  System.out.println("param5 = " + yourBusiness.getParam5());

这样SpringMVC就可以直接依据类中属性名和类型,来与传递参数中参数名和参数类型进行匹配,并完成对象创建和属性赋值过程。例如如果HTTP请求以提交表单的方式传递参数,那么赋值情况如下:

POST /v1/test11/findYourBusiness
Content-Type: multipart/form-data; boundary=--------------------------265656227991878818897904
cache-control: no-cache
......
param1=value1param2=value2param3=100param4=3.1415926
.....

这时后端处理过程得到的控制台输出结果为:
param1 = value1
param2 = value2
param3 = 100
param4 = 3.1415926
param5 = null

再例如,HTTP请求通过URL的Query部分进行参数传递,那么赋值情况如下:

POST /v1/test11/findYourBusiness?param1=value1&param2=value2&param3=900&param4=6.33333

这时后端处理过程得到的控制台输出结果为:
param1 = value1
param2 = value2
param3 = 900
param4 = 6.33333
param5 = null

请注意,直接匹配成对象属性的情况下对应的参数值类型必须匹配。例如param3属性在YourBusiness类中被定义成一个Integer属性,如果HTTP请求传值是param3参数名传递的是一个“value3”的字符串,则系统会报错。这个很好理解,因为“value3”的字符串值无法转换成一个整数值。

另外请注意,以上示例中关于类中param5属性的定义,该属性是一个日期类型的属性。显然日期类型的属性不可能通过字符串随意转换得到,因为开发人员可以用多种字符串格式进行同一个时间的描述。这时就需要借助@DateTimeFormat注解,来规范字符串和日期类型转换的格式。这时HTTP请求就可以直接传递日期信息了:

POST /v1/test11/findYourBusiness?param1=value1&param2=value2&param3=900&param4=6.33333&param5=2009-09-09 19:59:50

这时后端处理过程得到的控制台输出结果为:
param1 = value1
param2 = value2
param3 = 900
param4 = 6.33333
param5 = Wed Sep 09 19:59:50 CST 2009

最后注意,为了便于读者进行观察,HTTP请求中的各参数值都没有进行html encoding编码。如果在实际工作中读者也这样做,则很容易产生兼容性问题。所以请各位读者在正式工作中书写这些代码时,一定要进行参数值的html encoding编码工作。

2.5、通过@RequestBody注解接收请求传参

2.4节介绍的对象转换方式适用于使用URL的Query部分进行参数传递场景,也适用于使用HTTP请求进行表单提交的参数值传递场景(multipart/form-data描述格式或者application/x-www-form-urlencoded描述格式都可行)。那么如果开发人员使用Body部分进行参数传递呢?例如使用Body部分进行JSON格式的信息描述时。这时借助SpringMVC提供的@RequestBody注解可以将Body部分的信息直接转换为对象。首先我们来看这种场景下controller层的方法定义:

  // 注意这里使用了RequestBody注解 
  @PostMapping("/findYourBusiness")
  public void find(@RequestBody YourBusiness yourBusiness) 
    System.out.println("param1 = " + yourBusiness.getParam1());
    System.out.println("param2 = " + yourBusiness.getParam2());
    System.out.println("param3 = " + yourBusiness.getParam3());
    System.out.println("param4 = " + yourBusiness.getParam4());
    System.out.println("param5 = " + yourBusiness.getParam5());
  

其中增加的@RequestBody注解,主要表示与对象匹配的参数信息来源于HTTP请求的Body部分。这时我们可以在HTTP请求的Body部分描述一段JSON结构的信息,并使用“application/json”格式类型进行标识。HTTP请求概要如下所示:

POST 8081/v1/test11/findYourBusiness
......
Content-Type: application/json
cache-control: no-cache
......
cookie: SESSION=ZjI2OTJkNTctMTI2NC00YTE4LTljYTgtNzVhNDY4NzEwN2Zl
accept-encoding: gzip, deflate
content-length: 112
 "param1":"value1", "param2":"value2", "param3":900, "param4":6.555, "param5":"2009-09-09 19:59:50" 
......

这样,SpringMVC就会自动将这段JSON信息自动转换为一个yourBusiness对象。当然除了默认接收JSON格式的参数并自动转换为对象外,SpringMVC还可以接受诸如XML格式的参数并转换为所需要的对象。请求格式如下:

POST 8081/v1/test11/findYourBusinessXml
......
Content-Type: application/xml
cache-control: no-cache
......
content-length: 153
<xml><param1>value1</param1> <param2>value2</param2> <param3>900</param3> <param4>7.97734</param4></xml>

XML格式的Body信息可以默认转换为javax.xml.transform.dom.DOMSource类的对象,但如果需要自动转换为开发人员所指定类型的对象,那么指定类的定义需要结合一些注解进行标记,如下所示:

// 开发人员可以直接将携带XML格式的Body部分,自动转换为DOMSource类的对象
// 如下定义controller层的方法
@PostMapping(value = "/findYourBusinessXml")
public void find(@RequestBody DOMSource domSource) 
  // 你对XML的解析逻辑


// 如果你需要转换成特定类的对象,则需要一些额外的标记
// 以下对象类使用了@XmlRootElement注解
@XmlRootElement(name ="xml") 
public class YourBusiness 
  private String param1;
  private String param2;
  private Integer param3;
  private BigDecimal param4;
  // .....


// 这样一来就可以直接转换为指定的类型对象
// 如下定义controller层的方法
@PostMapping(value = "/findYourBusinessXml" , consumes="application/xml")
public void findYourBusinessXml(@RequestBody YourBusiness yourBusiness) 
  System.out.println("param1 = " + yourBusiness.getParam1());
  System.out.println("param2 = " + yourBusiness.getParam2());
  System.out.println("param3 = " + yourBusiness.getParam3());
  System.out.println("param4 = " + yourBusiness.getParam4());


// 根据传递的XML信息格式,这里的代码输出为:
// param1 = value1
// param2 = value2
// param3 = 900
// param4 = 7.97734

SpringMVC之所以可以在controller层将HTTP请求携带的信息转换成指定的对象,主要是基于HttpMessageConverter接口的各种实现。例如之所以能够将JSON信息转换为对象,主要是依靠MappingJackson2HttpMessageConverter转换器的支持;而之所以能够将XML信息转换为对象,主要是依靠Jaxb2RootElementHttpMessageConverter转换器的支持。开发人员还可以根据自己业务场景的要求配置不同的HttpMessageConverter接口实现。本专题后续文章将对HttpMessageConverter的具体工作过程进行详细讲解。

当然,根据以上介绍的传参特点,我们可以直接使用两个对象同时接受在Query部分描述的参数以及在Body部分中使用JSON格式描述的参数,HTTP请求信息类似如下所示:

POST 8081/v1/test11/findYourBusiness?param1=vv1&param2=vv2&param3=1000&param4=31.4567
......
Content-Type: application/json
cache-control: no-cache
......
cookie: SESSION=ZjI2OTJkNTctMTI2NC00YTE4LTljYTgtNzVhNDY4NzEwN2Zl
accept-encoding: gzip, deflate
content-length: 112
 "param1":"value1", "param2":"value2", "param3":900, "param4":6.555, "param5":"2009-09-09 19:59:50" 

以上HTTP请求信息中,我们可以看到用来进行参数传递的区域主要有两个,一个区域使用URL的Query部分进行参数传递;另一个区域使用HTTP请求的Body部分进行参数传递,并使用application/json标识Body部分的信息格式。那么SpringMVC的controller层可以使用两个独立的对象接受参数,如下所示:

@PostMapping("/findYourBusiness")
public void find(@RequestBody YourBusiness yourBusiness , YourBusiness queryBusiness) 
  // 你的处理逻辑......

通过@RequestBody注解标识的对象将用于匹配Body部分内的JSON信息,而没有@RequestBody注解标识的对象(方法中的第二个对象)将匹配Query部分的信息。

2.6、接受和操作HTTP请求中Head部分的参数

关于如何接收和操作HTTP请求中Head部分参数的知识点并不是本文的重点,这里只进行一个简要的说明。

  • 通过常规的HttpServletRequest操作对象获取Head信息:

    由servlet提供的HttpServletRequest操作对象除了可以接受HTTP信息Body部分的传参外,还可以接受Head部分的参数;主要通过getHeaders(String)、getHeader(String)、getHeaderNames()等方法完成,如果开发人员向获取在HTTP协议规范中既定的一些重要Head参数,那么HttpServletRequest操作对象也提供了获取这些参数的快速方式,例如:getContentType()、getContentLength()等方法。现对这些方法(简要)说明如下:

    • getHeaderNames():获取本次请求中所有可用的请求头名称,返回的结果是一组枚举数据,类型为String。
    • getHeader(String):通过本次请求的Head部分某个属性名称,获取参数值。
    • getHeaders(String):和getHeader(String)方法工作意义类似,只不过这里可以获取head参数值的枚举(一个属性多个值)
    • getContentType():可获取本次HTTP请求中,由Head部分Content-Type属性携带的格式说明信息
    • getContentLength():可获取本次HTTP请求中,由Head部分content-length属性携带的信息,该信息说明了本次HTTP请求Body部分内容的长度。
    • 其余从略,可查看其它资料
  • 通过@RequestHeader注解接收请求参数:

    除了上文提到的,可以通过HttpServletRequest操作对象获取Head部分的参数外,开发人员还可以在controller层的方法中通过@RequestHeader注解接收请求参数。该注解的使用方式和前文介绍的@RequestParam注解的使用方式类似,示例如下:

    @GetMapping("/findSomethingFromHead")
    public void findSomethingFromHead(HttpServletRequest request , HttpServletResponse response ,
                                      @RequestHeader("some") String something , 
                                      @RequestHeader("others") String[] others) 
      // 你的处理逻辑
    
    

    以上示例代码可以正确接受如下请求中head部分的信息:

    GET 8081/v1/XXXXX/findSomethingFromHead
    ......
    some: somevalue
    others: otherValue1,otherValue2
    cache-control: no-cache
    ......
    Host: localhost:8081
    cookie: SESSION=NWFlNDQ0ZDYtNTQ0Yy00ZWY5LWJhOGEtY2M1YmU2MWY3NTkz
    ......
    

    这时findSomethingFromHead方法得到的something参数的值将为“somevalue”;others参数的值会是一个数组[“otherValue1”,“otherValue2”]。但是以上示例代码如果接收如下请求中head部分,则会产生歧义:

    GET 8081/v1/test11/findSomethingFromHead
    ......
    some: somevalue1,somevalue2
    others: otherValue1,otherValue2
    ......
    Host: localhost:8081
    cookie: SESSION=NWFlNDQ0ZDYtNTQ0Yy00ZWY5LWJhOGEtY2M1YmU2MWY3NTkz
    

    很明显以上请求中,前端请求者希望通过Head部分的some参数传递一个包含两个元素的数组(数组值为[“somevalue1”],[“somevalue2”])。但由于controller层方法findSomethingFromHead使用了一个字符串而非字符串数组接收参数,所以实际上接收到的some参数值将是一个字符串“somevalue1,somevalue2”。

  • 基于Head部分的HTTP响应信息进行操作:

    开发人员还可以通过HttpServletResponse操作对象对HTTP响应信息的Head部分进行操作(设定),如果开发人员需要操作的Head部分的属性是那些由HTTP协议规定的特定属性,那么一般来说HttpServletResponse操作对象都提供了对应的属性设定方式,示例如下:

    ......
    // 设定响应信息Body部分的内容格式为application/json
    response.setContentType("application/json;charset=UTF-8");
    // 设定响应信息Body部分的内容长度为10
    response.setContentLength(10);
    // 设定响应状态为502
    response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
    ......
    

    但绝大部分情况下开发人员无需这样设定返回信息,这是因为SpringMVC会根据controller层方法的返回值设定,结合HttpMessageConverter接口的具体实现,自动完成response中Head部分各关键属性的设定。当然也有一些典型场景需要开发人员自行设定一些HTTP响应信息中的关键Head属性,例如在进行文件下载时。开发人员如果需要向HTTP响应信息的Head部分写入一些自定义参数,那么这个诉求可以通过HttpServletResponse操作对象提供setHeader(String, String)方法完成,示例代码如下所示:

    ......
    // 在响应信息的head部分,设定一个自定义参数
    response.setHeader("somekey", "somevalue");
    ......
    

    请注意有的时候开发人员在controller层设定的一些head部分属性,会在最终返回给HTTP请求者时失效(变成了其它值或者无故消失)。引起这个效果的原因主要有两种,其一就是在HTTP响应信息在某个地方被重置了,例如在某个filter中被修改或清理;另一主要原因是HTTP响应信息在传回给请求者前,被某个代理服务拦截并进行了修改,例如某个nginx中。

  • 基于Head部分对客户端cookies信息进行修改:

    这里特别说明一下在HTTP响应信息中,对客户端存储的cookie信息进行修改操作的注意事项。要完成这个目标,使用HttpServletResponse操作对象提供setHeader(String, String)方法是无法达到的。例如以下操作无法完成对cookie信息的设置:

    // 以下方式无法设定cookie
    response.setHeader("cookie", "somevalue");
    

    开发人员可以使用HttpServletResponse操作对象提供addCookie(Cookie)方法完成对客户端cookie信息的设定(本文不打算展开讲解cookie信息的结构),实例代码如下所示:

    // 设置客户端的cookie信息
    response.addCookie(new Cookie("cookieKey", "cookieValue"));
    
    // 设置成功后,HTTP响应信息将携带Set-Cookie属性;
    ......
    Set-Cookie →cookieKey=cookieValue
    ......
    

以上是关于Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)的主要内容,如果未能解决你的问题,请参考以下文章

Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)

Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)

Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)

Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程

Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程

Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程