JSON deserializer conflict with Spring Boot Starter Web and Spring Cloud Stream - spring-boot

I have a fairly simple Java app that listens to a Kafka topic for JSON messages.
These are the main dependencies and versions:
id 'org.springframework.boot' version '2.3.5.RELEASE'
...
set('springCloudVersion', "Hoxton.SR9")
...
implementation 'org.springframework.cloud:spring-cloud-stream'
implementation 'org.springframework.cloud:spring-cloud-stream-binder-kafka'
The application.properties config that specifies the JSON format:
spring.cloud.stream.bindings.listener-in-0.content-type = application/json
And the "core loop":
#Bean
public Consumer<Message<MyDataModel>> listener() {
return message -> {
...
And it works like a charm. But now I'm trying to add a /metrics endpoint to the app, with the Actuator library:
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
After adding these to build.gradle, without changing anything in the code itself, the consumer in the above snippet fails to deserialize the incoming messages, every field of the model object is null.
Clearly, the spring-boot-starter-web package overwrites the JSON handling mechanism that came with the spring-cloud-stream library, but I have no idea what to do. Experimented with excluding parts of the web-starter library and changing around the springBoot version, but no success yet.

Upgrading the springboot version to 2.4.2 from 2.3.5.RELEASE, and spring-cloud version to 2020.0.1 from Hoxton.SR9 solved the issue for us.

Related

WebSocket cannot connect to endpoint when running Spring-Boot 2.2 with lazy bean initialization?

I'm having trouble getting the client to connect to a WebSocket endpoint when the Spring-Boot 2.2 application is started in lazy-init mode.
I was able to get this Spring.io tutorial to work. It uses spring-boot-starter-parent version 2.1.6. I changed the pom.xml to use spring-boot-starter-parent version 2.2.0 and got it to work also.
But when I set spring.main.lazy-initialization=true in application.properties, the client does not connect to the server via WebSocket anymore when I click on the "Connect" button. In Chrome Developer Tool > Network > WebSocket, I see that the client sends a CONNECT request, but it never receives a "CONNECTED" response.
I've uploaded my project file to GitHub here:
https://github.com/hirokiterashima/spring-boot-stomp-messaging-websocket. The first commit is the 'complete' directory of the original project in the Spring.io tutorial, which uses Spring-Boot 2.1.6: https://github.com/spring-guides/gs-messaging-stomp-websocket/tree/master/complete. The second commit contains my changes to pom.xml to use Spring-Boot 2.2.0 and addition of application.properties file to enable lazy initialization. As you can see, all I did in the second commit was change to Spring Boot 2.2.0, updated the jQuery webjars dependency, and enabled lazy initialization. If you comment-out the spring.main.lazy-initialization line in application.properties, it will work.
Did anybody else come across a similar issue? What can I do to make this work?
Thanks for your help!
just register the following #Bean:
#Bean
public LazyInitializationExcludeFilter stompWebSocketHandlerMappingLazyInitializationExcludeFilter() {
return LazyInitializationExcludeFilter.forBeanTypes(HandlerMapping.class);
}
or
#Bean
public LazyInitializationExcludeFilter stompWebSocketHandlerMappingLazyInitializationExcludeFilter() {
return ((beanName, beanDefinition, beanType) -> beanName.equals("stompWebSocketHandlerMapping"));
}

Spring Boot 2 integrate Brave MySQL-Integration into Zipkin

I am trying to integrate the Brave MySql Instrumentation into my Spring Boot 2.x service to automatically let its interceptor enrich my traces with spans concerning MySql-Queries.
The current Gradle-Dependencies are the following
compile 'io.zipkin.zipkin2:zipkin:2.4.5'
compile('io.zipkin.reporter2:zipkin-sender-okhttp3:2.3.1')
compile('io.zipkin.brave:brave-instrumentation-mysql:4.14.3')
compile('org.springframework.cloud:spring-cloud-starter-zipkin:2.0.0.M5')
I already configured Sleuth successfully to send traces concerning HTTP-Request to my Zipkin-Server and now I wanted to add some spans for each MySql-Query the service does.
The TracingConfiguration it this:
#Configuration
public class TracingConfiguration {
/** Configuration for how to send spans to Zipkin */
#Bean
Sender sender() {
return OkHttpSender.create("https://myzipkinserver.com/api/v2/spans");
}
/** Configuration for how to buffer spans into messages for Zipkin */
#Bean AsyncReporter<Span> spanReporter() {
return AsyncReporter.create(sender());
}
#Bean Tracing tracing(Reporter<Span> spanListener) {
return Tracing.newBuilder()
.spanReporter(spanReporter())
.build();
}
}
The Query-Interceptor works properly, but my problem now is that the spans are not added to the existing trace but each are added to a new one.
I guess its because of the creation of a new sender/reporter in the configuration, but I have not been able to reuse the existing one created by the Spring Boot Autoconfiguration.
That would moreover remove the necessity to redundantly define the Zipkin-Url (because it is already defined for Zipkin in my application.yml).
I already tried autowiring the Zipkin-Reporter to my Bean, but all I got is a SpanReporter - but the Brave-Tracer-Builder requries a Reporter<Span>
Do you have any advice for me how to properly wire things up?
Please use latest snapshots. Sleuth in latest snapshots uses brave internally so integration will be extremely simple.

Metrics don't show up on the /prometheus endpoint

I have two services, ping and pong where ping sends requests to pong. This metric shows up on the /metrics endpoint for the ping service:
gauge.servo.hystrix.hystrixcommand.http://pong.pongclient#hello().90
but it doesn't appear on the /prometheus endpoint. Other metrics appear on this endpoint, but not the servo metrics with information about Feign/Hystrix http requests.
How do I get those metrics to appear on the /prometheus endpoint?
I have the following dependencies on my build.gradle
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-actuator'
compile 'org.springframework.cloud:spring-cloud-starter-eureka'
compile 'org.springframework.cloud:spring-cloud-starter-hystrix'
compile 'org.springframework.cloud:spring-cloud-starter-feign'
compile 'org.springframework.retry:spring-retry'
compile "io.micrometer:micrometer-core:${micrometerVersion}"
compile "io.micrometer:micrometer-spring-legacy:${micrometerVersion}"
compile "io.micrometer:micrometer-registry-prometheus:${micrometerVersion}"
with the following versions
springCloudVersion = 'Dalston.SR4'
micrometerVersion = '1.0.0-rc.4'
The code can be found here https://github.com/fiunchinho/spring-resiliency
You need to manually add the plugin for Hystrix:
HystrixPlugins.getInstance().registerMetricsPublisher(new MicrometerMetricsPublisher(Metrics.globalRegistry));
You could add it in a #PostConstruct in a configuration.
I've created https://github.com/micrometer-metrics/micrometer/issues/237 to address the shortcoming in the future.
checketts' answer did not work for me and threw a java.lang.IllegalStateException: Another strategy was already registered. on startup, but adding the HystrixMetricsBinder bean, which does more or less the same internally, did the trick.
#Configuration
public class MetricsConfig {
#Bean
HystrixMetricsBinder registerHystrixMetricsBinder() {
return new HystrixMetricsBinder();
}
}
taken from https://stackoverflow.com/a/52740957/60518
You need to instrument and configure the spring boot services to monitor with prometheus as follows:
You need to include a dependency in the gradle.build file
You need to implement Metric endpoint
You need to add the standard exporters from Prometheus JVM
For more implementation details on how to do it see the examples here and also here

Where can I find SqsListener

We are trying to use spring-cloud-aws to receive messages from AWS SQS
We would like to receive messages using annotation. In spring documentation, it is confusing.
Below, they stated to use MessageMapping and QueueMessageHandler annotation.
Annotation-driven listener endpoints are the easiest way for listening
on SQS messages. Simply annotate methods with MessageMapping and the
QueueMessageHandler will route the messages to the annotated methods.
But in the sample, #SQSListener is used.
#SqsListener("queueName")
public void queueListener(Person person) {
// ...
}
I searched for #SqsListener and found that it is being used in test classes like here . So we tried to import, org.springframework.cloud.aws.messaging.listener.annotation.SqsListener. Unfortunately this annotation class is not available in latest release.
Is the org.springframework.cloud.aws.messaging.listener.annotation.SqsListener that I am using is proper one? Or it is not yet present in released version? If not released can I use #MessageMapping to receive messages from SQS?
It appears to not be included in the 1.0.4 release of Spring Cloud AWS however I was able to successfully import SqsListener when using 1.1.0.RC1
You need to add:
dependencyManagement {
imports {
mavenBom 'org.springframework.cloud:spring-cloud-aws:1.1.0.RC1'
mavenBom "org.springframework.boot:spring-boot-starter-parent:1.3.3.RELEASE"
}
Additionally the messaging dependency needs to be added (and I've got actuator included too):
dependencies {
compile("org.springframework.cloud:spring-cloud-starter-aws")
compile("org.springframework.cloud:spring-cloud-aws-messaging")
compile("org.springframework.boot:spring-boot-starter-actuator")
}
Note, I haven't tested it to see if it can actually consume a message of SQS but at least the dependency is resolving.
I am using the 1.1.0.RELEASE, that's the dependencies I have:
compile("org.springframework.boot:spring-boot-starter:1.3.5.RELEASE")
compile("org.springframework.cloud:spring-cloud-starter-aws-messaging:1.1.0.RELEASE")
I tried both annotations #SqsListener and #MessageMapping both work fine. The SqsListener is a specialization of the MessageMapping annotation which is adding an additional property, the deletion policy.
I am guessing the documentation has to be updated, I got confused as well.
Now #SqsListener available with 1.1.0.RELEASE.

Spring Integration with Jackson ObjectMapper and Java 8 Time (JSR-310)

I am struggling with configuring a "custom" ObjectMapper to be used by the Spring Integration DSL transformers.
I receive an java.time.Instant json representations that I would like to parse to object properties. i.e:
{"type": "TEST", "source":"TEST", "timestamp":{"epochSecond": 1454503381, "nano": 335000000}}
The message is a kafka message which raises a question: Should I write a custom serializer implementing Kafka encoders/decoders in order to be able to transform the kafka message to the right object or spring-integration have to do this automatically?
fw/dependencies and version:
Spring Boot - 1.3.2.RELEASE
Spring Integration Java Dsl - 1.1.1.RELEASE
FasterXml Jackson - 2.6.5
I've added this Java Configuration to the project following the Jackson documentation:
https://github.com/FasterXML/jackson-datatype-jsr310
#Configuration
public class IntegrationConfiguration {
#Bean
public JsonObjectMapper<JsonNode, JsonParser> jsonObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
return new Jackson2JsonObjectMapper(mapper);
}
}
and the following Jackson JSR-310 artefact as well:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.6.5</version>
</dependency>
Based on this post on the Spring blog I don't even have to register the new Java8 time module.
https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring#jackson-modules
This is the exception I got:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class java.time.Instant]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: {"type":"TEST","source":"TEST","timestamp":{"epochSecond":1454503381,"nano":335000000}}; line: 1, column: 71] (through reference chain: my.app.MyDto["timestamp"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1106)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:296)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:133)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:520)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:95)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:258)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:125)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3736)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2764)
at org.springframework.integration.support.json.Jackson2JsonObjectMapper.fromJson(Jackson2JsonObjectMapper.java:75)
at org.springframework.integration.support.json.Jackson2JsonObjectMapper.fromJson(Jackson2JsonObjectMapper.java:44)
at org.springframework.integration.support.json.AbstractJacksonJsonObjectMapper.fromJson(AbstractJacksonJsonObjectMapper.java:56)
at org.springframework.integration.json.JsonToObjectTransformer.doTransform(JsonToObjectTransformer.java:78)
at org.springframework.integration.transformer.AbstractTransformer.transform(AbstractTransformer.java:33)
... 74 more
RESOLUTION:
The problem was that I expected that Spring will detect the jackson-datatype-jsr310 archetype and register the JavaTimeModule, but it doesn't which is totally fine.
There are two way we can fix this:
1. The accepted answer if we use Spring Boot with Spring Integration as is.
2. If using the Spring Integration Dsl, just keep the IntegrationConfiguration class with the jsonObjectMapper() bean and use it like that:
#Autowired
private JsonObjectMapper jsonObjectMapper;
return IntegrationFlows
.from(inboundChannel())
.transform(Transformers.fromJson(myDto.class, jsonObjectMapper))
...
There is nothing to do with the Spring Boot on the matter to force Spring Integration to use that.
You just need to configure JsonToObjectTransformer with that your jsonObjetMapper():
#Bean
#Transformer(inputChannel="input", outputChannel="output")
JsonToObjectTransformer jsonToObjectTransformer() {
return new JsonToObjectTransformer(jsonObjectMapper());
}
Although there is no reason to register JsonObjectMapper as a bean.
Have you defined an encoder to your channel adapter?
You should always use an encoder for whichever adapter you're using, inbound channel adapter or message drive channel adapter.
In your case StringEncoder should solve the problem.
<bean id="myEncoder" class="org.springframework.integration.kafka.serializer.common.StringEncoder"/>

Resources