I'm just trying use Camel Reactive Stream together with Spring Boot Reactor using the following code
package com.manning.camel.reactive;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.reactive.streams.api.CamelReactiveStreamsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
/**
* A simple Camel route that triggers from a timer and calls a bean and prints to system out.
* <p/>
* Use <tt>#Component</tt> to make Camel auto-detect this route when starting.
*/
#RestController
public class MySpringBootRouter extends RouteBuilder {
#Autowired
private ProducerTemplate template;
#Autowired
private CamelReactiveStreamsService crss;
#GetMapping
public Mono<String> sayHi() {
template.asyncSendBody("direct:works", "Hi");
//return Mono.from(crss.fromStream("greet", String.class));
return Mono.from(crss.fromStream("greet", String.class));
}
#Override
public void configure() {
from("direct:works")
.log("Fired")
.to("reactive-streams:greet");
}
}
After run the code
java.lang.IllegalStateException: The stream has no active subscriptions
After a long time, solved the error, as can be noticed the Router Class logic was changed a little
#Slf4j
#Service
#AllArgsConstructor
public class MyService {
final CamelContext context;
#PostConstruct
public void consumerData() {
var rCamel = CamelReactiveStreams.get(context);
var numbers = rCamel.fromStream("numbers", Integer.class);
Flux.from(numbers).subscribe(e -> log.info("{}", e));
}
}
#Component
#NoArgsConstructor
public class MyRouter extends RouteBuilder {
// Injects the Subscriber
#Autowired MyService service;
#Override
public void configure() {
//onException(ReactiveStreamsNoActiveSubscriptionsException.class)
// .continued(true);
from("timer://reactiveApp?fixedRate=true&period=2s")
.transform(method(Random.class, "nextInt(100)"))
//.log("${body}");
.to("direct:message");
from("direct:message")
//.log("${body}")
.to("reactive-streams:numbers");
}
}
Related
I have a spring boot application with routes defined as follows:
#Component
public class SampleRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
rest("/customers-controller/")
.get("/customers").to("direct:getcustomer)
.get("/{id}").to("direct:customerDetail")
.get("/{id}/orders").to("direct:customerOrders")
.post("/neworder").to("direct:customerNewOrder");
}
I'm trying to create a contract to test this endpoint ('/customers') and create a stub that will be used in my consumer class.
A contract for messaging services like camel is similar to this:
Contract.make {
label("positive")
input {
messageFrom("seda:getcustomer")
messageBody([
id: "25_body"
])
messageHeaders {
messagingContentType(applicationJson())
// header("id","123_header")
}
}
outputMessage {
sentTo("seda:iris-address-int")
body([
"id":"25_body","firstname":null,"lastname":null,"email":null,"phone":null,"street":null,"city":null,"postcode":null
]
)
headers {
messagingContentType()
}
}
}
Now, I'm not sure on how to define the contract such that it points to my chosen rest endpoint like I would do with a RestController.
Consider the test below. Is it possible to generate this test on the provider side using spring cloud contract given that I'm not using the #RestController but rather the rest component ?
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class TestRestRoute {
#Autowired
private TestRestTemplate restTemplate;
#LocalServerPort
int randomServerPort
#Test
public void test_bank_route() throws URISyntaxException, IOException {
//call the REST API
final String baseUrl = "http://localhost:" + randomServerPort + "/customers-controller/customers";
URI uri = new URI(baseUrl);
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class );
Assert.assertEquals(MediaType.APPLICATION_JSON_VALUE, Objects.requireNonNull(response.getHeaders().getContentType()).toString());
Assert.assertEquals(200, response.getStatusCodeValue());
Assert.assertNull(response.getBody());
}
With this commit https://github.com/spring-cloud-samples/spring-cloud-contract-samples/commit/760d7105bde2f253ca0bff84fa3f4d5a35a1931e I've added a sample for Camel to spring cloud contract branch 3.0.x. Of course same rules apply to Spring Cloud Contract in other versions. In general what you can do is on the producer side:
Define a configuration for a route and extract the component URIs to separate methods:
#Configuration
class RouteConfiguration {
#Bean
RoutesBuilder myRouter() {
return new RouteBuilder() {
#Override
public void configure() {
from(start())
.bean(MyProcessor.class)
.to(finish());
}
};
}
// rabbitmq://hostname[:port]/exchangeName?[options]
String start() { return "rabbitmq:localhost/person?queue=person"; }
String finish() {
return "rabbitmq:localhost/verifications?queue=verifications";
}
}
However in the contract we will leverage the seda component (the way you did it)
Contract.make {
label("positive")
input {
messageFrom("seda:person")
messageBody([
age: 25
])
messageHeaders {
messagingContentType(applicationJson())
}
}
outputMessage {
sentTo("seda:verifications")
body([
eligible: true
])
headers {
messagingContentType(applicationJson())
}
}
}
Now, in the base class for the generated tests we will change the configuration to reflect what we have in the contract
package com.example.demo;
import org.apache.camel.test.spring.CamelSpringRunner;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
#RunWith(CamelSpringRunner.class)
#SpringBootTest(classes = BaseClass.TestConfiguration.class)
// IMPORTANT
#AutoConfigureMessageVerifier
// IMPORTANT
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public abstract class BaseClass {
#Configuration
#EnableAutoConfiguration
static class TestConfiguration extends RouteConfiguration {
// was: rabbit
// will be: a queue
#Override
String start() {
return "seda:person";
}
#Override
String finish() {
return "seda:verifications";
}
}
}
We're overriding the start() and finish() methods. You could do sth similar in your case. Then on the consumer side you can reference it like this:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.camel.CamelContext;
import org.apache.camel.ConsumerTemplate;
import org.apache.camel.ProducerTemplate;
import org.assertj.core.api.BDDAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
import org.springframework.test.annotation.DirtiesContext;
#SpringBootTest
#AutoConfigureStubRunner(
ids = "com.example:beer-api-producer-camel",
stubsMode = StubRunnerProperties.StubsMode.LOCAL
)
// IMPORTANT
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class DemoApplicationTests {
#Autowired ConsumerTemplate consumerTemplate;
#Autowired ProducerTemplate producerTemplate;
#Autowired CamelContext camelContext;
ObjectMapper objectMapper = new ObjectMapper();
// consumer -> seda:person
// producers -> seda:person -> person -> verifications -> seda:verifications
// consumer -> seda:verifications
#BeforeEach
public void setup() {
this.camelContext.getShutdownStrategy().setTimeout(1);
}
#Test
public void should_trigger_a_negative_verification() throws Exception {
this.producerTemplate.sendBodyAndHeader("seda:person", new Person(17),
"contentType", "application/json");
String string =
this.consumerTemplate.receiveBody("seda:verifications", String.class);
Verification verification = this.objectMapper.readerFor(Verification.class).readValue(string);
BDDAssertions.then(verification).isNotNull();
BDDAssertions.then(verification.eligible).isFalse();
}
#Test
public void should_trigger_a_positive_verification() throws Exception {
this.producerTemplate.sendBodyAndHeader("seda:person", new Person(25),
"contentType", "application/json");
String string =
this.consumerTemplate.receiveBody("seda:verifications", String.class);
Verification verification = this.objectMapper.readerFor(Verification.class).readValue(string);
BDDAssertions.then(verification).isNotNull();
BDDAssertions.then(verification.eligible).isTrue();
}
}
I have this simple example of Spring Scheduler:
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
#Component
public class AppScheduler {
#Scheduled(fixedRate = 10000)
public void myScheduler() {
System.out.println("Test print");
}
}
Is there a way to trigger execution in the current moment let's say from web page?
Just make a dump controller to call myScheduler method:
#Controller
public class DumpController {
#Autowired
private AppScheduler scheduler;
#RequestMapping("/ping")
public void ping() {
scheduler.myScheduler();
}
}
I'm working on Java for more than 4 years but new to Spring. I'm facing issue for initializing blank List for every single PUT request.
public abstract class BaseX implements InterfaceX
public class DefaultX extends BaseX
public class DefaultXAndY extends DefaultX
Issue:
List used in class BaseX is not clearing for every HTTP PUT request. The code is as followed.
InterfaceX.java
public interface InterfaceX {
public void process(Long id);
public void publish();
}
BaseX.java
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
#Component
public abstract class BaseX implements InterfaceX{
private List<Long> listLong = new ArrayList<Long>();
public void addToList(Long id){
System.out.println("Here in BaseX");
listLong.add(id);
}
#Override
public void publish() {
System.out.println("Print list values");
listLong.stream().forEach(System.out::println);
}
}
DefaultX.java
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
#Component
#Resource(name = "defaultX")
public class DefaultX extends BaseX{
#Override
public void process(Long id) {
//business logic
System.out.println("Here in DefaultX");
addToList(id);
}
}
DefaultXAndY.java
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
#Component
#Resource(name = "defaultXAndY")
public class DefaultXAndY extends DefaultX{
#Override
public void process(Long id) {
//Business logic different than X
System.out.println("Here in DefaultXAndY");
id = id + 10;
super.process(id);
}
}
TestService.java
public interface TestService {
public void testServiceMethod(Long id);
}
TestServiceImpl
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.orderhive.inventory.service.TestService;
import com.orderhive.inventory.stock.InterfaceX;
#Service
public class TestServiceImpl implements TestService{
#Autowired
#Resource(name = "defaultXAndY")
private InterfaceX interfaceX;
#Override
public void testServiceMethod(Long id) {
interfaceX.process(id);
interfaceX.publish();
System.out.println("API call finished for id: " + id);
System.out.println("--------------------------------");
}
}
TestRestController
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.orderhive.inventory.service.TestService;
#RestController
#RequestMapping("/test")
public class TestRestController {
#Autowired
private TestService testService;
#RequestMapping(method = RequestMethod.PUT, value = "/number/{id}")
void stockUpdate(#PathVariable Long id){
testService.testServiceMethod(id);
}
}
Output
**PUT: localhost:8080/test/number/1**
Here in DefaultXAndY
Here in DefaultX
Here in BaseX
Print list values
11
API call finished for id: 1
--------------------------------
**PUT: localhost:8080/test/number/2**
Here in DefaultXAndY
Here in DefaultX
Here in BaseX
Print list values
11
12
API call finished for id: 2
--------------------------------
List is holding value from previous request.
===========================================================
UPDATE
Following changes worked for me but is it the best practice?
removed #Component from BaseX
#Scope("request") added in DefaultX and DefaultXAndY
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) added in TestServiceImpl suggested by #anatoly-shamov
===========================================================
Solution:
removed #Component from BaseX
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) added in DefaultX and DefaultXAndY
BaseX is a bean of singleton scope (by default). It means there is only one instance of BaseX in Spring IoC container. All requests and references for that bean returns the same object.
You should abstract the listLong state with a separate bean of request scope. A new instance of this bean will be created for every HTTP request.
#Component
#Scope(value = "request", proxyMode= ScopedProxyMode.TARGET_CLASS)
public class ListX {
private List<Long> listLong = new ArrayList<Long>();
public List<Long> getListLong() {
return listLong;
}
public void setListLong(List<Long> listLong) {
this.listLong = listLong;
}
}
Use it in as listLong value holder in other components:
#Component
public abstract class BaseX implements InterfaceX{
#Autowired
ListX listHolder;
public void addToList(Long id){
System.out.println("Here in BaseX");
listHolder.getListLong().add(id);
}
#Override
public void publish() {
System.out.println("Print list values");
listHolder.getListLong().stream().forEach(System.out::println);
}
}
I'm developing a WebSocket server application using spring.
Class PlayerHandler
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
/**
* Created by kris on 11.07.16.
*/
public class PlayerHandler extends TextWebSocketHandler{
public PlayerHandler(){}
#Override
#AuthorizationRequired
public void handleTextMessage(WebSocketSession session, TextMessage tm) throws IOException {
session.sendMessage(tm);
}
}
I want user to be authorized with every incoming request by token, so I created a Aspect UserAuthorization
package com.berrigan.axevor.authorization;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class UserAuthorization {
#Around("#annotation(com.berrigan.axevor.authorization.AuthorizationRequired)")
public void authorize(ProceedingJoinPoint jp) throws Throwable{
System.out.println("\n\n\n\n\Works\n\n\n\n\n\n");
jp.proceed();
}
}
I added the #AuthorizationRequired annotation, which indicates methods in which users are going to be authorized. Unfortunately method authorize never get called. I've added following code to my main class to check if the bean get created.
UserAuthorization ua = ctx.getBean(UserAuthorization.class); // ApplicationContext
if(au == null) System.out.println("is null")
But I don't get such log.
My spring config
#EnableAutoConfiguration
#Configuration
#EnableAspectJAutoProxy
#Import({com.berrigan.axevor.websocket.WebSocketConfig.class})
#ComponentScan(basePackages = {"com.berrigan.axevor"})
public class Config {}
Annotation code:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface AuthorizationRequired{}
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry){
registry.addHandler(playerHandler(), "/game").setAllowedOrigins("*");
}
#Bean
public WebSocketHandler playerHandler(){
return new PlayerHandler();
}
}
Solution found, corrupted pom.xml file. After regenerating it, everything works like a charm
I'm trying to create a simple spring boot app with spring boot that "produce" messages to a rabbitmq exchange/queue and another sample spring boot app that "consume" these messages.
So I have two apps (or microservices if you wish).
1) "producer" microservice
2) "consumer" microservice
The "producer" has 2 domain objects. Foo and Bar which should be converted to json and send to rabbitmq.
The "consumer" should receive and convert the json message into a domain Foo and Bar respectively.
For some reason I can not make this simple task. There are not much examples about this.
For the message converter I want to use org.springframework.messaging.converter.MappingJackson2MessageConverter
Here is what I have so far:
PRODUCER MICROSERVICE
package demo.producer;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.stereotype.Service;
#SpringBootApplication
public class ProducerApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
#Bean
Queue queue() {
return new Queue("queue", false);
}
#Bean
TopicExchange exchange() {
return new TopicExchange("exchange");
}
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("queue");
}
#Bean
public MappingJackson2MessageConverter jackson2Converter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
return converter;
}
#Autowired
private Sender sender;
#Override
public void run(String... args) throws Exception {
sender.sendToRabbitmq(new Foo(), new Bar());
}
}
#Service
class Sender {
#Autowired
private RabbitMessagingTemplate rabbitMessagingTemplate;
#Autowired
private MappingJackson2MessageConverter mappingJackson2MessageConverter;
public void sendToRabbitmq(final Foo foo, final Bar bar) {
this.rabbitMessagingTemplate.setMessageConverter(this.mappingJackson2MessageConverter);
this.rabbitMessagingTemplate.convertAndSend("exchange", "queue", foo);
this.rabbitMessagingTemplate.convertAndSend("exchange", "queue", bar);
}
}
class Bar {
public int age = 33;
}
class Foo {
public String name = "gustavo";
}
CONSUMER MICROSERVICE
package demo.consumer;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Service;
#SpringBootApplication
#EnableRabbit
public class ConsumerApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
#Autowired
private Receiver receiver;
#Override
public void run(String... args) throws Exception {
}
}
#Service
class Receiver {
#RabbitListener(queues = "queue")
public void receiveMessage(Foo foo) {
System.out.println("Received <" + foo.name + ">");
}
#RabbitListener(queues = "queue")
public void receiveMessage(Bar bar) {
System.out.println("Received <" + bar.age + ">");
}
}
class Foo {
public String name;
}
class Bar {
public int age;
}
And here is the exception I'm getting:
org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener method could not be invoked with the incoming message
Endpoint handler details:
Method [public void demo.consumer.Receiver.receiveMessage(demo.consumer.Bar)]
Bean [demo.consumer.Receiver#1672fe87]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:116)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:93)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:756)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:679)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$001(SimpleMessageListenerContainer.java:83)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$1.invokeListener(SimpleMessageListenerContainer.java:170)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.invokeListener(SimpleMessageListenerContainer.java:1257)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:660)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:1021)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:1005)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$700(SimpleMessageListenerContainer.java:83)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1119)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.amqp.support.converter.MessageConversionException: Cannot handle message
... 13 common frames omitted
Caused by: org.springframework.messaging.converter.MessageConversionException: No converter found to convert to class demo.consumer.Bar, message=GenericMessage [payload=byte[10], headers={amqp_receivedRoutingKey=queue, amqp_receivedExchange=exchange, amqp_deliveryTag=1, amqp_deliveryMode=PERSISTENT, amqp_consumerQueue=queue, amqp_redelivered=false, id=87cf7e06-a78a-ddc1-71f5-c55066b46b11, amqp_consumerTag=amq.ctag-msWSwB4bYGWVO2diWSAHlw, contentType=application/json;charset=UTF-8, timestamp=1433989934574}]
at org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver.resolveArgument(PayloadArgumentResolver.java:115)
at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:77)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:127)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:100)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:113)
... 12 common frames omitted
The exception says there is no converter, and that is true, my problem is that I have no idea how to set the MappingJackson2MessageConverter converter in the consumer side (please note that I want to use org.springframework.messaging.converter.MappingJackson2MessageConverter and not org.springframework.amqp.support.converter.JsonMessageConverter)
Any thoughts ?
Just in case, you can fork this sample project at:
https://github.com/gustavoorsi/rabbitmq-consumer-receiver
Ok, I finally got this working.
Spring uses a PayloadArgumentResolver to extract, convert and set the converted message to the method parameter annotated with #RabbitListener. Somehow we need to set the mappingJackson2MessageConverter into this object.
So, in the CONSUMER app, we need to implement RabbitListenerConfigurer. By overriding configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) we can set a custom DefaultMessageHandlerMethodFactory, to this factory we set the message converter, and the factory will create our PayloadArgumentResolver with the the correct convert.
Here is a snippet of the code, I've also updated the git project.
ConsumerApplication.java
package demo.consumer;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.stereotype.Service;
#SpringBootApplication
#EnableRabbit
public class ConsumerApplication implements RabbitListenerConfigurer {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
#Bean
public MappingJackson2MessageConverter jackson2Converter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
return converter;
}
#Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(jackson2Converter());
return factory;
}
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
}
#Autowired
private Receiver receiver;
}
#Service
class Receiver {
#RabbitListener(queues = "queue")
public void receiveMessage(Foo foo) {
System.out.println("Received <" + foo.name + ">");
}
#RabbitListener(queues = "queue")
public void receiveMessage(Bar bar) {
System.out.println("Received <" + bar.age + ">");
}
}
class Foo {
public String name;
}
class Bar {
public int age;
}
So, if you run the Producer microservice it will add 2 messages in the queue. One that represent a Foo object and another that represent a Bar object.
By running the consumer microservice you will see that both are consumed by the respective method in the Receiver class.
Updated issue:
There is a conceptual problem about queuing from my side I think. What I wanted to achieved can not be possible by declaring 2 methods annotated with #RabbitListener that points to the same queue. The solution above was not working properly. If you send to rabbitmq, let say, 6 Foo messages and 3 Bar messages, they wont be received 6 times by the listener with Foo parameter. It seems that the listener are invoked in parallel so there is no way to discriminate which listener to invoke based on the method argument type.
My solution (and I'm not sure if this is the best way, I'm open to suggestions here) is to create a queue for each entity.
So now, I have queue.bar and queue.foo, and update #RabbitListener(queues = "queue.foo")
Once again, I've updated the code and you can check it out in my git repository.
Have not done this myself but it seems like you need to register the appropriate conversions by setting up a RabbitTemplate. Take a look at section 3.1.8 in this Spring documentation. I know it is configured using the AMQP classes but if the messaging class you are mentioning is compatible there is no reason you can't substitute it. Looks like this reference explains how you might do it using Java configuration rather than XML. I have not really used Rabbit so I don't have any personal experience but I would love to hear what you find out.