How to set custom Jackson ObjectMapper with Spring Cloud Netflix Feign - spring

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.

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;
}
}

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>

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);
};
}
}

#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.

Related

Use org.json with Spring Boot

I am using Spring Boot framework and trying to create a structure where the developer can only return org.json.JSONObject instance. I have this endpoint declaration.
#RequestMapping(path = "/hello", method = RequestMethod.POST)
#ResponseBody
public org.json.JSONObject hello(HttpServletRequest request, HttpServletResponse response) throws IOException
This always returns {"empty":false} because Jackson used by the framework does not know how to serialize the org.json instance. I am trying to tell Jackson how to serialize the org.json instance by using the following dependency.
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-json-org</artifactId>
<version>2.13.0</version>
</dependency>
But I cannot get it work unless I change the return type to Map value which is not possible. Using
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JsonOrgModule())
.build()
does not help. Is there a global ObjectMapper object that is used by Spring Boot where I can register the JsonOrgModule at the application startup? How can I use org.json.JSONObject return type using Spring Boot framework.
Thanks!
As per Spring Boot docs 4.3 Customize the Jackson ObjectMapper section:
Any beans of type com.fasterxml.jackson.databind.Module are
automatically registered with the auto-configured
Jackson2ObjectMapperBuilder and are applied to any ObjectMapper
instances that it creates. This provides a global mechanism for
contributing custom modules when you add new features to your
application.
Therefore, if you provide a #Bean of type JsonOrgModule it will be automatically applied to the default ObjectMapper created at startup.
For exmaple:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public JsonOrgModule jsonOrgModule() {
return new JsonOrgModule();
}
}
#SpringBootTest
class ObjectMapperTests {
#Autowired
ObjectMapper defaultObjectMapper;
#Test
void defaultObjectMapperShouldWriteJsonObject() throws JSONException, JsonProcessingException {
// Given
var jsonObject = new JSONObject().put("username", "eHayik");
// When
var json = defaultObjectMapper.writeValueAsString(jsonObject);
// Then
assertThat(json).isEqualTo("{\"username\":\"eHayik\"}");
}
}

Customise JSON date formatting of JSON for spring-mvc (non-boot)?

I am converting my app to get rid of spring-boot, it now uses only Spring (5.3).
I've added the #EnableWebMvc configuration and I have my endpoints working properly for the most part - they return the data I want as JSON.
Previously, I customised the date format with the spring-boot property: spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
In the new pure-spring app though, it's regressed back serializing to a long value.
I've tried the following, but it doesn't seem to even use these beans at all:
#Bean
public ObjectMapper objectMapper() {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
ObjectMapper dateFormatMapper = new ObjectMapper();
dateFormatMapper.setDateFormat(dateFormat);
return dateFormatMapper;
}
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2JsonView(){
var converter = new MappingJackson2HttpMessageConverter();
converter.getObjectMapper().setDateFormat(
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") );
return converter;
}
I'm looking to customise the format globally, not on a per-field basis.
What would be the equivalent of spring.jackson.date-format for pure Spring #EnableWebMvc setup?
You can customize MappingJackson2HttpMessageConverter by using WebMvcConfigurer with #EnableWebMvc.
For example:
#Configuration
#EnableWebMvc
public class YourConfiguration implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
For more information, please see 1.11.7. Message Converters - Web on Servlet Stack - docs.spring.io.

circuit breaker annotation with Spring mvc(NOT Spring-boot)

I am trying to use #CircuitBreaker annotation in spring-mvc project but it does not seem to work.
Does resilience4j-annotations work on a traditional non spring-boot setup?
#Bean
public CircuitBreaker edocCircuitBreaker() {
LOGGER.info("Creating Circuit");
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.slowCallDurationThreshold(Duration.ofMillis(1000))
.minimumNumberOfCalls(2)
.slidingWindowSize(2)
.failureRateThreshold(100)
.build();
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);
CircuitBreaker serviceClientCircuitBreaker = circuitBreakerRegistry.circuitBreaker("ClientCircuitBreaker");
serviceClientCircuitBreaker.getEventPublisher().onStateTransition(this::serviceCircuitOpenHanlder);
return serviceClientCircuitBreaker;
}
#CircuitBreaker(name="ClientCircuitBreaker")
public String sendAndReceive(String request, RequestParameters parameters) throws RuntimeException {
StreamSource source = new StreamSource(new StringReader(request));
StreamResult result = new StreamResult(new StringWriter());
.....
You should use resilience4j-spring. It provides a lot of #Configuration classes for you.
You need to import the Configuration classes:
#Import({ CircuitBreakerConfiguration.class, RetryConfiguration.class, TimeLimiterConfiguration.class, BulkheadConfiguration.class }
The CircuitBreakerConfiguration is important, because it configures the CircuitBreakerAspect bean which is needed so that annotations are working.
You have to create a #Bean which uses your external configuration properties file and fills and returns CircuitBreakerConfigurationProperties.
In resilience4j-spring-boot2 we configure it automatically with #EnableConfigurationProperties(CircuitBreakerProperties.class)

How to define global static header on Spring Boot Feign Client

I have a spring boot app and want to create a Feign client which has a statically defined header value (for auth, but not basic auth). I found the #Headers annotation but it doesn't seem to work in the realm of Spring Boot. My suspicion is this has something to do with it using the SpringMvcContract.
Here's the code I want to work:
#FeignClient(name = "foo", url = "http://localhost:4444/feign")
#Headers({"myHeader:value"})
public interface LocalhostClient {
But it does not add the headers.
I made a clean spring boot app with my attempts and posted to github here: github example
The only way I was able to make it work was to define the RequestInterceptor as a global bean, but I don't want to do that because it would impact other clients.
You can also achieve this by adding header to individual methods as follows:
#RequestMapping(method = RequestMethod.GET, path = "/resource", headers = {"myHeader=value"})
Using #Headers with dynamic values in Feign client + Spring Cloud (Brixton RC2) discusses a solution for dynamic values using #RequestHeader.
You can set a specific configuration class on your feign interface and define a RequestInterceptor bean in there. For example:
#FeignClient(name = "foo", url = "http://localhost:4444/feign",
configuration = FeignConfiguration.class)
public interface LocalhostClient {
}
#Configuration
public class FeignConfiguration {
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate requestTemplate) {
// Do what you want to do
}
};
}
}
You could specify that through the application.yml file:
feign:
client:
config:
default:
defaultRequestHeaders:
Authorization:
- Basic 3ncond2dS3cr2t
otherHeader:
- value
Note that this will be applicable to all your Feign Clients if it happened that you're using more than one. If that's the case, you could add a section per client instead of adding this to the default section.
Try this
#Component
public class AuthFeignInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
final HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
template.header("Header_name","Value");
}
}
}

Configured ObjectMapper not used in spring-boot-webflux

I have mixins configured in my objectmapperbuilder config, using the regular spring web controller, the data outputted according to the mixins.
However using webflux, a controller with a method returning a Flow or Mono have the data serialized like if the objectmapper a default one.
How to get webflux to enforce an objectmapper configuration to be used ?
sample config:
#Bean
JavaTimeModule javatimeModule(){
return new JavaTimeModule();
}
#Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.mixIn(MyClass.class, MyClassMixin.class);
}
I actually found my solution by stepping through the init code:
#Configuration
public class Config {
#Bean
JavaTimeModule javatimeModule(){
return new JavaTimeModule();
}
#Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.mixIn(MyClass.class, MyClassMixin.class);
}
#Bean
Jackson2JsonEncoder jackson2JsonEncoder(ObjectMapper mapper){
return new Jackson2JsonEncoder(mapper);
}
#Bean
Jackson2JsonDecoder jackson2JsonDecoder(ObjectMapper mapper){
return new Jackson2JsonDecoder(mapper);
}
#Bean
WebFluxConfigurer webFluxConfigurer(Jackson2JsonEncoder encoder, Jackson2JsonDecoder decoder){
return new WebFluxConfigurer() {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(encoder);
configurer.defaultCodecs().jackson2JsonDecoder(decoder);
}
};
}
}
I translated the solution of #Alberto Galiana to Java and injected the configured Objectmapper for convenience, so you avoid having to do multiple configurations:
#Configuration
#RequiredArgsConstructor
public class WebFluxConfig implements WebFluxConfigurer {
private final ObjectMapper objectMapper;
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(
new Jackson2JsonEncoder(objectMapper)
);
configurer.defaultCodecs().jackson2JsonDecoder(
new Jackson2JsonDecoder(objectMapper)
);
}
}
Just implement WebFluxConfigurer and override method configureHttpMessageCodecs
Sample code for Spring Boot 2 + Kotlin
#Configuration
#EnableWebFlux
class WebConfiguration : WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
configurer.defaultCodecs().jackson2JsonEncoder(Jackson2JsonEncoder(ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)))
configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(ObjectMapper()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)))
}
}
Make sure all your data classes to be encoded/decoded have all its properties annotated with #JsonProperty even if property name is equal in class and json data
data class MyClass(
#NotNull
#JsonProperty("id")
val id: String,
#NotNull
#JsonProperty("my_name")
val name: String)
In my case, I was trying to use a customized ObjectMapper while inheriting all of the behavior from my app's default WebClient.
I found that I had to use WebClient.Builder.codecs. When I used WebClient.Builder.exchangeStrategies, the provided overrides were ignored. Not sure if this behavior is something specific to using WebClient.mutate, but this is the only solution I found that worked.
WebClient customizedWebClient = webClient.mutate()
.codecs(clientCodecConfigurer ->
clientCodecConfigurer.defaultCodecs()
.jackson2JsonDecoder(new Jackson2JsonDecoder(customObjectMapper)))
.build();
I have tried all the different solutions (#Primary #Bean for ObjectMapper, configureHttpMessageCodecs(), etc.). What worked for me at the end was specifying a MIME type. Here's an example:
#Configuration
class WebConfig: WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
val encoder = Jackson2JsonEncoder(objectMapper, MimeTypeUtils.APPLICATION_JSON)
val decoder = Jackson2JsonDecoder(objectMapper, MimeTypeUtils.APPLICATION_JSON)
configurer.defaultCodecs().jackson2JsonEncoder(encoder)
configurer.defaultCodecs().jackson2JsonDecoder(decoder)
}
}

Resources