内容协商的概述

内容协商指的是SpringMVC获取客户端可接收的媒体类型和服务器能生产的媒体类型,通过匹配,
找出最合适的媒体类型,再调用相应的HttpMessageConverter转换成匹配成功后的媒体类型数据写给客户端。

内容协商管理器

SpringMVC就是通过内容协商管理器来获取前端所需要的媒体类型,内容协商管理器中有不同的内容协商策略,根据不同的内容协商策略类来获取前端需要的媒体类型。

在SpringBoot中,默认情况下内容管理器中只有,HeaderContentNegotiationStrategy,
即根据请求头中accept中的属性来获取前端需要的媒体类型。

我们也可以通过在SpringBoot的配置文件中设置spring.mvc.contentnegotiation.favor-parameter=true来开启基于参数的内容协商策略,即只需要在请求参数中携带format属性来指定客户端的媒体类型即可,例如http://localhost:8080/test3?format=json即可返回json类型数据

需要注意的是:通过在配置文件中开启的参数协商策略只支持xml数据和json数据,即format的取值只能为xml或json,要想设置自定义的媒体类型就需要创建自定义的参数协商策略,请继续往下看。

自定义HttpMessageConverter

当我们在accept属性中以application/xml方式发请求时,服务器器返回给我们xml数据,当我们以application/json方式发请求时,服务器返回给我们json数据,那么怎么定制我们自定义的媒体类型数据呢?
例如:当我们以application/x-keyi的方式发请求时,服务器返回给我们自定义的数据格式,如属性$属性$的方式返回数据给我们。

这个时候就需要自定义消息转换器了,通过消息转换器,客户端需要application/x-keyi的数据格式,而我自定义的消息转换器支持转换这种数据格式。接下来看看一个小案例吧

请求路径

http://localhost:8888/test3,使用api工具设置请求的Accept属性为application/x-keyi
这种方式的请求模拟的是ajax请求,但原生的浏览器请求并不能设置Accept属性,只能使用基于参数的内容协商策略,还是上面的问题,默认基于参数的内容协商策略只支持xml和json格式数据,所以我们需要自定义参数内容协商策略,这个之后再谈,先看看基于ajax,使用自定义媒体类型方式发出请求吧。

控制器方法
@ResponseBody
@GetMapping("test3")
public Student test3(){
    /*
       当我们控制器方法使用@ResponseBody注解,则springmvc找到requestResponseBodyMethodProcessor
     	该返回值处理器经过匹配,调用到自定义messageConverter转换Student对象数据
     */
    Student student = new Student("万一",20,"计信");
    return student;
}
自定义HttpMessageConverter
/**
 * @author 万一
 * @date 2021年04月01日9:42
 */
/*泛型为Student类型,表示只支持写出Student类型的数据*/
public class MyMessageConverter implements HttpMessageConverter<Student> {
    /*能不能支持读取*/
    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    /*能不能支持写入数据*/
    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(Student.class);
    }

    /*自定义消息转换器支持哪些媒体类型*/
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-keyi");
    }

    @Override
    public Student read(Class<? extends Student> clazz, HttpInputMessage inputMessage) 
        throws IOException, HttpMessageNotReadableException {
        return null;
    }

    /*向response对象中写入数据*/
    @Override
    public void write(Student student, MediaType contentType, 
                      HttpOutputMessage outputMessage) 
        throws IOException,HttpMessageNotWritableException {
        String data = student.getName()+"$"+student.getAge()+"$"+student.getClassName();
        OutputStream body = outputMessage.getBody();
        body.write(data.getBytes());
    }
}
向容器中添加自定义的HttpMessageConverter
/*配置WebMvcConfigurer,定制化SpringMVC的功能*/
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {

            /*配置自定义消息转换器*/
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters)             {
                converters.add(new MyMessageConverter());
            }
        };
    }
总结
以上就是基于AJAX方式,使用自定义媒体类型(application/x-keyi)的方式发出请求,
然后服务器找到自定义的消息转换器来转换Student数据以属性值$属性值$属性值的方式发送给客户端
但这里有个问题
ajax请求可以自定义请求的Accept属性值,而普通浏览器请求的方式却不行,普通的浏览器请求要想使用自定义
媒体类型可以创建自定义的参数内容协商策略
自定义参数内容协商策略
向容器中添加内容协商策略组件
/*配置自定义参数协商策略*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    
    /*创建自定义参数协商策略支持的媒体类型,如果是默认的参数协商策略则只支持xml和json*/
    HashMap<String,MediaType> mediaTypes = new HashMap<>();
    mediaTypes.put("json",MediaType.APPLICATION_JSON);
    mediaTypes.put("xml",MediaType.APPLICATION_XML);
    mediaTypes.put("keyi",MediaType.parseMediaType("application/x-keyi"));
    
    ParameterContentNegotiationStrategy parameterStrategy = new 
        ParameterContentNegotiationStrategy(mediaTypes);
    
    /*增加请求头协商策略,因为自定义参数协商策略会把默认的请求头协商策略给覆盖掉,
        所以在这里重新设置一个请求头协商策略
    */
    HeaderContentNegotiationStrategy headerStrategy = new 
        HeaderContentNegotiationStrategy();
    /*向内容协商管理器中添加两个内容协商策略*/
    configurer.strategies(Arrays.asList(parameterStrategy,headerStrategy));
}

使用了自定义参数内容协商策略,就可以在请求中携带format=keyi的方式表示浏览器需要的媒体类型
application/x-keyi,所以服务器就会调用自定义的消息转换器对数据转换并且输出给浏览器,
需要注意的是,我们也可以自定义参数名称,parameterStrategy.setParameterName("strategyName");
的方式来改变请求时携带的参数名称。即format改成strategyName.