ContentNegotiation内容协商机制(一)---Spring MVC内置支持的4种内容协商方式【享学Spring MVC】(中)

简介: ContentNegotiation内容协商机制(一)---Spring MVC内置支持的4种内容协商方式【享学Spring MVC】(中)

原因简析


Chrome浏览器请求默认发出的Accept是:Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3。

由于我例子使用的是@ResponseBody,因此它不会返回一个view:交给消息转换器处理,因此这就和MediaType以及权重有关了。


消息最终都会交给AbstractMessageConverterMethodProcessor.writeWithMessageConverters()方法:


// @since 3.1
AbstractMessageConverterMethodProcessor:
  protected <T> void writeWithMessageConverters( ... ) {
    Object body;
    Class<?> valueType;
    Type targetType;
    ...
    HttpServletRequest request = inputMessage.getServletRequest();
    // 这里交给contentNegotiationManager.resolveMediaTypes()  找出客户端可以接受的MediaType们~~~
    // 此处是已经排序好的(根据Q值等等)
    List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
    // 这是服务端它所能提供出的MediaType们
    List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
    // 协商。 经过一定的排序、匹配  最终匹配出一个合适的MediaType
    ...
    // 把待使用的们再次排序,
    MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
    // 最终找出一个最合适的、最终使用的:selectedMediaType 
      for (MediaType mediaType : mediaTypesToUse) {
        if (mediaType.isConcrete()) {
          selectedMediaType = mediaType;
          break;
        } else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
          selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
          break;
        }
      }
  }


acceptableTypes是客户端通过Accept告知的。

producibleTypes代表着服务端所能提供的类型们。参考这个getProducibleMediaTypes()方法:


AbstractMessageConverterMethodProcessor:
  protected List<MediaType> getProducibleMediaTypes( ... ) {
    // 它设值的地方唯一在于:@RequestMapping.producers属性
    // 大多数情况下:我们一般都不会给此属性赋值吧~~~
    Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    if (!CollectionUtils.isEmpty(mediaTypes)) {
      return new ArrayList<>(mediaTypes);
    }
    // 大多数情况下:都会走进这个逻辑 --> 从消息转换器中匹配一个合适的出来
    else if (!this.allSupportedMediaTypes.isEmpty()) {
      List<MediaType> result = new ArrayList<>();
      // 从所有的消息转换器中  匹配出一个/多个List<MediaType> result出来
      // 这就代表着:我服务端所能支持的所有的List<MediaType>们了
      for (HttpMessageConverter<?> converter : this.messageConverters) {
        if (converter instanceof GenericHttpMessageConverter && targetType != null) {
          if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
            result.addAll(converter.getSupportedMediaTypes());
          }
        }
        else if (converter.canWrite(valueClass, null)) {
          result.addAll(converter.getSupportedMediaTypes());
        }
      }
      return result;
    } else { 
      return Collections.singletonList(MediaType.ALL);
    }
  }


可以看到服务端最终能够提供哪些MediaType,来源于消息转换器HttpMessageConverter对类型的支持。


本例的现象:起初返回的是json串,仅仅只需要导入jackson-dataformat-xml后就返回xml了。原因是因为加入MappingJackson2XmlHttpMessageConverter都有这个判断:


  private static final boolean jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
    if (jackson2XmlPresent) {
      addPartConverter(new MappingJackson2XmlHttpMessageConverter());
    }


所以默认情况下Spring MVC并不支持application/xml这种媒体格式,所以若不导包协商出来的结果是:application/json。


默认情况下优先级是xml高于json。当然一般都木有xml包,所以才轮到json的。


另外还需要注意一点:有的小伙伴说通过在请求头里指定Content-Type:application/json来达到效果。现在你应该知道,这样做显然是没用的(至于为何没用,希望读者做到了心知肚明),只能使用Accept这个头来指定~~~


第一种协商方式是Spring MVC完全基于HTTP Accept首部的方式了。该种方式Spring MVC默认支持且默认已开启。


优缺点:


  • 优点:理想的标准方式
  • 缺点:由于浏览器的差异,导致发送的Accept Header头可能会不一样,从而得到的结果不具备浏览器兼容性


方式二:(变量)扩展名

基于上面例子:若我访问/test/1.xml返回的是xml,若访问/test/1.json返回的是json;完美~


这种方式使用起来非常的便捷,并且还不依赖于浏览器。但我总结了如下几点使时的注意事项:


  1. 扩展名必须是变量的扩展名。比如上例若访问test.json / test.xml就404~
  2. @PathVariable的参数类型只能使用通用类型(String/Object),因为接收过来的value值就是1.json/1.xml,所以若用Integer接收将报错类型转换错误~1. 小技巧:我个人建议是这部分不接收(这部分不使用@PathVariable接收),拿出来只为内容协商使用
  3. 扩展名优先级比Accept要高(并且和使用神马浏览器无关)


优缺点:


优点:灵活,不受浏览器约束

缺点:丧失了同一URL的多种展现方式。在实际环境中使用还是较多的,因为这种方式更符合程序员的习惯


方式三:请求参数

这种协商方式Spring MVC支持,但默认是关闭的,需要显示的打开:


@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
      // 支持请求参数协商
        configurer.favorParameter(true);
    }
}


请求URL:/test/1?format=xml返回xml;/test/1?format=json返回json。同样的我总结如下几点注意事项:


  1. 前两种方式默认是开启的,但此种方式需要手动显示开启
  2. 此方式优先级低于扩展名(因此你测试时若想它生效,请去掉url的后缀)


优缺点:


  • 优点:不受浏览器约束
  • 缺点:需要额外的传递format参数,URL变得冗余繁琐,缺少了REST的简洁风范。还有个缺点便是:还需手动显示开启。


方式四:固定类型(produces)


它就是利用@RequestMapping注解属性produces(可能你平时也在用,但并不知道原因):


@ResponseBody
@GetMapping(value = {"/test/{id}", "/test"}, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Person test() { ... }


访问:/test/1返回的就是json;即使你已经导入了jackson的xml包,返回的依旧还是json。


它也有它很很很重要的一个注意事项:produces指定的MediaType类型不能和后缀、请求参数、Accept冲突。例如本利这里指定了json格式,如果你这么访问/test/1.xml,或者format=xml,或者Accept不是application/json或者*/* 将无法完成内容协商:http状态码为406,报错如下:

image.png



相关文章
|
3月前
|
人工智能 JSON 安全
Spring Boot实现无感刷新Token机制
本文深入解析在Spring Boot项目中实现JWT无感刷新Token的机制,涵盖双Token策略、Refresh Token安全性及具体示例代码,帮助开发者提升用户体验与系统安全性。
361 5
|
6月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
315 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
6月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
477 0
|
2月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
177 0
|
2月前
|
JSON 前端开发 Java
Spring MVC 核心组件与请求处理机制详解
本文解析了 Spring MVC 的核心组件及请求流程,核心组件包括 DispatcherServlet(中央调度)、HandlerMapping(URL 匹配处理器)、HandlerAdapter(执行处理器)、Handler(业务方法)、ViewResolver(视图解析),其中仅 Handler 需开发者实现。 详细描述了请求执行的 7 步流程:请求到达 DispatcherServlet 后,经映射器、适配器找到并执行处理器,再通过视图解析器渲染视图(前后端分离下视图解析可省略)。 介绍了拦截器的使用(实现 HandlerInterceptor 接口 + 配置类)及与过滤器的区别
193 0
|
2月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
110 0
|
2月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
148 0
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
371 0
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
160 0
|
存储 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(五)
经过一段时间的准备,新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始,在之前的文章中,讲解了商城系统的整体功能设计,页面布局设计,环境搭建,系统配置,及首页【商品类型,banner条,友情链接,降价促销,新品爆款】,商品列表页面,商品详情等功能的开发,今天继续讲解购物车功能开发,仅供学习分享使用,如有不足之处,还请指正。
255 0

热门文章

最新文章