0


Spring MVC 教程-@RequestHeader详解

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

当我们在接口中想获取请求头的值的时候,怎么写代码更简单呢?

SpringMVC中提供了@RequestHeader注解,用来获取请求头中的信息。

本文将介绍@RequestHeader的4种用法及原理。

1、预备知识

​​​​​​

  1. 接口测试利器 HTTP Client
  2. 参数解析器HandlerMethodArgumentResolver解密

2、@RequestHeader介绍

@RequestBody注解源码如下,可以用来标注在接口的参数上,用来获取HTTP请求header中的值,下面通过案例列出常见的4种用法。

  1. @Target(ElementType.PARAMETER)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface RequestHeader {
  5. /**
  6. * header的名称
  7. */
  8. @AliasFor("name")
  9. String value() default "";
  10. /**
  11. * 同value,指定header的名称
  12. */
  13. @AliasFor("value")
  14. String name() default "";
  15. /**
  16. * 是否是必须的,默认为ture,若指定的name在请求中不存在且未指定默认值defaultValue,则会出现异常
  17. */
  18. boolean required() default true;
  19. /**
  20. * 默认值
  21. */
  22. String defaultValue() default ValueConstants.DEFAULT_NONE;
  23. }

3、用法1:指定name,获取指定的header

3.1、接口代码

参数使用@RquestHeader标注,并指定name属性的值,可以获取请求头中name对应头的值。

test1方法有3个参数:

  • name:用来获取头中name的值,required为false,表示不是必须的,并设置了defaultValue=ready,如果name不传递,则默认值为ready
  • age:相当于Integer.parseInt(request.getHeader("age"))
  • header1:类型为List,相当于Arrays.asList(request.getHeaders("header1"))
  1. @RequestMapping("/requestheader/test1")
  2. public Map<String, Object> test1(@RequestHeader(value = "name", required = false, defaultValue = "ready") String name,
  3. @RequestHeader(value = "age") int age,
  4. @RequestHeader(value = "header1") List<String> header1) {
  5. Map<String, Object> result = new LinkedHashMap<>();
  6. result.put("name", name);
  7. result.put("age", age);
  8. result.put("header1", header1);
  9. return result;
  10. }

下面来测试3种场景,验证效果

3.2、用例1:所有header都传递

  1. ### 所有头都传递
  2. GET http://localhost:8080/chat18/requestheader/test1
  3. name: java
  4. age: 25
  5. header1: java
  6. Header1: spring

点击红框旁边的绿色箭头运行,输出

  1. {
  2. "name": "java",
  3. "age": 25,
  4. "header1": [
  5. "java",
  6. "spring"
  7. ]
  8. }

3.3、用例2:name头不传递,会使用默认值

我们将头中的name干掉,相当于方法的第一个参数不传递,看看什么效果

  1. ### 头name不传递,会使用默认值
  2. GET http://localhost:8080/chat18/requestheader/test1
  3. age: 25
  4. header1: java
  5. header1: spring

运行输出如下,name取了默认值ready

  1. {
  2. "name": "ready",
  3. "age": 25,
  4. "header1": [
  5. "java",
  6. "spring"
  7. ]
  8. }

3.4、用例3:required为true的,没有默认值的,不传递,会报错

大家注意第二个参数

  1. @RequestHeader(value = "age") int age

  1. required

属性取的默认值true,表示请求头中必须有age,否则会报错,我们直接在浏览器中访问接口,报错了,400错误,详细信息如下,大家熟悉下,以后遇到的时候,可以快速定位


4、用法2:不指定name属性,参数为Map类型,用来接收所有头

4.1、用法

当我们想获取所有头的时候,参数上使用@RequestHeader标注,参数类型为Map<String,String>类型,用来接收所有请求头

4.2、代码

  1. /**
  2. * {@link RequestHeader}不指定name,用于接收所有header的值,
  3. * 参数类型为Map<String,String>:key为头的名称,value为header的值
  4. *
  5. * @param headerMap
  6. * @return
  7. */
  8. @RequestMapping("/requestheader/test2")
  9. public Map<String, String> test2(@RequestHeader Map<String, String> headerMap) {
  10. return headerMap;
  11. }

4.3、调用接口

  1. ### 用法2:未指定name属性,参数为Map<String,Strintg>类型,用来接收所有头
  2. GET http://localhost:8080/chat18/requestheader/test2
  3. name: java
  4. age: 25
  5. header1: java
  6. header1: spring

4.4、运行输出

上面调用接口的代码中传递了4个头,输出中却多了几个,多出的几个是http client自动为我们加上的几个,大家不必在意。

这里输出中有个问题,header1我们传递了2个,输出中只输出了第一个的值,第二个丢失了,那是因为参数是Map<String,String>结构导致的,用法3和用法4会解决这个问题。

  1. {
  2. "name": "java",
  3. "age": "25",
  4. "header1": "java",
  5. "host": "localhost:8080",
  6. "connection": "Keep-Alive",
  7. "user-agent": "Apache-HttpClient/4.5.12 (Java/11.0.10)",
  8. "accept-encoding": "gzip,deflate"
  9. }

5、用法3:不指定name属性,参数为MultiValueMap类型,用来接收所有头

5.1、用法

用法2中,使用Map接收的时候,有重复的头的时候,同名的只会取一个,后面的会丢失,如何解决这个问题呢?

将参数类型改为MultiValueMap即可解决,MultiValueMap相当于Map<String,List>,值是一个集合。

  1. /**
  2. * 参数为如果为 org.springframework.core.io.Resource 类型,
  3. * 则只能为Resource的[ByteArrayResource,InputStreamResource]这2种子类型:
  4. *
  5. * @param body
  6. * @return
  7. * @throws IOException
  8. */
  9. @RequestMapping("/requestbody/test3")
  10. public String test3(@RequestBody InputStreamResource body) throws IOException {
  11. String content = IOUtils.toString(body.getInputStream(), "UTF-8");
  12. System.out.println("content:" + content);
  13. return "ok";
  14. }

5.2、调用接口

  1. ### 用法3:不指定name属性,参数为MultiValueMap类型,用来接收所有头
  2. GET http://localhost:8080/chat18/requestheader/test3
  3. name: java
  4. age: 25
  5. header1: java
  6. header1: spring

5.3、运行输出

  1. {
  2. "name": [
  3. "java"
  4. ],
  5. "age": [
  6. "25"
  7. ],
  8. "header1": [
  9. "java",
  10. "spring"
  11. ],
  12. "host": [
  13. "localhost:8080"
  14. ],
  15. "connection": [
  16. "Keep-Alive"
  17. ],
  18. "user-agent": [
  19. "Apache-HttpClient/4.5.12 (Java/11.0.10)"
  20. ],
  21. "accept-encoding": [
  22. "gzip,deflate"
  23. ]
  24. }

6、用法4:不指定name属性,参数为HttpHeaders类型,用来接收所有头

6.1、HttpHeaders

上面的几种用法不是特别方便,比如获取到所有的header之后,此时我想获取某个头,那么我需要调用map.get(头名)来获取,此时我们可以将参数类型改为HttpHeaders,用这种类型,那么就方便了,先来看看这个类是什么玩意。

SpringMVC为咱们提供了一个更牛逼的类:HttpHeaders,用来表示http请求头,这个类中有很多好东西

  • 实现了MultiValueMap<String,String>接口,而MultiValueMap实现了Map接口,相当于Map<String,List>
  • 定义了大量header名称常量,基本所有常见的header名称,这个里面都有
  • 定义了大量获取各种header的方法,比如List<MediaType> getAccept()等等,非常好用
  • 还定义了大量set方法,用来设置头的信息

6.1、用法:参数为HttpHeaders类型,接收所有请求头

  1. /**
  2. * {@link RequestHeader}不指定name,用于接收所有header的值,
  3. * 参数类型为HttpHeaders,实现了MultiValueMap接口,HttpHeaders中提供了大量的获取头信息的各种方法,使用特别方便
  4. *
  5. * @param httpHeaders
  6. * @return
  7. */
  8. @RequestMapping("/requestheader/test4")
  9. public Map<String,List<String>> test4(@RequestHeader HttpHeaders httpHeaders) {
  10. Map<String,List<String>> result = new LinkedHashMap<>(httpHeaders);
  11. return result;
  12. }

6.2、调用接口

  1. ### 用法4:不指定name属性,参数为HttpHeaders类型,用来接收所有头
  2. GET http://localhost:8080/chat18/requestheader/test4
  3. name: java
  4. age: 25
  5. header1: java
  6. header1: spring

6.3、运行输出

  1. {
  2. "name": [
  3. "java"
  4. ],
  5. "age": [
  6. "25"
  7. ],
  8. "header1": [
  9. "java",
  10. "spring"
  11. ],
  12. "host": [
  13. "localhost:8080"
  14. ],
  15. "connection": [
  16. "Keep-Alive"
  17. ],
  18. "user-agent": [
  19. "Apache-HttpClient/4.5.12 (Java/11.0.10)"
  20. ],
  21. "accept-encoding": [
  22. "gzip,deflate"
  23. ]
  24. }

7、@RequestHeader原理

方法参数上标注了@RequestHeader注解的,参数的值的获取主要有2个类来处理

  • 指定了name的会被org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver处理
  • 未指定name的,会被org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver处理

后面这个类的源码贴出来让大家瞅瞅,特别的简单,主要就2个方法,第一个方法用来判断是否支持解析当前参数,判断规则就是看参数上是否有

  1. RequestHeader

注解及参数的类型是否为Map类型。

resolveArgument方法用来解析参数,代码就不解释了,简单明了,一看就懂,另外一个类

  1. RequestHeaderMethodArgumentResolver

代码就不贴了,大家也自己去看看。

  1. public class RequestHeaderMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
  2. @Override
  3. public boolean supportsParameter(MethodParameter parameter) {
  4. return (parameter.hasParameterAnnotation(RequestHeader.class) &&
  5. Map.class.isAssignableFrom(parameter.getParameterType()));
  6. }
  7. @Override
  8. public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
  9. NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
  10. Class<?> paramType = parameter.getParameterType();
  11. if (MultiValueMap.class.isAssignableFrom(paramType)) {
  12. MultiValueMap<String, String> result;
  13. if (HttpHeaders.class.isAssignableFrom(paramType)) {
  14. result = new HttpHeaders();
  15. }
  16. else {
  17. result = new LinkedMultiValueMap<>();
  18. }
  19. for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
  20. String headerName = iterator.next();
  21. String[] headerValues = webRequest.getHeaderValues(headerName);
  22. if (headerValues != null) {
  23. for (String headerValue : headerValues) {
  24. result.add(headerName, headerValue);
  25. }
  26. }
  27. }
  28. return result;
  29. }
  30. else {
  31. Map<String, String> result = new LinkedHashMap<>();
  32. for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
  33. String headerName = iterator.next();
  34. String headerValue = webRequest.getHeader(headerName);
  35. if (headerValue != null) {
  36. result.put(headerName, headerValue);
  37. }
  38. }
  39. return result;
  40. }
  41. }
  42. }

关于

  1. @RequestHeader

的用法,大家其实可以去看看其源码中注释,注释中已经详细说了其用法,如下图,遇到其他的类的时候,也可以先看注释。


8、代码位置及说明

8.1、git地址

  1. https://gitee.com/javacode2018/springmvc-series

8.2、本文案例代码结构说明

标签: springMVC

本文转载自: https://blog.csdn.net/smart_an/article/details/140944699
版权归原作者 smart哥 所有, 如有侵权,请联系我们删除。

“Spring MVC 教程-@RequestHeader详解”的评论:

还没有评论