Multiple FeignClients with different Configuration - spring-boot

I am using FeignClient to call external components, so far without any issue.
When trying to create a new FeignClient in the same package that should handle as a response a complex Object including LocalDateTime as a property it failed with the
Expected BEGIN_ARRAY but was STRING at line 1 column {whatever column the LocalDateTime was}
At first I thought that adding the #JsonDeserializer(LocalDateTimeDeserializer.class) above the LocalDateTime property would work, but it didn't, forcing me to understand that FeignClient Deserialization had an issue.
I implemented a Custom Decoder to handle this, registering the JavaTimeModule in ObjectMapper which works.
#Configuration
public class AdapterConfiguration {
#Bean
public Decoder feignDecoder(ObjectProvider<HttpMessageConverterCustomizer> customizers) {
MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(JsonMapper.builder()
.disable(
MapperFeature.USE_ANNOTATIONS)
.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.addModule(
new JavaTimeModule())
.build());
HttpMessageConverters httpMessageConverters = new HttpMessageConverters(jacksonConverter);
org.springframework.beans.factory.ObjectFactory<HttpMessageConverters> messageConverters =
() -> httpMessageConverters;
return new ResponseEntityDecoder(new SpringDecoder(messageConverters, customizers));
}
However, it forces the other FeignClients to use that Decoder, which is unwanted because it fails parsing correct responses (throws DecoderException).
After carefully examining the documentation, it states that:
FooConfiguration does not need to be annotated with #Configuration. However, if it is, then take care to exclude it from any #ComponentScan that would otherwise include this configuration as it will become the default source for feign.Decoder, feign.Encoder, feign.Contract, etc., when specified. This can be avoided by putting it in a separate, non-overlapping package from any #ComponentScan or #SpringBootApplication, or it can be explicitly excluded in #ComponentScan.
And thus, I removed the #Configuration annotation and added the config in the #FeignClient that I wanted. However, this client does not use the decoder properly.
My current Structure is:
- FeignConfig.java, with Custom Decoder
- FeignClientA.java (with the need of custom decoder) with the following specifications:
#FeignClient(
name = "feign-client-A",
url = "${myUrl}", configuration = {FeignConfig.class})
- FeignClientB (which **works **with the default decoder and **NOT **with the Custom Decoder) with the following specifications:
#FeignClient(name = "feign-client-B", url = "${myOtherUrl}")
I cannot understand what I am missing and why I cant customize each feign client to have separate decoders.

Related

Spring and serialization to Json - how to customize globally Jackson without Spring Boot

I'm using clean Spring MVC framework (v5.3.21) without Spring Boot.
I was working with Gson library, which was used by Spring to serialize view models, returned with request methods.
public class Coffee {
String name = "n";
String brand = "b";
}
#RequestMapping(value={"/coffe"}, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Coffee getCoffee() {
return new Coffee();
}
Recently I added Jackson (v 2.13.3) on the classpath and I've noticed serialization works much different. First of all - in Gson non-private field where serialized by default, now they are not visible at the client side.
I know I can add annotation
#JsonAutoDetect(fieldVisibility = Visibility.NON_PRIVATE)
to all the model classes, or change fields to public (Jackson default visibility for fields is PUBLIC, as far as I found out).
But I would like to change just once, globally, in configuration, without rewriting code of many
I tried many options, but none of them doesn't work without Spring Boot.
Do you know to change this default setting with clean Spring?
You can create ObjectMapper bean which can be global for application
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.NON_PRIVATE));
return mapper;
}

intercept request start and end in Vaadin 14 (Flow) with Spring Boot

I'm using vaadin-spring-boot-starter for integration of Vaadin Framework 14 and Spring Boot.
I would like to override the requestStart and requestEnd methods of the SpringServlet class to do the following things:
put stuff such as the current route / view path and current user ID into the SLF4J MDC in order to include it in each logging statement
log the duration of the request
In Vaadin 8 there was a SpringVaadinServlet class which I could replace by simply annotating my custom subclass with #SpringComponent("vaadinServlet").
This approach no longer works. The vaadin-spring integration contains SpringBootConfiguration which contains a direct call to the SpringServlet constructor:
#Bean
public ServletRegistrationBean<SpringServlet> servletRegistrationBean() {
String mapping = configurationProperties.getUrlMapping();
Map<String, String> initParameters = new HashMap<>();
boolean rootMapping = RootMappedCondition.isRootMapping(mapping);
if (rootMapping) {
mapping = VaadinServletConfiguration.VAADIN_SERVLET_MAPPING;
initParameters.put(Constants.SERVLET_PARAMETER_PUSH_URL,
VaadinMVCWebAppInitializer
.makeContextRelative(mapping.replace("*", "")));
}
ServletRegistrationBean<SpringServlet> registration = new ServletRegistrationBean<>(
new SpringServlet(context, rootMapping), mapping); // <-- HERE
registration.setInitParameters(initParameters);
registration.setAsyncSupported(configurationProperties.isAsyncSupported());
registration.setName(
ClassUtils.getShortNameAsProperty(SpringServlet.class));
return registration;
}
They should use a conditional bean here so we could replace it, but unfortunately they're not.
Just adding a custom ServletRegistrationBean with a copy of the above code (but the constructor call substituted with my own) doesn't work, even with #Primary.
So is there a better way to do what I want than to exclude the whole vaadin-spring autoconfiguration and copy everything in my own configuration bean? It works but I have to check if everything's still OK after each vaadin-spring upgrade.
You could add a VaadinServiceInitListener through which you can add a custom request handler. Alternatively you could use a Filter.

How to set header variables in GraphQL-SPQR

I'm running a GraphQL API using GraphQL-SPQR and Spring Boot.
At the moment, I am throwing RuntimeExceptions to return GraphQL errors. I have a customExceptionHandler that implements DataFetcherExceptionHandler that returns errors in the correct format, as shown below:
class CustomExceptionHandler : DataFetcherExceptionHandler {
override fun onException(handlerParameters: DataFetcherExceptionHandlerParameters?): DataFetcherExceptionHandlerResult {
// get exception
var exception = handlerParameters?.exception
val locations = listOf(handlerParameters?.sourceLocation)
val path = listOf(handlerParameters?.path?.segmentName)
// create a GraphQLError from your exception
if (exception !is GraphQLError) {
exception = CustomGraphQLError(exception?.localizedMessage, locations, path)
}
// cast to GraphQLError
exception as CustomGraphQLError
exception.locations = locations
exception.path = path
val errors = listOf<GraphQLError>(exception)
return DataFetcherExceptionHandlerResult.Builder().errors(errors).build()
}
}
I use the CustomExceptionHandler as follows (in my main application class):
#Bean
fun graphQL(schema: GraphQLSchema): GraphQL {
return GraphQL.newGraphQL(schema)
.queryExecutionStrategy(AsyncExecutionStrategy(CustomExceptionHandler()))
.mutationExecutionStrategy(AsyncSerialExecutionStrategy(CustomExceptionHandler()))
.build()
}
I'd like to set a header variable for a UUID that corresponds to the exception, for logging purposes. How would I do that?
Even better, is it possible to create a Spring Bean that puts the UUID in the header for all queries and mutations?
Thanks!
when you're using spring boot, there's two options:
you're using the spring boot graphql spqr starter (which brings it's own controller to handle all graphQL requests)
you're using plain graphql-spqr and have your own controller to handle GraphQL requests
In any case, you've got a few options:
Making your CustomExceptionHandler a Spring Bean and Autowiring HttpServletResponse
That would probably be the easiest way to go - and it would probably work in any case: You could simply make your CustomExceptionHandler a Spring bean and have it autowire the HttpServletRequest - in the handler method, you could then set it to whatever you would like it to be. Here's some dummy code in Java (sorry, I am not proficient enough in Kotlin):
#Component
class CustomExceptionHandler implements DataFetcherExceptionHandler {
private final HttpServletResponse response;
public CustomExceptionHandler(HttpServletResponse response) {
this.response = response;
}
#Override
public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
response.setHeader("X-Request-ID", UUID.randomUUID().toString());
// ... your actual error handling code
}
}
This is going to work because spring will realise that HttpServletRequest differs for each request. It will therefore inject a dynamic proxy into your error handler that will point to the actual HttpServletResponse instance for every request.
I would argue, that it's not the most elegant way, but it will certainly solve your problem.
for the graphql-spqr spring boot starter
There's a default controller implementation that is used in projects using this starter. That controller will handle every graphql request that you receive. You can customise it, by implementing your own GraphQLExecutor and making it a spring bean. That executor is responsible to call the GraphQL engine, pass the parameters in and output the response. Here's the default implementation, that you might want to base your work on.
Similarly to the previous solution, you could autowire the HttpServletResponse in that class and set a HTTP Response header.
That solution would allow you to decide, if you want to set a request id in all cases, or just in specific error cases. (graphql.execute returns an object from which you can get the information if and what errors existed)
when using graphql-spqr without the spring boot starter
Locate your GraphQL controller, add an argument to that method of type HttpServletRequest - and then add headers to that as you prefer (see previous section on some more specific suggestions)

Manually configure Jackson Module for Spring WebTestClient

Prerequisites
Spring Boot: 2.0.3.RELEASE
Spring REST Docs: 2.0.1.RELEASE
Spring Auto REST Docs: 2.0.2-SNAPSHOT - Which is a local build of the current master.
The GeoJsonModule is configured in the Main Application:
import org.springframework.data.mongodb.core.geo.GeoJsonModule;
#SpringBootApplication
public class MyApplication {
[...]
#Bean
public GeoJsonModule registerGeoJsonModule() {
return new GeoJsonModule();
}
[...]
}
I am using Spring Auto REST Docs and therefore cannot use the #AutoConfigure-Annotation (Or atleast I don't know how).
Current Implementation
I configure the WebTestClient like so:
WebTestClient
.bindToApplicationContext(context)
.configureClient()
.filter(
WebTestClientRestDocumentation
.documentationConfiguration(restDocumentation)
.snippets()
.withDefaults(
WebTestClientInitializer.prepareSnippets(context),
CliDocumentation.curlRequest(),
HttpDocumentation.httpRequest(),
HttpDocumentation.httpResponse(),
AutoDocumentation.requestFields(),
AutoDocumentation.responseFields(),
AutoDocumentation.pathParameters(),
AutoDocumentation.requestParameters(),
AutoDocumentation.description(),
AutoDocumentation.methodAndPath(),
AutoDocumentation.section()
)
)
.build();
However when using the WebTestClient like this I get the following error:
org.springframework.core.codec.CodecException: Type definition error: [simple type, class org.springframework.data.mongodb.core.geo.GeoJsonPoint]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.mongodb.core.geo.GeoJsonPoint` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.example.MyClass["location"])
So I looked around and found, that the WebTestClientAutoConfiguration and SpringBootWebTestClientBuilderCustomizer are additionally customizing Codecs.
Changing the initialization to include the customizers fixes the deserialization issue and it works.
Collection<CodecCustomizer> customizers = context.getBeansOfType(CodecCustomizer.class).values();
SpringBootWebTestClientBuilderCustomizer builderCustomizer = new SpringBootWebTestClientBuilderCustomizer(customizers);
builderCustomizer.customize(builder);
builder.build();
However I do not know if this is the correct way of configuring the WebTestClient and if it works entirely or if something is still broken I just did not discover yet.
Somehow I don't think that this is the intended way and I would like to know how the WebTestClient can be configured properly.

Spring-web tries to find resource named with informed path variable

Using spring-web, I am mapping a method to receive a request containing dots "." on the path:
#RequestMapping(value = "download/{id:.+}", method = RequestMethod.GET, produces = "application/xls")
public String download(#PathVariable(value = "id") String id) { ... }
For example, /download/file.xls should be a valid address. But when I try to access that address, Spring returns Could not find acceptable representation as if it was trying to find a resource named file.xls.
Spring shouldn't execute download method rather than try to find a resource named as the path variable?
Obs.: my application is a spring-boot application.
Your #RequestMapping says it produces "application/xls", but your return type is a String and you haven't annotated the return type with #ResponseBody.
If you want to return an Excel spreadsheet, you need to produce that spreadsheet on the server and return it as a byte[] from your request mapping. I'm not sure how or why you'd return a String, unless you're controller is a simple #Controller and you're returning the view name.
Have you tried configuring your RequestMappingHandlerMapping
handler.setUseSuffixPatternMatch( false )
(I was configuring my RequestMappingHandlerMapping anyway, so for me I just needed to add that line - chances are you may be letting Spring Boot autoconfig that class).
See https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.html#setUseRegisteredSuffixPatternMatch-boolean-
Possibly you may need to turn off content negotiation as well - I can't remember exactly what Spring Boot default content negotiation is, but it might be affecting your case.
#Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
}
Worth noting that if you are working on a wider/existing application then both these configurations have possible implications more widely, so if that is the case then tread carefully!

Resources