22

I'm running into a scenario where I need to define a one-off @FeignClient for a third party API. In this client I'd like to use a custom Jackson ObjectMapper that differs from my @Primary one. I know it is possible to override spring's feign configuration defaults however it is not clear to me how to simply override the ObjectMapper just by this specific client.

Newbie
  • 7,031
  • 9
  • 60
  • 85
  • Have you tried it and it doesn't work? Spring Cloud Feign uses the same `HttpMessageConverters` object that Spring MVC uses. Configuring it the normal Spring Boot way should 'just work' (thought I haven't tried it myself). http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-customize-the-jackson-objectmapper – spencergibb Mar 07 '16 at 21:07
  • @spencergibb I can override the ObjectMapper and it is correctly used by all Spring MVC controllers and all the Feign clients. However, what I need is a particular feign client, out of the many, to use a different object mapper from the one configured by default. I'm not sure how to even get started to make this work. – Newbie Mar 07 '16 at 22:00
  • You'd have to create a `SpringDecoder` bean using the doc link a previously posted and mess with it there. – spencergibb Mar 07 '16 at 22:04
  • @spencergibb, I got to work as shown in the answer below. Thanks for you help. – Newbie Mar 09 '16 at 05:23
  • https://blog.birost.com/a?ID=00600-16a0c674-d3f2-41a7-8e41-335e75f48dd0 – firstpostcommenter Aug 22 '22 at 08:20

5 Answers5

46

Per the documentation, you can provide a custom decoder for your Feign client as shown below.

Feign Client Interface:

@FeignClient(value = "foo", configuration = FooClientConfig.class)
public interface FooClient{
    //Your mappings
}

Feign Client Custom Configuration:

@Configuration
public class FooClientConfig {

    @Bean
    public Decoder feignDecoder() {
        HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());

        HttpMessageConverters httpMessageConverters = new HttpMessageConverters(jacksonConverter);
        ObjectFactory<HttpMessageConverters> objectFactory = () -> httpMessageConverters;


        return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
    }

    public ObjectMapper customObjectMapper(){
        ObjectMapper objectMapper = new ObjectMapper();
        //Customize as much as you want
        return objectMapper;
    }
}
Newbie
  • 7,031
  • 9
  • 60
  • 85
13

follow @NewBie`s answer, i can give the better one...

  @Bean
  public Decoder feignDecoder() {
    return new JacksonDecoder();
  }

if you want use jackson message converter in feign client, please use JacksonDecoder, because SpringDecoder will increase average latency of feignclient call in production.

    <!-- feign-jackson decoder -->
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-jackson</artifactId>
      <version>10.1.0</version>
    </dependency>
suiwenfeng
  • 1,865
  • 1
  • 25
  • 32
4

@NewBie's answer has serious performance problems. During the new HttpMessageConverters process, loadclass will be performed, resulting in a large number of thread block. If you have used this code, please modify it as follows:

ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);

change to

HttpMessageConverters httpMessageConverters = new HttpMessageConverters(jacksonConverter);
ObjectFactory<HttpMessageConverters> objectFactory = () -> httpMessageConverters;

You can use JMeter and Arthas to reproduce this phenomenon, and the modified program has been greatly improved.

0

Define a custom decoder as below, annotated with @Configuration and set as parameter for the feign client interface, configuration = CustomFeignClientConfig.class

@Configuration
public class CustomFeignClientConfig {
    @Bean
    public Decoder feignDecoder() {
        return (response, type) -> {
            String bodyStr = Util.toString(response.body().asReader(Util.UTF_8));
            JavaType javaType = TypeFactory.defaultInstance().constructType(type);
            return new ObjectMapper().readValue( bodyStr, javaType);
        };
    }
}
Ömer
  • 84
  • 8
0

you can use SpringDecoder and SpringEncoder.

public SpringDecoder(ObjectFactory<HttpMessageConverters> messageConverters)

is deprecated. you should use another constructor:

public SpringDecoder(ObjectFactory<HttpMessageConverters> messageConverters,
            ObjectProvider<HttpMessageConverterCustomizer> customizers)

for example:

    @Bean
    public Decoder feignDecoder(ObjectProvider<HttpMessageConverterCustomizer> customizers) {
//        HttpMessageConverter<?> jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
//        HttpMessageConverters httpMessageConverters = new HttpMessageConverters(jacksonConverter);
        
        var httpMessageConverters = new HttpMessageConverters();
        return new ResponseEntityDecoder(new SpringDecoder(() -> httpMessageConverters, customizers));
    }

    @Bean
    public Encoder feignEncoder() {
        var httpMessageConverters = new HttpMessageConverters();
        return new SpringEncoder(() -> httpMessageConverters);
    }

if you want to write a decode time customizer, then you can wright like this:

@Component
public class HttpMessageCustomizer implements HttpMessageConverterCustomizer {
    @Override
    public void accept(List<HttpMessageConverter<?>> httpMessageConverters) {
        //customizing
    }

    @Override
    public Consumer<List<HttpMessageConverter<?>>> andThen(Consumer<? super List<HttpMessageConverter<?>>> after) {
        return HttpMessageConverterCustomizer.super.andThen(after);
    }
}