28.自定义MessageConverter

之前的文章我们知道,如果使用@ResponseBody,我们在发请求的时候,对请求头进行设置,可以获取到我们想要的返回数据格式,例如json数据,或者XML数据(当然这些都要导入对应的依赖,也要开启基于请求参数的内容协商功能),这是服务器与浏览器进行内容协商后的结果。那么,如果我们想自定义返回的数据类型,如何设置?
例如引入xml依赖:
 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

开启协商功能:

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

进行不同返回类型测试:

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

• HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型

• 3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
• 4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。
• 5、客户端需要【application/xml】。服务端能力【10种、json、xml】


• 6、进行内容协商的最佳匹配媒体类型
• 7、用支持将对象转为最佳匹配媒体类型 的converter。调用它进行转化 。


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

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

WebMvcConfigurationSupport
jackson2XmlPresent = 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()));
        }

自定义 MessageConverter
在开发过程中,我们可能对返回的数据有自己的要求


实现多协议数据兼容。json、xml、x-guigu
@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
Processor 处理方法返回值。通过 MessageConverter 处理
所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
内容协商找到最终的 messageConverter;
SpringMVC的什么功能。一个入口给容器中添加一个 WebMvcConfigurer
 

@Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
            }
        }
    }

注意:如果使用的是configureMessageConverter,那么是覆盖了默认的所有messageconverter
自定义converter

public class GuiguMessageConverter implements HttpMessageConverter<Person> {

    @Override
    //如果这里设置为true,那就可以读@RequestBody的值
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

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

    /**
     * 服务器要统计所有MessageConverter都能写出哪些内容类型
     *
     * application/x-guigu
     * @return
     */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-guigu");
    }

    @Override
    public Person read(Class<? extends Person> 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());
    }
}

将自定义的converter添加进去:

 

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



以参数的形式进行内容协商(即将要返回的数据类型,加载url中)
上面的方式是以请求头的形式,进行内容协商的,如果以参数的形式,会方便一些。

我们可以看到,内容协商功能是由contentNegotiationManager实现的,在它下面有两种策略:
ParameterContentNegotiationStrategy(基于请求参数)
HeadContentnegotiationStrategy(基于请求头)
而请求参数的策略,默认只支持xml,json两种方式,那么看到这里,思路自然也就有了,那就是自定义内容协商管理器contentNegotiationManager。

 

@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);
//                parameterStrategy.setParameterName("ff");

                HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();

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



效果:

但是在这里要注意:如果我们自定义了参数策略,他的头策略反而会失效,这时就需要debug了。。。。在spingboot中,有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效,这里的解决方法是在configureContentNegotiation中也添加自定义头策略.

posted @ 2022-08-08 13:39  随遇而安==  阅读(26)  评论(0编辑  收藏  举报