I have configured my rabbit properties via application.yaml and spring configurationProperties.
Thus, when I configure exchanges, queues and bindings, I can use the getters of my properties
#Bean Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(properties.getQueue());
}
#Bean Queue queue() {
return new Queue(properties.getQueue(), true);
}
#Bean TopicExchange exchange() {
return new TopicExchange(properties.getExchange());
}
However, when I configure a #RabbitListener to log the messages on from the queue, I have to use the full properties name like
#RabbitListener(queues = "${some.long.path.to.the.queue.name}")
public void onMessage(
final Message message, final Channel channel) throws Exception {
log.info("receiving message: {}#{}", message, channel);
}
I want to avoid this error prone hard coded String and refer to the configurationProperties bean like:
#RabbitListener(queues = "${properties.getQueue()}")
I had a similar issue once with #EventListener where using a bean reference "#bean.method()" helped, but it does not work here, the bean expression is just interpreted as queue name, which fails because a queue namde "#bean...." does not exist.
Is it possible to use ConfigurationProperty-Beans for RabbitListener queue configuration?
Something like this worked for me where I just used the Bean and SpEL.
#Autowired
Queue queue;
#RabbitListener(queues = "#{queue.getName()}")
I was finally able to accomplish what we both desired to do by taking what #David Diehl suggested, using the bean and SpEL; however, using MyRabbitProperties itself instead. I removed the #EnableConfigurationProperties(MyRabbitProperties.class) in the config class, and registered the bean the standard way:
#Configuration
//#EnableConfigurationProperties(RabbitProperties.class)
#EnableRabbit
public class RabbitConfig {
//private final MyRabbitProperties myRabbitProperties;
//#Autowired
//public RabbitConfig(MyRabbitProperties myRabbitProperties) {
//this.myRabbitProperties = myRabbitProperties;
//}
#Bean
public TopicExchange myExchange(MyRabbitProperties myRabbitProperties) {
return new TopicExchange(myRabbitProperties.getExchange());
}
#Bean
public Queue myQueueBean(MyRabbitProperties myRabbitProperties) {
return new Queue(myRabbitProperties.getQueue(), true);
}
#Bean
public Binding binding(Queue myQueueBean, TopicExchange myExchange, MyRabbitProperties myRabbitProperties) {
return BindingBuilder.bind(myQueueBean).to(myExchange).with(myRabbitProperties.getRoutingKey());
}
#Bean
public MyRabbitProperties myRabbitProperties() {
return new MyRabbitProperties();
}
}
From there, you can access the get method for that field:
#Component
public class RabbitQueueListenerClass {
#RabbitListener(queues = "#{myRabbitProperties.getQueue()}")
public void processMessage(Message message) {
}
}
#RabbitListener(queues = "#{myQueue.name}")
Listener:
#RabbitListener(queues = "${queueName}")
application.properties:
queueName=myQueue
Related
I am fairly new to spring and spring integration. What I'm trying to do: publish mqtt messages using spring integration.
Here is the code:
#Configuration
#IntegrationComponentScan
#Service
public class MQTTPublishAdapter {
private MqttConfiguration mqttConfiguration;
public MQTTPublishAdapter(MqttConfiguration mqttConfiguration) {
this.mqttConfiguration = mqttConfiguration;
}
#Bean
public MessageChannel mqttOutboundChannel() {
return new PublishSubscribeChannel();
}
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new
DefaultMqttPahoClientFactory();
//... set factory details
return factory;
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MQTTCustomMessageHandler mqttOutbound() {
String clientId = UUID.randomUUID().toString();
MQTTCustomMessageHandler messageHandler =
new MQTTCustomMessageHandler(clientId, mqttClientFactory());
//...set messagehandler details
return messageHandler;
}
//I extend this only because the publish method is protected and I want to
send messages to different topics
public class MQTTCustomMessageHandler extends MqttPahoMessageHandler {
//default constructors
public void sendMessage(String topic, String message){
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(message.getBytes());
try {
super.publish(topic, mqttMessage, null);
} catch (Exception e) {
log.error("Failure to publish message on topic " + topic,
e.getMessage());
}
}
}
This is the clase where I am trying to inject the Handler
#Service
public class MQTTMessagePublisher {
private MQTTCustomMessageHandler mqttCustomMessageHandler;
public MQTTMessagePublisher(#Lazy MQTTCustomMessageHandler
mqttCustomMessageHandler) {
this.mqttCustomMessageHandler = mqttCustomMessageHandler;
}
public void publishMessage(String topic, String message) {
mqttCustomMessageHandler.sendMessage(topic, message);
}
}
So my question is about how should I inject the bean I am trying to use because if I remove the #Lazy annotation it says that "Requested bean is currently in creation: Is there an unresolvable circular reference?". I do not have any circular dependencies as in the bean I only set some strings, so I'm guessing that I don't really understand how this should work.
Very sorry about the formating, it's one of my first questions around here.
Edit:
If I remove
#ServiceActivator(inputChannel = "mqttOutboundChannel")
and add
messageHandler.setChannelResolver((name) -> mqttOutboundChannel());
it works. I'm still unclear why the code crashes.
You show a lot of custom code, but not all of them.
It's really hard to answer to questions where it is only a custom code. Would be great to share as much info as possible. For example an external project on GitHub to let us to play and reproduce would be fully helpful and would save some time.
Nevertheless, I wonder what is your MQTTCustomMessageHandler. However I guess it is not a MessageHandler implementation. From here the #ServiceActivator annotation is not going to work properly since it is applied really for the mqttOutbound(), not whatever you expect. Or you need to move this annotation to your sendMessage() method in the MQTTCustomMessageHandler or have it as a MessageHandler.
On the other hand it is not clear why do you need that #ServiceActivator annotation at all since you call that method manually from the MQTTMessagePublisher.
Also it is not clear why you have so much custom code when Framework provides for your out-of-the-box channel adapter implementations.
Too many questions to your code, than possible answer...
See more info in the reference manual:
https://docs.spring.io/spring-integration/docs/current/reference/html/#annotations
https://docs.spring.io/spring-integration/docs/current/reference/html/#mqtt
I have a service that reads all messages from AWS SQS.
#Slf4j
#Configuration
#EnableJms
public class JmsConfig {
private SQSConnectionFactory connectionFactory;
public JmsConfig(
#Value("${amazon.sqs.accessKey}") String awsAccessKey,
#Value("${amazon.sqs.secretKey}") String awsSecretKey,
#Value("${amazon.sqs.region}") String awsRegion,
#Value("${amazon.sqs.endpoint}") String awsEndpoint) {
connectionFactory = new SQSConnectionFactory(
new ProviderConfiguration(),
AmazonSQSClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(
new BasicAWSCredentials(awsAccessKey, awsSecretKey)))
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(awsEndpoint, awsRegion))
.build());
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(this.connectionFactory);
factory.setDestinationResolver(new DynamicDestinationResolver());
factory.setConcurrency("3-10");
factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
factory.setReceiveTimeout(2000L); //??????????
return factory;
}
#Bean
public JmsTemplate defaultJmsTemplate() {
return new JmsTemplate(this.connectionFactory);
}
I've heard about long polling so I wonder how I could use it in my case. I wonder how this listener works - I do not want to create unnecessary calls to the AWS SQS.
My listener that reads messages and converts them to the Object and saves on Redis db:
#JmsListener(destination = "${amazon.sqs.destination}")
public void receive(String requestJSON) throws JMSException {
log.info("Received");
try {
Trace trace = Trace.fromJSON(requestJSON);
traceRepository.save(trace);
(...)
I'd like to know your opinions - what is the best approach to minimalize unnecessary calls to SQS to get messages.
Maybe shoud I use for example
factory.setReceiveTimeout(2000L);
Unfortunately there is too little information in Internet about it
Thanks,
Matthew
I have a Spring Boot project that I'm using to receive events from an Amazon SQS queue. I've been using the Spring Cloud AWS project to make this easier.
The problem is this: the Spring Boot application starts up just fine, and appears to instantiate all the necessary beans just fine. However, when the method that is annotated with SqsListener is invoked, all the event handler's dependent beans are null.
Another thing that's important to note: I have two methods of propagating the event: 1) thru a POST web service call, and 2) thru the Amazon SQS. If I choose to run the event as a POST call with the same data in the POST body, it works just fine. The injected dependencies are only ever null whenever the SQSListener method is invoked by the SimpleMessageListenerContainer.
Classes:
#Service("systemEventsHandler")
public class SystemEventsHandler {
// A service that this handler depends on
private CustomService customService;
private ObjectMapper objectMapper;
#Autowired
public SystemEventsHandler(CustomService customService, ObjectMapper objectMapper) {
this.matchStatusSvc = matchStatusSvc;
this.objectMapper = objectMapper;
}
public void handleEventFromHttpCall(CustomEventObject event) {
// Whenever this method is called, the customService is
// present and the method call completes just fine.
Assert.notNull(objectMapper, "The objectMapper that was injected was null");
customService.handleEvent(event);
}
#SqsListener(value = "sqsName", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
private void handleEventFromSQSQueue(#NotificationMessage String body) throws IOException {
// Whenever this method is called, both the objectMapper and
// the customService are null, causing the invocation to
// fail with a NullPointerException
CustomEventObject event = objectMapper.readValue(body, CustomEventObject.class);
matchStatusSvc.scoresheetUploaded(matchId);
}
}
The controller (for when I choose to run the event as a POST). As stated above, it works just fine whenever I run it as a POST call.
#RestController
#RequestMapping("/events")
public class SystemEventsController {
private final SystemEventsHandler sysEventSvc;
#Autowired
public SystemEventsController(SystemEventsHandler sysEventSvc) {
this.sysEventSvc = sysEventSvc;
}
#RequestMapping(value = "", method = RequestMethod.POST)
public void handleCustomEvent(#RequestBody CustomEventObject event) {
sysEventSvc.handleEventFromHttpCall(event);
}
}
Pertinent config:
#Configuration
public class AWSSQSConfig {
#Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(AmazonSQSAsync amazonSQS) {
SimpleMessageListenerContainer msgListenerContainer = simpleMessageListenerContainerFactory(amazonSQS).createSimpleMessageListenerContainer();
msgListenerContainer.setMessageHandler(queueMessageHandler(amazonSQS));
return msgListenerContainer;
}
#Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory(AmazonSQSAsync amazonSQS) {
SimpleMessageListenerContainerFactory msgListenerContainerFactory = new SimpleMessageListenerContainerFactory();
msgListenerContainerFactory.setAmazonSqs(amazonSQS);
msgListenerContainerFactory.setMaxNumberOfMessages(10);
msgListenerContainerFactory.setWaitTimeOut(1);
return msgListenerContainerFactory;
}
#Bean
public QueueMessageHandler queueMessageHandler(AmazonSQSAsync amazonSQS) {
QueueMessageHandlerFactory queueMsgHandlerFactory = new QueueMessageHandlerFactory();
queueMsgHandlerFactory.setAmazonSqs(amazonSQS);
QueueMessageHandler queueMessageHandler = queueMsgHandlerFactory.createQueueMessageHandler();
return queueMessageHandler;
}
#Bean(name = "amazonSQS", destroyMethod = "shutdown")
public AmazonSQSAsync amazonSQSClient() {
AmazonSQSAsyncClient awsSQSAsyncClient = new AmazonSQSAsyncClient(new DefaultAWSCredentialsProviderChain());
return awsSQSAsyncClient;
}
}
Other info:
Spring boot version: Dalston.RELEASE
Spring cloud AWS version:
1.2.1.RELEASE
Both the spring-cloud-aws-autoconfigure and spring-cloud-aws-messaging packages are on the classpath
Any thoughts?
As spencergibb suggested in his comment above, changing the method's visibility from private to public worked.
From spring boot tutorial:
https://spring.io/guides/gs/messaging-rabbitmq/
They give an example of creating 1 queue and 1 queue only, but, what if I want to be able to create more then 1 queue? how would it be possible?
Obviously, I can't just create the same bean twice:
#Bean
Queue queue() {
return new Queue(queueNameAAA, false);
}
#Bean
Queue queue() {
return new Queue(queueNameBBB, false);
}
You can't create the same bean twice, it will make ambiguous.
Give the bean definition factory methods different names. Usually, by convention, you would name them the same as the queue, but that's not required...
#Bean
Queue queue1() {
return new Queue(queueNameAAA, false);
}
#Bean
Queue queue2() {
return new Queue(queueNameBBB, false);
}
The method name is the bean name.
EDIT
When using the queues in the binding beans, there are two options:
#Bean
Binding binding1(#Qualifier("queue1") Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(queueNameAAA);
}
#Bean
Binding binding2(#Qualifier("queue2") Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(queueNameBBB);
}
or
#Bean
Binding binding1(TopicExchange exchange) {
return BindingBuilder.bind(queue1()).to(exchange).with(queueNameAAA);
}
#Bean
Binding binding2(TopicExchange exchange) {
return BindingBuilder.bind(queue2()).to(exchange).with(queueNameBBB);
}
or even better...
#Bean
Binding binding1(TopicExchange exchange) {
return BindingBuilder.bind(queue1()).to(exchange).with(queue1().getName());
}
#Bean
Binding binding2(TopicExchange exchange) {
return BindingBuilder.bind(queue2()).to(exchange).with(queue2().getName());
}
I have an application that publishes a message using Spring AMQP’s RabbitTemplate and subscribes to the message on a POJO using MessageListenerAdapter, pretty much as per the Getting Started - Messaging with RabbitMQ guide.
void handleMessage(final String message) {...}
rabbitTemplate.convertAndSend(EXCHANGE_NAME, QUEUE_NAME, "message");
However, this is sending messages as String's; surely there is a way to send and receive Object messages directly?
I have tried registering a JsonMessageConverter but to no avail.
Any help would be greatly appreciated - at present I'm manually de/serialising Strings on either side which seems messy and I'm surprised this isn't a supported feature.
I have tried registering a JsonMessageConverter but to no avail.
It would be better to see your attempt and figure out the issue on our side.
Right now I only can say that you should supply JsonMessageConverter for both sending and receiving parts.
I've just tested with the gs-messaging-rabbitmq:
#Autowired
RabbitTemplate rabbitTemplate;
#Autowired
MessageConverter messageConverter;
.....
#Bean
MessageListenerAdapter listenerAdapter(Receiver receiver, MessageConverter messageConverter) {
MessageListenerAdapter adapter = new MessageListenerAdapter(receiver, "receiveMessage");
adapter.setMessageConverter(messageConverter);
return adapter;
}
#Bean
MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
.....
System.out.println("Sending message...");
rabbitTemplate.setMessageConverter(messageConverter);
rabbitTemplate.convertAndSend(queueName, new Foo("Hello from RabbitMQ!"));
Where Receiver has been changed to this:
public void receiveMessage(Foo message) {
System.out.println("Received <" + message + ">");
latch.countDown();
}
So, the output is:
Waiting five seconds...
Sending message...
Received <Foo{foo='Hello from RabbitMQ!'}>
when we use Foo like this:
#Override
public String toString() {
return "Foo{" +
"foo='" + foo + '\'' +
'}';
}
with appropriate getter and setter.
Thanks #Artem for the hints. My code is pretty much as per the Getting Started Guide and I had already tried adding a Converter.
But as #Artem has pointed out, the trick is to register the converter with both the container, the listener adapter, and the rabbit template (which was auto-configured in the example).
So my #Configuration class now looks like so, in addition to whatever is mentioned in the Getting Started Guide:
#Bean
SimpleMessageListenerContainer container(final ConnectionFactory connectionFactory, final MessageListenerAdapter messageListenerAdapter,
final MessageConverter messageConverter)
{
final SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(QUEUE_NAME);
container.setMessageListener(messageListenerAdapter);
container.setMessageConverter(messageConverter);
return container;
}
#Bean
RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory, final MessageConverter messageConverter)
{
final RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
#Bean
MessageConverter messageConverter()
{
return new Jackson2JsonMessageConverter();
}
#Bean
Receiver receiver()
{
return new Receiver();
}
#Bean
MessageListenerAdapter listenerAdapter(final Receiver receiver, final MessageConverter messageConverter)
{
return new MessageListenerAdapter(receiver, messageConverter);
}
which means the Receiver can have an Object method signature such as:
void handleMessage(final CbeEvent message)