Jackson用Feign无法反序列化Spring的org.springframework.data.domain.Sort

问题描述 投票:0回答:5

问题:假客户端对返回

Page<T>
的 Spring boot Rest API 进行 API 调用,无法反序列化该页面的
sort
属性。

  • Spring Boot:2.3.3.发布
  • Spring Cloud Feign:2.2.5.RELEASE

com.fasterxml.jackson.databind.exc.InvalidDefinitionException:不能 构造

org.springframework.data.domain.Sort
的实例(无 创建者,如默认构造函数,存在):无法反序列化 对象值(无基于委托或基于属性的创建者)位于[来源: (缓冲阅读器);行:1,列:238](通过参考链: org.springframework.cloud.openfeign.support.PageJacksonModule$SimplePageImpl["排序"])

不知道为什么注册的

PageJacksonModule
似乎不支持这一点。

给定手动配置的 Feign 客户端:

public class TelematicsConfig {

  private String host;

  ObjectMapper provideObjectMapper() {
    return new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .setPropertyNamingStrategy(SnakeCaseStrategy.SNAKE_CASE)
        .registerModule(new PageJacksonModule());
  }

  @Bean
  TelematicsClient provideTelematicsClient() {
    return Feign.builder()
        .client(new OkHttpClient())
        .encoder(new JacksonEncoder(provideObjectMapper()))
        .decoder(new JacksonDecoder(provideObjectMapper()))
        .logger(new Slf4jLogger(TelematicsClient.class))
        .logLevel(Logger.Level.FULL)
        .target(TelematicsClient.class, host);
  }

}

客户本身:

public interface TelematicsClient {

  @RequestLine("GET /api/v1/telematics/devices")
  Page<TelematicsDevice> getDevices();

}

当调用这个我得到:

2020-09-16 12:38:49.707 ERROR 96244 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException: Cannot construct instance of `org.springframework.data.domain.Sort` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (BufferedReader); line: 1, column: 238] (through reference chain: org.springframework.cloud.openfeign.support.PageJacksonModule$SimplePageImpl["sort"]) reading GET http://localhost:8081/api/v1/telematics/devices] with root cause

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Sort` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (BufferedReader); line: 1, column: 238] (through reference chain: org.springframework.cloud.openfeign.support.PageJacksonModule$SimplePageImpl["sort"])
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1611)
    at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1077)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1320)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:535)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:419)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1310)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4482)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3463)
    at feign.jackson.JacksonDecoder.decode(JacksonDecoder.java:61)

任何关于为什么这不起作用的见解将不胜感激。

编辑:下面的类似乎暗示支持排序,不是吗?

https://github.com/spring-cloud/spring-cloud-openfeign/blob/master/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageJacksonModule。 java#L69

java spring-boot spring-cloud-feign feign openfeign
5个回答
18
投票

如果您使用自动配置的 Feign 客户端,您可以按照 Spring Cloud OpenFeign 文档 打开相应的配置属性:

您可以考虑启用 Jackson 模块来获得支持

org.springframework.data.domain.Page
org.springframework.data.domain.Sort
解码。

feign.autoconfiguration.jackson.enabled=true

11
投票

找到答案了。

https://github.com/spring-cloud/spring-cloud-openfeign/blob/master/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SortJacksonModule。 java

    return new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .setPropertyNamingStrategy(SnakeCaseStrategy.SNAKE_CASE)
        .registerModule(new PageJacksonModule())
        .registerModule(new SortJacksonModule()); // <-- This.  duh.

0
投票

尝试添加:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-openfeign-core</artifactId>
        <version>3.1.3</version>
        <scope>compile</scope>
    </dependency>

0
投票

您可以通过添加 openfeign-core 依赖来获取这些模块:

implementation "org.springframework.cloud:spring-cloud-openfeign-core"

但这在 Spring Boot 3 中不再适用于

SortJacksonModule

例如:

代码(Kotlin)
private val mapper = jacksonObjectMapper().registerModules(
    PageJacksonModule(),
    SortJacksonModule(),
    JavaTimeModule(),
)
配置:

Config Not Found

抛出此异常:

java.lang.NoClassDefFoundError: feign/codec/EncodeException at org.springframework.cloud.openfeign.support.SortJacksonModule.setupModule(SortJacksonModule.java:47)

分辨率

OpenFeign spring 团队正在等待投票,以便花时间解决这个问题。请务必在这里对该问题进行投票:

https://github.com/spring-cloud/spring-cloud-openfeign/issues/675


0
投票

我解决假客户端的

Sort
Page
问题的解决方案如下:


  1. 创建

    CustomPageDeserializer<T>
    类型的类:

    import com.fasterxml.jackson.core.JsonParser; 
    import com.fasterxml.jackson.databind.DeserializationContext; 
    import com.fasterxml.jackson.databind.JsonDeserializer; 
    import com.fasterxml.jackson.databind.JsonNode; 
    import com.fasterxml.jackson.databind.ObjectMapper; 
    import org.springframework.data.domain.PageImpl; 
    import org.springframework.data.domain.PageRequest;
    import java.io.IOException; 
    import java.util.List;
    
    public class CustomPageDeserializer<T> extends JsonDeserializer<PageImpl<T>> {
    
        private final Class<T> contentClass;
    
        public CustomPageDeserializer(Class<T> contentClass) {
            this.contentClass = contentClass;
        }
    
        @Override
        public PageImpl<T> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
            ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
            JsonNode root = mapper.readTree(jsonParser);
            JsonNode content = root.get("content");
            List<T> contentList = mapper.readValue(content.traverse(), mapper.getTypeFactory().constructCollectionType(List.class, contentClass));
            int totalElements = root.get("totalElements").asInt();
            int totalPages = root.get("totalPages").asInt();
            int number = root.get("number").asInt();
    
            int pageSize = contentList.isEmpty() ? 1 : contentList.size();
            return new PageImpl<>(contentList, PageRequest.of(number, pageSize), totalElements);
        } } 
    
  2. 创建班级

    CustomPageModule
    :

    import com.fasterxml.jackson.databind.module.SimpleModule; 
    import org.springframework.data.domain.PageImpl;
    
    public class CustomPageModule  extends SimpleModule {
        public CustomPageModule(Class<?> contentClass) {
            addDeserializer(PageImpl.class, new CustomPageDeserializer<>(contentClass));
        } }
    
  3. 创建班级

    MyFeignClientConfiguration
    :

    import com.fasterxml.jackson.databind.ObjectMapper; 
    import feign.codec.Decoder; 
    import feign.optionals.OptionalDecoder; 
    import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
    import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
    import org.springframework.cloud.openfeign.support.SpringDecoder;
    import org.springframework.context.annotation.Bean; 
    import org.springframework.context.annotation.Configuration; 
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    
    @Configuration 
    public class MyFeignClientConfiguration {
        @Bean
        public Decoder feignDecoder() {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.registerModule(new CustomPageModule(MyEntity.class));
            return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(() -> new HttpMessageConverters(new
    MappingJackson2HttpMessageConverter(objectMapper)))));
        } } 
    
  4. 创建班级

    MyFeignClient
    :

    import org.springframework.cloud.openfeign.FeignClient; 
    import org.springframework.http.ResponseEntity; 
    import org.springframework.web.bind.annotation.GetMapping; 
    import java.util.List;
    
    @FeignClient(name = "myFeignClient", url =
    "${my.service.feign.url}", configuration =
    MyFeignClientConfiguration.class) 
    public interface MyFeignClient {
    
        @GetMapping("/api/action/products")
        ResponseEntity<List<String>> getAvailableProducts(); } 
    
© www.soinside.com 2019 - 2024. All rights reserved.