问题
前几天帮忙其他部门的多个祖先级项目改造开发,服务间使用Feign方式调用,发现接口提供方接收到的请求,没有请求参数,经过排查发现服务调用方的FastJsonHttpMessageConverter配置方式存在问题,导致请求中RequestBody的序列化出现问题。
排查步骤
- 服务提供方排查
经debug DispatcherServlet发现request中各请求参数名发生了变化—命名规则由camel变成了snake_case(蛇形命名规则),接口的请求参数名的命名规则是camel,所以导致接口中各个参数没有值,判定很有可能是请求调用方的HttpMessageConverter的配置有问题;
- 服务调用方排查
debug查发出请求时请求的所有的messageConverter,发现项目里添加了FastJsonHttpMessageConverter,在经过FastJsonHttpMessageConverter 对请求参数的处理后,HttpOutputMessage输出的请求参数名发生了变化—把原来camel规则的请求参数统一进行了snake_case转换;
- 至此基本定位问题原因-FastJsonHttpMessageConverter的配置导致参数命名发生改变,进而导致服务提供方接收不到原有的参数。
解决方法
深入阅读FastJsonHttpMessageConverter源码,在writeInternal 和 readInternal时,会使用FastJsonConfig-> SerializeConfig,来进行请求体的序列化和反序列化,如图-1
图-1
方法一:
- 自定义FeignClient的configuration覆盖默认的FeignClientsConfiguration,在自定义的FeignConfiguration中去重新定义SpringEncoder和SpringDecoder,在Encoder和Decoder中去重新配置FastJsonHttpMessageConverter;
- 针对此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 }
建议使用第二种方式,服务提供方明确告知请求参数的命名规则,当然第一种方法中如果有服务提供方来提供FeignConfiguration的话更方便。