SpringBoot入门(六)数据响应与内容协商
发布日期:2022-03-04 11:48:25 浏览次数:8 分类:技术文章

本文共 8544 字,大约阅读时间需要 28 分钟。

目录


第七章 Web开发

7.4 数据响应与内容协商

7.4.1 响应JSON

7.4.1.1 jackson.jar+@ResponseBody---返回值处理器

   项目中引入web的依赖场景,而web的依赖场景中已经引用了josn的依赖场景,不需要我们自己手动引入依赖。

<dependency>

        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
</dependency>
web场景自动引入了json场景
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-json</artifactId>
        <version>2.3.4.RELEASE</version>
        <scope>compile</scope>
</dependency>

   json场景中又引入了jackson的依赖:

  • 返回值处理器

  •  经过之前说过的请求参数处理,执行相应的方法后得到返回值,进入ServletInvocableHandlerMethod类的invokeAndHandle方法:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,			Object... providedArgs) throws Exception {		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);		setResponseStatus(webRequest);        //如果当前返回值为空		if (returnValue == null) {			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {				disableContentCachingIfNecessary(webRequest);				mavContainer.setRequestHandled(true);				return;			}		}        //检测是否有一些返回失败的原因		else if (StringUtils.hasText(getResponseStatusReason())) {			mavContainer.setRequestHandled(true);			return;		}        		mavContainer.setRequestHandled(false);		Assert.state(this.returnValueHandlers != null, "No return value handlers");		//核心代码        try {			this.returnValueHandlers.handleReturnValue(					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);		}		catch (Exception ex) {			if (logger.isTraceEnabled()) {				logger.trace(formatErrorForReturnValue(returnValue), ex);			}			throw ex;		}	}
try {   this.returnValueHandlers.handleReturnValue(         returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}

  • 进入HandlerMethodReturnValueHandlerComposite类的handleReturnValue方法:

  • selectHandle方法就是查找哪个返回值处理器可以处理当前的返回值类型:

  • 返回值处理器判断是否支持这种类型返回值 supportsReturnType

  • 返回值处理器调用 handleReturnValue 进行处理

    • RequestResponseBodyMethodProcessor可以处理返回值标了@ResponseBody注解的。

      • 进入处理器RequestResponseBodyMethodProcessor,执行其方法handleReturnValue()

@Override	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {		mavContainer.setRequestHandled(true);		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);		// Try even with null return value. ResponseBodyAdvice could get involved.        //使用消息转换器进行写出操作		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);	}
      • 利用MessageConverters进行处理,将数据写为json

        • 内容协商(浏览器默认会以请求头的方式告诉服务器它能接受什么样的内容类型)

        • 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据

        • SpringMVC会挨个遍历所有容器底层的HttpMessageConverter ,看谁能处理

          • 得到MappingJackson2HttpMessageConverter可以将对象写为json

          • 利用MappingJackson2HttpMessageConverter将对象转为json再写出去

7.4.1.2 SpringMVC支持哪些返回值

ModelAndView

Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有注解@ModelAttribute且为对象类型的
有注解@ResponseBody---> RequestResponseBodyMethodProcessor;

7.4.2 HTTPMessageConverter(消息转换器)原理

7.4.2.1 MessageConverter规范

   HttpMessageConverter接口:看是否支持将此Class类型的对象,转为MediaType类型的数据

        例子:Person对象转为JSON。或者 JSON转为Person

7.4.2.2 默认的MessageConverter

0 - 只支持Byte类型的

1 - String

2 - String

3 - Resource

4 - ResourceRegion

5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class

6 - MultiValueMap

7 - true(直接返回true,相当于任意类型都可以处理),这种转换器只有导入jackon依赖才会存在

8 - true

9 - 支持注解方式xml处理的。

7.4.3 内容协商

   根据客户端接收能力不同,服务器返回不同媒体类型的数据

   只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。

7.4.3.1 引入支持返回XML的依赖

com.fasterxml.jackson.dataformat
jackson-dataformat-xml

7.4.3.2 开启浏览器参数方式内容协商功能

   为了方便内容协商,开启基于请求参数的内容协商功能。

spring:

    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

   在发起请求时,加一个format参数,如:

        http://localhost:8080/test/person?format=json

        http://localhost:8080/test/person?fprmat=xml

  •  Parameter策略优先,确定是要返回json数据(获取请求头中的format的值)

  • 最终进行内容协商,返回给客户端json即可

7.4.3.3 内容协商原理

  • 判断当前响应头中是否已经有确定的媒体类型。MediaType
  • 获取客户端(PostMan、浏览器)支持接收的内容类型,即获取客户端Accept请求头字段
    • contentNegotiationManager内容协商管理器默认使用基于请求头的策略

    • 基于请求头的话,HeaderContentNegotiationStrategy确定客户端可以接收的内容类型
  • 遍历循环所有当前系统的MessageConverter,看哪个支持操作这个对象(Person)

  • 找到支持操作Person类型的converter,把converter支持的媒体类型统计出来

  • 客户端需要【application/xml】。服务端能力【10种、json、xml】,即服务端可以把当前类型转为哪些格式

  • 进行内容协商的最佳匹配媒体类型

  • 用支持将对象转为最佳匹配媒体类型的converter。调用它进行转化 。

    • 整个过程中converter要进行两次匹配,第一次匹配是找出哪些converter可以转换当前返回的类型,第二次匹配就是在当前能够使用的converter中找到转换后的类型和浏览器需要的类型最匹配的converter

   导入了jackson处理xml的包,xml的converter就会自动进来:

WebMvcConfigurationSupportjackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);        if (jackson2XmlPresent) {			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();			if (this.applicationContext != null) {				builder.applicationContext(this.applicationContext);			}			messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));        }

7.4.3.4 自定义MessageConverter消息转换器

  • @ResponseBody响应数据出去调用RequestResponseBodyMethodProcessor(返回值处理器)处理

  • Processor处理方法返回值。通过MessageConverter(消息转换器)处理

  • 所有MessageConverter合起来可以支持各种媒体类型数据的操作(读、写)

  • 内容协商找到最终的messageConverter

   假设此时有一种场景,不同的请求返回的类型也不同:

        浏览器发请求直接返回xml   [application/xml]---jacksonXmlConverter

        发ajax请求直接返回json   [application/json]---jacksonJsonConverter

        某app发情期,返回自定义协议类型的数据   [application/xxx]---自定义xxxConverter

                假设此时的返回类型是:属性值1;属性值2

   如何自定义converter?

        想要实现/扩展SpringMVC的功能,都只有一个入口,添加一个WebMvcConfigurer,重写其

      方法。想要扩展converter,首先需要自定义一个converter,继承于HttpMessageConverter

      然后重写其方法;最后将这个自定义的converter加入默认的converters队列中

/** * 自定义的converter */public class XXXMessageConverter implements HttpMessageConverter
{ //读指的是请求转来的参数类型能不能通过这个转换器读 @Override public boolean canRead(Class clazz, MediaType mediaType) { return false; } //写指的是方法返回结果的参数类型能不能通过这个转换器写出去 @Override public boolean canWrite(Class clazz, MediaType mediaType) { //只要是Person类型的数据,就能写出去 return clazz.isAssignableFrom(Person.class); } /** * 服务器要统计所有MessageConverter都能写出哪些内容类型 * application/xxx * @return */ //获取所有支持的媒体类型,能够把返回类型转换成哪些媒体类型 @Override public List
getSupportedMediaTypes() { return MediaType.parseMediaTypes("application/xxx"); } @Override public Person read(Class
clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null; } @Override public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { //自定义协议数据的写出 String data = person.getUserName() + ";" + person.getAge() + ";" + person.getBirth(); //写出去 OutputStream body = outputMessage.getBody(); body.write(data.getBytes()); }}
@Bean    public WebMvcConfigurer webMvcConfigurer(){        return new WebMvcConfigurer() {            //在默认converter的基础上,扩展一个自定义的converter            @Override            public void extendMessageConverters(List
> converters) { //添加上自定义的converter converters.add(new XxxMessageConverter()); } }; }

   上面的方法在使用请求头的时候有效,如果想使用请求参数format来实现(默认请求参数策略能够实现的format只有xml、json),在WebMvcConfigurer中重写如下方法:

@Bean    public WebMvcConfigurer webMvcConfigurer() {        return new WebMvcConfigurer() {            /**             * 自定义内容协商策略             * @param configurer             */            @Override            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {                //Map
mediaTypes,即支持的媒体类型 Map
mediaTypes = new HashMap<>(); mediaTypes.put("json",MediaType.APPLICATION_JSON); mediaTypes.put("xml",MediaType.APPLICATION_XML); mediaTypes.put("xxx",MediaType.parseMediaType("application/xxx")); //策略,指定支持解析哪些参数对应的哪些媒体类型 ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes); //基于请求头的策略 HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy(); configurer.strategies(Arrays.asList(parameterStrategy,headerStrategy)); } }; }

   需要注意:有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。

PS:根据尚硅谷课程整理,如有侵权,联系删除

转载地址:https://blog.csdn.net/xxyneymar/article/details/122594248 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:SpringBoot入门(七)视图解析/Thymeleaf/拦截器/文件上传
下一篇:力扣题59螺旋矩阵II

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年03月21日 17时16分41秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章