SpringBoot:图解数据响应与内容协商原理 ---- 9

sss当通过浏览器或者PostMan模拟请求时, 我们首先是执行方法(获取解析参数,执行方法体),然后开始处理返回值,也就是我们今天的主题,响应处理(数据响应与内容协商)。其逻辑都在一个方法中:
this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest); , 这是我们今天分析的主题。

数据响应与内容协商

sssdsadasdasdasddasadaasd在这里插入图片描述

响应JSON
JSON1 jackson.jar+@ResponseBody
   <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>  #这里面有相关的jackson依赖
      <version>2.3.7.RELEASE</version>
      <scope>compile</scope>
    </dependency>

sss● 正因为有这样的配置,给前端自动返回json数据;

测试代码:

sss● 测试代码:

@Controller
public class ResponseTestController {
    @ResponseBody
    @GetMapping("/test/person")
    public Person getPerson(){
        Person person = new Person();
        person.setAge(28);
        person.setBirth(new Date());
        person.setBirth(new Date());
        person.setUserName("Zhangsan");
        return person;
    }
}

sss● 结果:在这里插入图片描述

返回值解析器:

sss● 当我们在执行前目标方法时(获得参数,解析参数,执行方法体,返回值,响应返回值),我们需要先获取参数解析器和返回值解析器,关于参数获得解析的相关方法、逻辑在前几篇文章已经介绍,我们重点看返回值处理器(一共15个):
sssddsadsaasdasd在这里插入图片描述
ssdasds【SpringMVC到底支持哪些返回值?】:(网图)
ssddasdasdasd在这里插入图片描述
ssdsdasd有 @ModelAttribute 且为对象类型的 @ResponseBody 注解 —> RequestResponseBodyMethodProcessor;

sss● 获得参数处理器和返回值处理器后,执行下图方法(我们在文章开始处就声明了该方法)(注意其中参数,首先是执行完方法体后的Object返回对象,然后是该返回对象的类型,最后是mav容器和requst请求):
在这里插入图片描述sssdasdas在这里插入图片描述
sss● 点击进入handleReturnValue()方法,有两个核心方法:①、遍历返回值处理器,获取相应的返回值处理器 (我们获得的返回值处理器是 RequestResponseBodyMethodProcessor) ②、利用匹配的返回值i处理器对返回值进行响应。
在这里插入图片描述
sss● 我们重点分析第二个方法,看其源码:
在这里插入图片描述
sss【注】:inputMessage、outputMessage就是将原生的webReques包装起来,对应着请求、响应。

sss第二个方法writeWithMessageConverters(): 利用MessageConverter(消息转换器)将数据写为 json(当然也可以是别的形式),该方法重点是内容协商,就是如何选用消息转换器MessageConverter,然后转成什么样的格式返回?我们先简单分析这个方法,然后在下一节深入研究:
在这里插入图片描述
sss【上图问题解答 1】:① 注释: 是person类型,不是字符串类型,valueTypre是返回值类型,target是目标类型,所以赋值位置是在else后。

sss【上图问题解答 2】:②注释: 返回值类型是不是资源类型,比如流数据。

sss【上图问题解答 3】:③~④注释中间:会先通过OutputMessage的响应头看有没有内容类型,如果这个请求已经被处理过,则可能响应头里就有内容类型,这时候则直接使用,也就是selectedMedaiTypre = contentType,而不会执行④⑤⑥⑦注释所在的代码。

sss【上图问题解答 4】:⑧注释:到底应不应该优化呢?如果我们在注释④的位置之前我们得值响应头有内容类型,所以就不会执行⑤这个方法,就不会有缓存,所以还需要重新遍历所有的消息转换器。

sss【上图问题解答 5】:对于⑨前面的this.addContentDispositionHeader(inputMessage, outputMessage),是往outputMessage中加上ServletServerHttpResponse,不过目前为空。在⑨中添加headers,key为"Content-Type",value为 selectedMediaType.

sss【注 1】:默认的MessageConverter:
sasdadsadaasdsadasdasdsdassass在这里插入图片描述
dsdssss0 - 只支持Byte类型的dsdssss1 - Stringdsdssss2 - Stringdsdssss3 - Resource
dsdssss4 - ResourceRegion
dsdssss5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
dsdssss6 - MultiValueMap
dsdssss7.8 - true sss- true:无论什么类都返回true,换句话说,就是可以将任何类型的对象转换为浏览器所想要的数据类型;dsdssss9 - 支持注解方式xml处理的。

sss【注 2】:MessageConverter规范:
在这里插入图片描述
dsdssssHttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
dsdsssseg:Person对象转为JSON。或者 JSON转为Person

sss【注 3】:最终消息转换器 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
在这里插入图片描述
sss【注 4】:

sss总结:返回值解析器原理:(两个方法,先看是否支持,再处理!!!)
ssdsdsdsdsdsdsdsdss在这里插入图片描述
sss● 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
sss● 2、返回值处理器调用 handleReturnValue 进行处理
sss● 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
ssdsdss ○ 1. 利用 MessageConverters 进行处理 将数据写为json
sdsdsdsdsdss ■ 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
在这里插入图片描述
ssdsdsddsdss ■ 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
ssddsdsdsdss ■ 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
dsddssss在这里插入图片描述
ssdsdsdsdsdsdsdss ● 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
ssddssdsdsddssdss ● 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。

内容协商:

sss在数据响应的过程中,我们要考虑客户端接收什么类型的数据,而服务器又可以生成什么样的数据,是根据什么评判的?接下来我们开始分析。

测试: 根据客户端接收能力不同,返回不同媒体类型的数据。

sss引入xml依赖:

 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

sss浏览器测试返回xml: 在这里插入图片描述

sss为什么浏览器会直接返回xml格式的数据 ?(因为xml优先级高)
在这里插入图片描述
sss【注】: postman分别测试返回JSON和XML。只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
在这里插入图片描述
sss【注1】: 若客户端无法解析服务端返回的内容,即媒体类型未匹配,那么响应406

sss【注2】:为了方便内容协商,开启基于请求参数的内容协商功能:

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

sdsdss发送请求 ① : http://localhost:8080/test/person?format=json
在这里插入图片描述
sdsdss发送请求 ② : http://localhost:8080/test/person?format=xml
在这里插入图片描述
sss【注3】: 优先级为:后缀 > 请求参数 > HTTP首部Accept。具体看SpringMVC配置相关讲解

内容协商原理:

sss当我们在获取获取客户端可接受的数据类型时,相应代码表现为:

 List<MediaType> acceptableTypes = this.getAcceptableMediaTypes(request);
 	====》点击进入查看源码:
 	    private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
        	return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request)) }
        	=====》我们发现我们是通过"内容协商管理器"来进行解析媒体类型的,那是如何解析参数的呢?	

ssds我们发现,“内容协商管理器”中存放着不同的策略,我们根据不同的策略来解析客户端可接受的媒体类型,通过源码可知,我们会遍历所有的策略,比如有两个策略,我们通过第一个策略先获得媒体类型,如果该类型包含了所有媒体类型(也就是范围没有缩小),则继续用下一个策略寻找媒体类型。源码如下:
asdasdasdsaddsasss
在这里插入图片描述
ssasdasdasdsaadsasd在这里插入图片描述
sss【注4】:服务器默认的策略其实只有一个,就是根据“请求头”来进行解析的,而如果想根据参数来解析,那么需要进行相关配置,具体请看【注2】

sss【注5】:我们通过参数内容协商策略发现,默认的媒体类型只有"xml"和"json",也就是说我们如果想通过这种策略接收别的媒体方式是不行的。(后面自定义会说)

sss接下来我们通过不同的设置来看不同的策略具体是怎么解析参数的(就是上图中的 ②)?

①、基于请求头的策略是如何解析客户端可以获得数据类型的?

在这里插入图片描述

②、基于参数类型的策略是如何解析客户端可以获得什么数据类型的?在这里插入图片描述sssⅰthis.getMediaTypeKey(webRequest) ? 如何根据请求获得参数名?

ssssdas在这里插入图片描述
sdsss【注6】:通过format,我们找对应的参数是多少:在这里插入图片描述
sssⅱ 如何根据参数名获得相应的参数媒体类型呢?
在这里插入图片描述

自定义 MessageConverter

sss实现多协议数据兼容。json、xml、x-test(自定义)SpringMVC自定义功能只需要给容器中添加一个WebMvcConfiguration

内容协商 - 基于请求头 Accept
@Controller
public class ResponseTestController {

    /**
     * 1、浏览器发送请求直接返回xml      【application/xml】  jackonXmlConverter
     * 2、如果是ajax请求,返回json          【application/json】   jackonJsonConverter
     * 3、如果硅谷app发请求,返回自定义协议数据    [application/x-test] xxxConverter
     *          属性值1:属性值2;
     * 步骤:
     * 1、添加自定义的MessageConverter进系统底层
     * 2、系统底层就会统计出所有MessageConvcerter能操作哪些类型
     * 3、客户端内容协商[guigu--->guigu]
     * @return
     */
    @ResponseBody
    @GetMapping("/test/person")
    public Person getPerson(){
        Person person = new Person();
        person.setAge(28);
        person.setBirth(new Date());
        person.setBirth(new Date());
        person.setUserName("Zhangsan");
        return person;
    }
}

sss①:自定义converter:

public class GuiguMessageConverter implements HttpMessageConverter<Person> {

    @Override
    public boolean canRead(Class<?> aClass, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class<?> aClass, MediaType mediaType) {
        return aClass.isAssignableFrom(aClass);
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-guigu");
    }

    @Override
    public Person read(Class<? extends Person> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Person person, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        String data = person.getUserName() + ";" + person.getAge() + ";" + person.getBirth();

        OutputStream body = httpOutputMessage.getBody();

        body.write(data.getBytes());
    }
}

sss②:将自定义的converter添加到WebConfig

@Configuration(proxyBeanMethods = false)
public class config /*implements WebMvcConfigurer*/ {
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new GuiguMessageConverter());
            }
        };
    }
}

sss利用postman发送请求,结果如下:在这里插入图片描述

内容协商 - 基于请求参数 format

sss基于请求参数的内容协商默认只支持两种类型 - xml、json,如果还想再增加一种类型,那么我们应该写这个内容协商管理器:

@Configuration(proxyBeanMethods = false)
public class config /*implements WebMvcConfigurer*/ {

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            /**
             * 自定义内容协商策略
             * @param configurer
             */
            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                //Map<String, MediaType> mediaTypes
                Map<String, MediaType> mediaTypes = new HashMap<>();
                //指定支持哪些参数对应的哪些媒体类型
                mediaTypes.put("json",MediaType.APPLICATION_JSON);
                mediaTypes.put("xml",MediaType.APPLICATION_XML);
                mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));

                ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);

                HeaderContentNegotiationStrategy headStrategy = new HeaderContentNegotiationStrategy();

                configurer.strategies(Arrays.asList(parameterStrategy,headStrategy));
            }

            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new GuiguMessageConverter());
            }
        };
    }
}

sss【注】:当我们自定义了基于请求参数的内容协商策略后,就无法解析基于请求头的内容协商了,如果这时发起基于请求头的内容协商,不管请求头要求返回什么类型的数据,都会默认返回JSON。这时,我们可以自定义基于请求头的策略,或者将原本的基于请求头的策略添加到内容协商管理器中。
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值