FastJsonHttpMessageConverter请求中参数序列化问题排查

问题

前几天帮忙其他部门的多个祖先级项目改造开发,服务间使用Feign方式调用,发现接口提供方接收到的请求,没有请求参数,经过排查发现服务调用方的FastJsonHttpMessageConverter配置方式存在问题,导致请求中RequestBody的序列化出现问题。

排查步骤

  1. 服务提供方排查

经debug DispatcherServlet发现request中各请求参数名发生了变化—命名规则由camel变成了snake_case(蛇形命名规则),接口的请求参数名的命名规则是camel,所以导致接口中各个参数没有值,判定很有可能是请求调用方的HttpMessageConverter的配置有问题;

  1. 服务调用方排查

debug查发出请求时请求的所有的messageConverter,发现项目里添加了FastJsonHttpMessageConverter,在经过FastJsonHttpMessageConverter 对请求参数的处理后,HttpOutputMessage输出的请求参数名发生了变化—把原来camel规则的请求参数统一进行了snake_case转换;

  1. 至此基本定位问题原因-FastJsonHttpMessageConverter的配置导致参数命名发生改变,进而导致服务提供方接收不到原有的参数。

解决方法

深入阅读FastJsonHttpMessageConverter源码,在writeInternal 和 readInternal时,会使用FastJsonConfig-> SerializeConfig,来进行请求体的序列化和反序列化,如图-1

 

图-1

方法一:

  1. 自定义FeignClient的configuration覆盖默认的FeignClientsConfiguration,在自定义的FeignConfiguration中去重新定义SpringEncoder和SpringDecoder,在Encoder和Decoder中去重新配置FastJsonHttpMessageConverter;
  2. 针对此FeignClient的configuration定义一个此feign实例的FastJsonConfig->SerializeConfig,而非使用全局的SerializeConfig(之所以不用全局,因为项目比较庞杂,怕影响其他三方服务调用),设置其propertyNameStrategy = CamelCase
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import feign.Logger.Level;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder;
import org.springframework.cloud.netflix.feign.support.SpringDecoder;
import org.springframework.cloud.netflix.feign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;

public class FeignConfiguration {
    HttpMessageConverters converters;
    ObjectFactory<HttpMessageConverters> httpMessageConverters;

    public FeignConfiguration() {
    }

    @PostConstruct
    public void init() {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        List<MediaType> supportedMediaTypes = new ArrayList();
        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
        supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
        supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
        supportedMediaTypes.add(MediaType.APPLICATION_PDF);
        supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XML);
        supportedMediaTypes.add(MediaType.IMAGE_GIF);
        supportedMediaTypes.add(MediaType.IMAGE_JPEG);
        supportedMediaTypes.add(MediaType.IMAGE_PNG);
        supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);
        supportedMediaTypes.add(MediaType.TEXT_HTML);
        supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);
        supportedMediaTypes.add(MediaType.TEXT_PLAIN);
        supportedMediaTypes.add(MediaType.TEXT_XML);
        converter.setSupportedMediaTypes(supportedMediaTypes);
        this.converters = new HttpMessageConverters(new HttpMessageConverter[]{converter});
        this.httpMessageConverters = () -> {
            return this.converters;
        };
    }

    @Bean
    public Level feignLoggerLevel() {
        return Level.FULL;
    }

    @Bean
    public ErrorDecoder businessErrorDecoder() {
        return new FeignErrorDecoder();
    }

    @Bean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(this.httpMessageConverters));
    }

    @Bean
    public Encoder feignEncoder() {
        return new SpringEncoder(this.httpMessageConverters);
    }

}

 

方法二:

服务提供方,feign接口的@RequestBody 对应的请求实体类添加@JSONType(naming=PropertyNamingStrategy.CamelCase)

SerializeConfig在getWriter时会读取这个注解来决定序列化/反序列化时的请求体中参数的命名规则。

 1 import com.alibaba.fastjson.PropertyNamingStrategy;
 2 import com.alibaba.fastjson.annotation.JSONType;
 3 
 4 import java.io.Serializable;
 5 
 6 /**
 7  * @author zhaoxinbo
 8  * @name: UserRequest
 9  * @description: 请求参数-user RequestBody
10  * @date 2021/9/24 20:22
11  */
12 @JSONType(naming = PropertyNamingStrategy.CamelCase)
13 public class UserRequest implements Serializable {
14     private Long userId;
15 
16     private String userName;
17 
18     public Long getUserId() {
19         return userId;
20     }
21 
22     public void setUserId(Long userId) {
23         this.userId = userId;
24     }
25 
26     public String getUserName() {
27         return userName;
28     }
29 
30     public void setUserName(String userName) {
31         this.userName = userName;
32     }
33 }
View Code

 

建议使用第二种方式,服务提供方明确告知请求参数的命名规则,当然第一种方法中如果有服务提供方来提供FeignConfiguration的话更方便。

posted @ 2021-09-24 20:39  Aboruo  阅读(1241)  评论(0编辑  收藏  举报