問題
前幾天幫忙其他部門的多個祖先級項目改造開發,服務間使用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的話更方便。