本文将介绍SpringMVC中内容协商,可能有朋友听过,没听过的估计觉得很陌生,不管怎么样,先告诉你一点,这篇是非常重要的一个知识点,一定不要错误,坚持看完,一定会有大量收获,
目录1、预备知识
2、先来做一个测试
2.1、测试场景1
2.2、结论1:返回值受服务器端的影响
2.3、测试场景2
2.4、结论2:返回值受客户端Accept头的影响
2.5、小结
3、为什么会这样?
3.1、这是由内容协商决定的
3.2、带来了2个问题
4、客户端如何告诉服务器端自己能够接受的内容类型?
4.1、常见2种方式
4.2、又带来了什么2个问题
5、什么是媒体类型(MimeType或MediaType)?
5.1、解释
5.2、MimeType格式
5.3、常见的MimeType举例
5.4、MimeType在http请求中的应用
5.5、特殊参数q:指定MimeType优先级
6、http请求头Accept是什么样的?
6.1、Accept作用
6.2、Accept格式
7、Spring中的类MediaType工具类
7.1、常见常量
7.2、最常用的方法
7.3、排序规则
8、服务端可响应的媒体类型
8.1、服务端有3种方式可以指定相应的媒体类型
8.2、方式1:@RequestMapping注解的produces属性
8.3、方式2:("Content-Type","媒体类型");
8.4、方式3:由SpringMVC内部机制自动确定能够响应的媒体类型列表
8.5、方式3源码解读
9、总结
10、案例代码git地址
10.1、git地址
10.2、本文案例代码结构说明
11、SpringMVC系列目录
12、更多系列文章
13、最新资料
1、预备知识接口测试利器HTTPClient
2、先来做一个测试思考下,下面这个问题springmvc接口会输出什么?
@RequestMapping(value="/cn/test1")@ResponseBodypublicListStringtest1(){ListStringresult=("刘德华","张学友","郭富城","黎明");returnresult;}代码很简单,方法上标识了ResponseBody注解,会输出如下json格式的数据。
浏览器中访问下这个接口,效果如下
2.1、测试场景1大家在项目maven配置中加入下面内容,然后再试试会输出什么
!--添加jackson配置--/groupIdartifactIdjackson-core//version//groupIdartifactIdjackson-databind//version//groupIdartifactIdjackson-dataformat-xml//version/depency
浏览器中再次访问,效果如下
2.2、结论1:返回值受服务器端的影响从这个上面我们可以看出,我们只是在服务器端调整了一下maven的配置,此时接口的返回结果却发生了变化,由json格式变成xml格式了。
这里得到第1个结论:接口的返回值格式受服务器端的影响。
2.3、测试场景2我们在idea中使用HttpClient来访问一下上面这个接口,效果如下,返回的结果依然是xml格式的数据。
GEThttp://localhost:8080/chat22/cn/test1Accept:application/json
这两次请求唯一不同的地方就是第二次多了Accept:application/json这部分代码,然后结果就变成json了,说明响应的结果收到了这个头的影响。
咱们再回过头去看一下浏览器的那次请求,它的请求头中的Accept是什么样的,如下图,内容我给提取出来了,如下代码,看起来好像很陌生啊,这玩意是啥?稍后我们会详细说明。
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.92.4、结论2:返回值受客户端Accept头的影响
场景2中,我们只是调整了一下http请求头中Accept,响应结果不一样了。
这里得到第2个结论:返回值受客户端Accept头的影响。
2.5、小结从上面可以看出,响应结果的格式受服务器端和客户端的影响,由二者共同决定的。
3、为什么会这样?3.1、这是由类容协商决定的服务器端和请求端协商决定最终返回什么格式的内容。
客户端发送请求的时候可以告知服务器端,自己希望对方返回的数据格式列表,而服务器端的接口也有自己能够支持的响应格式列表,最终返回结果会根据这2个类型列表,找到一种两边都能够支持的类型返回,如果找不到合适,则会报错。
比如:服务器端可以响应json和xml格式的数据,而浏览器发送请求的时候告诉服务器说:我能够接收html和json格式的数据,那么最终会返回二者都能够支持的类型:json格式的数据。
再比如:服务器端可以响应json和html格式的数据,而客户端发送http请求的时候,说自己希望接受xml格式的数据,此时服务器端没有能力返回xml格式的数据,最终会报错。
如果还是不懂,更通俗的解释:
小明找小王介绍女朋友,小明说能满足这些的就可以【有钱、漂亮、幽默】,小王收集了一下身边的资源,发现有钱的、漂亮的没有,幽默的倒是有,然后就将幽默的介绍给小明了,若小王这边没有满足这些条件的,那么就没法给小明介绍女友了。
小明找小王介绍女朋友,小明说能满足这些的就可以【有钱、漂亮、幽默】,如果都能够满足,那么优先选择有钱的,然后漂亮的,然后幽默的,小王收集了一下身边的资源,发现有钱的、漂亮的、幽默的都有,然后根据小明的需求,将优先级最高的有钱的介绍给他了,小明乐呵呵,哈哈。
3.2、带来了2个问题客户端如何告诉服务器端自己能够接受的内容类型?
服务器端开发的接口如何指定能够响应的类型?
4、客户端如何告诉服务器端自己能够接受的内容类型?4.1、常见2种方式方式1:http请求头中使用Accept来指定客户端能够接收的类型(又叫:媒体类型)
方式2:自定义的方式比如请求地址的后缀,、,通过后缀来指定类容类型比如请求中可以添加一个参数,如format来指定能够接收的内容类型
这2种方式SpringMVC中都有实现,SpringMVC中默认开启了第1种方式,而SpringBoot中默认开启了这2种方式的支持,本文主要讲解第1种方式,后续在SpringBoot系列中,将详细介绍第2种方式。
4.2、又带来了2个问题问题1:什么是媒体类型
问题2:http请求头Accept是什么样的?
5、什么是媒体类型(MimeType或MediaType)?5.1、解释简单点理解,媒体类型就是用来表示内容的格式,比如可以用来表示http请求体和响应体内容的格式。
英文称呼:MineType或者MediaType
5.2、MimeType格式格式:type/subtype;参数1=值1;参数2=值2;参数n=值n
type:表示主类型
subtype:表示子列类型
类型和参数之间用英文分号隔开
可以有很多参数,多个参数之间用英文分号隔开
5.3、常见的MimeType举例MimeType说明application/json表示json格式数据application/json;charset=UTF-8表示json格式数据,后面跟了一个编码参数text/plain表示纯文本格式内容text/html表示html格式内容text/html;charset=utf-8表示html,utf-8编码application/json;q=1表示json格式数据,有个q参数,这个参数比较特殊,表示优先级
5.4、MimeType在http请求中的应用(1)请求头Content-type:用来指定请求体中的内容的格式。(2)请求头Accept:用来告诉服务器,客户端能够接收的媒体类型。(3)响应头Content-type:用来告知客户端,响应体中的类容是什么格式。(4)Http中的Content-Type详解Http中的Content-Type是一个非常重要的东西,不了解的朋友建议先去这里了解下:
5.5、特殊参数q:指定MimeType优先级当有多个媒体类型在一起的时候,可以在媒体中添加q参数用来指定媒体类型的优先级,q值的范围从0.0~1.0(1.0优先级最高)
比如Http请求头Accept可以指定多个媒体类型,那么可以在媒体类型中加上q参数,用来指定媒体类型的优先级,服务器端优先选择媒体类型高的格式进行响应。
再回头来看看开头的案例,浏览器中看一下这个案例请求头中Accept的值
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.96、http请求头Accept是什么样的?6.1、Accept作用
用来指定客户端能够接收的媒体类型,用来告诉服务器端,客户端希望服务器端返回什么格式的数据
6.2、Accept格式媒体类型1,媒体类型2,媒体类型3
多个媒体之间用逗号隔开
媒体类型中可以使用q参数来标注其优先级,q值的范围从0.0~1.0(1.0优先级最高)
文章有点长,感谢大家的阅读,如果有收获,麻烦帮忙分享一下,路人在此感谢大家了。
咱们继续。
7、Spring中的类MediaType工具类spring中为了更方便操作媒体类型,提供了一个工具类,MediaType内部提供了很多常见的MediaType常量和常用的方法。
7.1、常见常量publicstaticfinalStringAPPLICATION_JSON_VALUE="application/json";//jsonpublicstaticfinalStringTEXT_PLAIN_VALUE="text/plain";//文本publicstaticfinalStringTEXT_HTML_VALUE="text/html";//htmlpublicstaticfinalStringAPPLICATION_XML_VALUE="application/xml";//xmlpublicstaticfinalStringIMAGE_GIF_VALUE="image/gif";//gif图片publicstaticfinalStringIMAGE_JPEG_VALUE="image/jpeg";//jpeg图片publicstaticfinalStringAPPLICATION_PDF_VALUE="application/pdf";//pdf格式publicstaticfinalStringAPPLICATION_FORM_URLENCODED_VALUE="application/x-www-form-urlencoded";//普通表单提交内容的格式publicstaticfinalStringMULTIPART_FORM_DATA_VALUE="multipart/form-data";//上传文件表单提交的内容格式7.2、常用的方法
方法说明staticMediaTypeparseMediaType(StringmediaType)将文本解析为MediaTypestaticListMediaTypeparseMediaTypes(@NullableStringmediaTypes)将文本解析为MediaType列表staticStringtoString(CollectionMediaTypemediaTypes)将MediaType列表解析为字符串staticvoidsortBySpecificityAndQuality(ListMediaTypemediaTypes)将多个MediaType进行排序,内部会按照q参数排序booleanincludes(@NullableMediaTypeother)判断当前的MediaType是否包含了参数中指定的other,比如当前的是:*/*,这是一个通配符类型的,那么可以匹配一切类型
7.3、排序规则SpringMVC内部会根据客户端Accept指定的媒体类型列表以及服务器端接口能够支持的媒体类型列表,处理之后得到一个双方都支持的媒体类型列表,然后调用MediaTypesortBySpecificityAndQuality方法排序,得到
application/xhtml+xml,application/xml;charset=UTF-8;q=0.9,application/xml;q=0.9,application/xml;charset=UTF-8;q=0.8,text/xml;charset=UTF-8;q=0.8,application/json;q=0.8,application/*+xml;charset=UTF-8;q=0.8,application/*+json;q=0.8
然后取第一个作为最终返回的类型:
Content-Type:application/xhtml+xml;charset=UTF-8
如下图,确实和浏览器中的结果一致
8、服务端可响应的媒体类型8.1、服务端有3种方式可以指定响应的媒体类型方式1:@RequestMapping注解的produces属性
方式2:("Content-Type","媒体类型");
方式3:如果上面2种方式都不指定,则由SpringMVC内部机制自动确定能够响应的媒体类型列表
8.2、方式1:@RequestMapping注解的produces属性(1)解释@RequestMapping注解有个produces属性,用来指定当前接口能够响应的媒体类型,也可以理解为此接口可以处理的媒体类型,其他的一些注解@PostMapping/@GettMapping/@PutMapping/@DeleteMapping/@PatchMapping/@PatchMapping也有这个属性,作用一样的;这里以@RequestMapping注解有个produces属性来做说明。
(2)案例比如要求接口只能返回json格式的数据,那么可以这么写
@RequestMapping(value="/cn/test1",produces={"application/json"})@ResponseBodypublicListStringtestProduct(){ListStringresult=("刘德华","张学友","郭富城","黎明");returnresult;}测试场景1:浏览器直接访问,返回的是json格式数据
测试场景2:头Accept指定为applicaiton/xml,出现了406,服务器端无法处理,那是因为客户单希望服务器端返回application/xml格式数据,而服务器端接口只能返回application/json格式的数据,请求还没有到达接口内部,就被springmvc拦截了,给拒绝了
8.3、方式2:("Content-Type","媒体类型");这种方式是直接忽略你客户端的要求,不管客户端的Accept是什么,服务器端都直接返回指定的类型,比如下面这段代码,不管客户端的Accept是什么值,最终都只会返回xml格式的数据。
@RequestMapping(value="/cn/contenttype")publicvoidtestContentType(HttpServletResponseresponse)throwsIOException{//指定了响应的结果的类型("Content-Type","application/xml");().write("List"+"item刘德华/item"+"item张学友/item"+"item郭富城/item"+"item黎明/item"+"/List");().flush();}8.4、方式3:由SpringMVC内部机制自动确定能够响应的媒体类型列表如下代码,这段代码就由SpringMVC内部结合请求头中的Accpet协商得到最终返回的媒体类型。
@RequestMapping(value="/cn/auto")@ResponseBodypublicListStringtestAuto(HttpServletResponseresponse)throwsIOException{ListStringresult=("刘德华","张学友","郭富城","黎明");returnresult;}比如,你Accept传递的是application/xml,表示客户端希望返回xml格式的数据。
Aceept传递的是application/json,表示客户端希望返回json格式的数据,那么返回但就是json格式的数据。
这个代码带来了一个问题:这段代码能够响应的媒体类型有哪些呢?这个问题大家有没有思考过
方法或者类上标注有@ResponseBody注解,通常这个接口的返回值会被SpringMVC中的(T,,,)step1:获取客户端支持的媒体类型列表
step2:获取服务器端能够响应的媒体类型列表对应的代码如下
ListMediaTypeproducibleTypes=getProducibleMediaTypes(request,valueType,targetType);
下面进入getProducibleMediaTypes方法
protectedListMediaTypegetProducibleMediaTypes(HttpServletRequestrequest,Class?valueClass,@NullableTypetargetType){//这个是从@RquestMapping的produces属性中取值,如果有就直接取这个的值SetMediaTypemediaTypes=(SetMediaType)(_MEDIA_TYPES_ATTRIBUTE);if(!(mediaTypes)){returnnewArrayList(mediaTypes);}//遍历HttpMessageConverter,调用其canWrite方法判断是否能够处理当前接口方法的返回值,比如当前接口是ListString//若可以处理,则调用其getSupportedMediaTypes方法,得到媒体类型列表ListMediaTyperesult=newArrayList();for(HttpMessageConverter?converter:){if(converterinstanceofGenericHttpMessageConvertertargetType!=null){if(((GenericHttpMessageConverter?)converter).canWrite(targetType,valueClass,null)){((valueClass));}}elseif((valueClass,null)){((valueClass));}}//如果上面的媒体类型为空,则返回*/*媒体类型,否则返回找到的媒体类型列表return(()?():result);}HttpMessageConverter我们截图看一下,这里有8个
HttpMessageConverter支持的MediaType支持的接口返回值类型说明StringHttpMessageConvertertext/plain,*/*String返回纯文本ByteArrayHttpMessageConverterapplication/octet-streambyte[]返回字节流FormHttpMessageConverterapplication/x-www-form-urlencoded
multipart/form-data
multipart/mixedMultiValueMapString,?将内容以表单提交的内容格式输出ResourceHttpMessageConverter*/*用来表示各种资源,这种可以用来下载文件MappingJackson2HttpMessageConverterapplication/json
application/*+json能够被jackson工具转换为json格式的类型都行响应json用的就是这个MappingJackson2XmlHttpMessageConverterapplication/xml
text/xml
application/*+xml能够被jacksonxml工具转换为xml格式的类型都行响应xml用的就是这个
上面列表中的最后2个Converter在下面这些包中,所以加了这些配置之后,SpringMVC才有了处理json和xml的能力,这里也算是解答了本文开头的问题。
getProducibleMediaTypes方法执行完毕之后,得到了服务器端能够响应的媒体类型列表
step3:根据双方支持的媒体类型列表,得到双方都认可媒体类型列表step4:对step中得到的双方都支持的媒体类型列表进行排序step5:取一个合适的作为响应的媒体类型step6:匹配到合适的HttpMessageConverter,将结果转换为指定的格式输出代码如下,根据接口的返回值和step5得到的MediaType,匹配到合适HttpMessageConverter,然后调用HttpMessageConverter的write方法,其内部将内容转换为指定的格式输出
if(selectedMediaType!=null){selectedMediaType=();for(HttpMessageConverter?converter:){GenericHttpMessageConvertergenericConverter=(converterinstanceofGenericHttpMessageConverter?(GenericHttpMessageConverter?)converter:null);if(genericConverter!=null?((GenericHttpMessageConverter)converter).canWrite(targetType,valueType,selectedMediaType):(valueType,selectedMediaType)){body=getAdvice().beforeBodyWrite(body,returnType,selectedMediaType,(Class?extsHttpMessageConverter?)(),inputMessage,outputMessage);if(body!=null){ObjecttheBody=body;(logger,traceOn-"Writing["+(theBody,!traceOn)+"]");addContentDispositionHeader(inputMessage,outputMessage);if(genericConverter!=null){(body,targetType,selectedMediaType,outputMessage);}else{((HttpMessageConverter)converter).write(body,selectedMediaType,outputMessage);}}else{if(()){("Nothingtowrite:nullbody");}}return;}}}9、总结本文的内容是非常非常重要的一个知识点,建议大家多看2遍,敲一敲+debug,测试测试,掌握就比较容易了;掌握这些之后才能更好的用好SpringMVC和SpringBoot。
10、案例代码git地址10.1、git地址10.2、本文案例代码结构说明





