How to set custom Jackson ObjectMapper with Spring Cloud Netflix Feign
Asked Answered
O

5

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.

Overly answered 7/3, 2016 at 20:59 Comment(5)
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). docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/…Lorineloriner
@Lorineloriner 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.Overly
You'd have to create a SpringDecoder bean using the doc link a previously posted and mess with it there.Lorineloriner
@spencergibb, I got to work as shown in the answer below. Thanks for you help.Overly
blog.birost.com/a?ID=00600-16a0c674-d3f2-41a7-8e41-335e75f48dd0Lifeordeath
O
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;
    }
}
Overly answered 9/3, 2016 at 5:21 Comment(3)
worked for me simply with return new JacksonDecoder(customObjectMapper());Riemann
This works very good. ThanksSenhauser
@Senhauser please consider the updated version of this answer.Overly
S
14

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>
Septempartite answered 8/7, 2019 at 2:33 Comment(4)
What's the dependency? And version? Can you show the pom.xml entry for this?Khamsin
Can you quantify the increase in latency in either percentage or ms or something? I'd like a reference point for how much difference this makes. Looks clean though.Idyll
great improved in my impression, 10ms average improved for 8k qps in production.Septempartite
Could you show how you pass a custom ObjectMapper in your solution?Footstalk
D
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.

Denounce answered 19/9, 2021 at 7:55 Comment(0)
G
1

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);
    }
}
Gean answered 29/8, 2023 at 9:25 Comment(0)
T
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);
        };
    }
}
Tarboosh answered 4/8, 2021 at 7:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.