zoukankan      html  css  js  c++  java
  • 为什么说HttpMessageConverter的顺序非常重要_SpringBoot

    问题描述

    系统内配置了,ProtobufJsonFormatHttpMessageConverter和FastJsonHttpMessageConverter。
    Spring官方内置的默认MessageConverter 比较标准,遇到什么 MediaType 就怎么解析。但是这两个比较特殊。

    对于Protobuf生成的参数:

    @PostMapping("/proto")
        public ResponseEntity<String> proto(@RequestBody AddressBookProtos.Person person) {
            try {
                log.info("input is {}", JsonFormat.printer().print(person));
            } catch (Exception e) {
                //
            }
            return ResponseEntity.ok().body("ok");
        }
    

    这里用到的是普通的JSON请求,也就是Request Header 的 ContentType是 application/json;charset=UTF-8;

    如果ProtobufJsonFormatHttpMessageConverter在FastJsonHttpMessageConverter 之后,那么读到的Protobuf消息是空白。
    也就说:Controller的 RequestBody 参数是空白的字符串。

    问题分析

    先看 org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver 类:

    
    //method: readWithMessageConverters()
    
    for (HttpMessageConverter<?> converter : this.messageConverters) {
        Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
        GenericHttpMessageConverter<?> genericConverter =
                (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
        if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
                (targetClass != null && converter.canRead(targetClass, contentType))) {
            if (message.hasBody()) {
                HttpInputMessage msgToUse =
                        getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
                body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                        ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
                body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
            }
            else {
                body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
            }
            break;
        }
    }
    

    这个类说明,Spring会根据Convert列表,逐个调用converter.canRead,判断是否能够支持这种内容的读写。
    FastJsonHttpMessageConverter 的canRead相当于直接返回true,因为mediaType 也支持 application/json;charset=UTF-8;
    这里考虑到JSON只是一个字符串,所以没法根据类型判断能不能读。字符串肯定能读。所以FastJSON这个地方还不能直接说他这么设计不合理。

    //FastJsonHttpMessageConverter.java
    
        @Override
        protected boolean supports(Class<?> clazz) {
            return true;
        }
    
    
        public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
            return super.canRead(contextClass, mediaType);
        }
    

    所以如果先找到了FastJsonHttpMessageConverter,那么FastJSON不认识 protobuf的 Bean,无法进行读写,因此读到一个空字符串。

    再看看ProtobufJsonFormatHttpMessageConverter的实现:

    //org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter#supports
    
    	@Override
    	protected boolean supports(Class<?> clazz) {
    		return Message.class.isAssignableFrom(clazz);
    	}
    

    这里十分精确,他就是要支持Message接口的,所有的Protobuf定义message的时候,都会继承这个接口。

    因此这里需要将 ProtobufJsonFormatHttpMessageConverter 提到FastJson之前。

    解决方案

    方案一

    
    @Bean 
    public ProtobufJsonFormatHttpMessageConverter protobufJsonFormatHttpMessageConverter() {
        return new ProtobufJsonFormatHttpMessageConverter();
    }
    
    

    这里定义的MessageConverter 会很早就扫描到Spring Context中。这里还不清楚为什么这个地方的ProtobufJsonFormatHttpMessageConverter 每次都是第一个。
    尝试修改Configuration的类名字为z开头 也总是第一个。

    同时FastJson转换器通常配置方式如下:

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            FastJsonConfig fastJsonConfig = new FastJsonConfig();
            fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue);
            FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
            fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
            fastJsonHttpMessageConverter.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8));
            converters.add(fastJsonHttpMessageConverter);
        }
    }
    

    这样这个WebMvcConfigurer 在Spring Boot启动比较晚的时候才会加载,所以这里的MessageConverter 会排到最后面。

    方案二(推荐)

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            ProtobufJsonFormatHttpMessageConverter protobufJsonFormatHttpMessageConverter = new ProtobufJsonFormatHttpMessageConverter();
            converters.add(protobufJsonFormatHttpMessageConverter);
        }
    
        @Override
        public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
            FastJsonConfig fastJsonConfig = new FastJsonConfig();
            fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue);
            FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
            fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
            fastJsonHttpMessageConverter.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8));
            converters.add(fastJsonHttpMessageConverter);
        }
    }
    

    这里configureMessageConverters 的调用顺序一定是在extendMessageConverters之前的。
    参见:

    //org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getMessageConverters
    
    	protected final List<HttpMessageConverter<?>> getMessageConverters() {
    		if (this.messageConverters == null) {
    			this.messageConverters = new ArrayList<>();
    			configureMessageConverters(this.messageConverters);
    			if (this.messageConverters.isEmpty()) {
    				addDefaultHttpMessageConverters(this.messageConverters);
    			}
    			extendMessageConverters(this.messageConverters);
    		}
    		return this.messageConverters;
    	}
    

    Spring并没有对HttpMessageConverter做什么特殊的排序。(只针对XML的排到最后,"with some slight re-ordering to put XML converters at the back of the list")

    另外参考一篇cnBlog文章 讲的HttpMessageConverter的比较详细。

  • 相关阅读:
    Java基础回顾---JVM&JDK&JRE
    学习
    学习
    学习
    进度
    进度
    毕设进度
    学习进度
    Beta阶段项目总结
    第二阶段冲刺——seven
  • 原文地址:https://www.cnblogs.com/slankka/p/11437034.html
Copyright © 2011-2022 走看看