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