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的話更方便。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM