Declaration of exchanges and queues in Spring AMQP - spring

I'm using RabbitMQ and trying to refactor my current native java implementation to using the Spring AMQP abstraction.
Declaration of exchanges, queues and their binding using the Spring library is via the AMQPAdmin interface, but I'm not sure when this sort of configuration should happen.
I have a web application that uses Rabbit to produce messages. And another app that consumes these messages. Shocker :)
But when show the declaration of the exchanges/queues take place?
Do I deploy the AMQPAdmin with the web applications and do exchange/queue administration within constructors of producers and consumers?
Declaration of these things are a one off, the broke doesn't need to know about them again, so any code would be a NOOP on subsequent executions.
Do I create a separate application for administration of the broker?
What is the current thinking or best practices here?

It would appear that very few people are using Spring's AMQP M1 release, so I will answer my own question with what I've done.
In the producer's constructor I declare the exchange. Then set the exchange on the RabbitTemplate. I also set the routing key on the RabbitTemplate as the queue name, but that isn't required, but it was the route I would be using.
#Service("userService")
public class UserService {
private final RabbitTemplate rabbitTemplate;
#Autowired
public UserService(final RabbitAdmin rabbitAdmin,
final Exchange exchange,
final Queue queue,
#Qualifier("appRabbitTemplate") final RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
rabbitAdmin.declareExchange(exchange);
rabbitTemplate.setExchange(exchange.getName());
rabbitTemplate.setRoutingKey(queue.getName());
}
public void createAccount(final UserAccount userAccount) {
rabbitTemplate.convertAndSend("Hello message sent at " + new DateTime());
}
}
In the consumer's constructor I declare the queue and create the binding.
public class Consumer implements ChannelAwareMessageListener<Message> {
public Consumer(final RabbitAdmin rabbitAdmin, final Exchange exchange, final Queue queue) {
rabbitAdmin.declareQueue(queue);
rabbitAdmin.declareBinding(BindingBuilder.from(queue).to((DirectExchange) exchange).withQueueName());
}
#Override
public void onMessage(Message message, Channel channel) throws Exception {
System.out.println(new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
}
}
Although the constructors may be run many times, RabbitMQ only declares the exchange, queue and bindings once.
If you need the whole source for this little example project, ask, and I'll put it up somewhere for you.

Related

Spring 3 and Rabbit MQ integration (not Spring Boot)

I'm having difficulty getting a Spring 3 application to integrate with RabbitMQ, in order to receive messages from a queue (I do not need to send messages).
Part of the challenge is much of the documentation now relates to Spring Boot. The related Spring guide is helpful, but following the steps does not seem to work in my case. For instance, the guide includes the text:
The message listener container and receiver beans are all you need to listen for messages.
So I have setup the listener container and receiver beans with the following code.
Setting up message handler
#Component
public class CustomMessageHandler {
public void handleMessage(String text) {
System.out.println("Received: " + text);
}
}
Setting up configuration
#Configuration
public class RabbitConfig {
#Bean
public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory){
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setRoutingKey("queue-name");
return rabbitTemplate;
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("...host...");
connectionFactory.setPort(5671);
connectionFactory.setVirtualHost("...virtual host..");
connectionFactory.setUsername("...username...");
connectionFactory.setPassword("...password...");
return connectionFactory;
}
#Bean
public MessageListenerAdapter messageListenerAdapter(CustomMessageHandler messageHandler) {
return new MessageListenerAdapter(messageHandler, "handleMessage");
}
#Bean
public SimpleMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory,
MessageListenerAdapter messageListenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setQueueNames("queue-name");
container.setConnectionFactory(connectionFactory);
container.setMessageListener(messageListenerAdapter);
return container;
}
}
Unfortunately with this setup, the application will start up, but it never triggers the message handler. The queue it is trying to read from also has one message sitting in it, waiting to be consumed.
Any ideas on something that is missing, or appears misconfigured?
Thanks to some dependency management assistance from #GaryRussell, I was able to see that the version of spring-rabbit and spring-amqp were too recent. Using the older 1.3.9.RELEASE unfortunately proved to add additional challenges.
Some other assistance came in the form of using an actual RabbitMQ Java client. This option was much simpler to implement, and avoided the dependency problems. Ultimately I needed to include the following dependency:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.3</version>
</dependency>
And then I simply followed their documentation on creating a connection, and consuming messages.
Voila, it works!

JMS with spring boot, sender and receiver on same package: what is its use?

I am learning JMS with spring boot and nice to know that spring boot comes with embed Active MQ JMS broker.
I started from spring page on how to achieve this and it works like charm. Now i went little further and create two separate spring boot application one containing jms sender code and another containing receiver code.
I tried starting and application failed as both application are using same port for JMS. I fixed this by including this on one application
#Bean
public BrokerService broker() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:61616");
broker.addConnector("vm://localhost");
broker.setPersistent(false);
return broker;
}
But now sender is sending message successfully but receiver is doing nothing. I search on stackoverflow and look at this and this. And they are saying:
If you want to use JMS in production, it would be much wiser to avoid using Spring Boot embedded JMS brokers and host it separately. So 3 node setup would be preferred for PROD.
So my questions are:
1. What is the purpose of putting both jms sender and receiver on same application? Is there any practical example
2. Is it really not possible to use spring boot embedded JMS to communicate two separate application.
You might have sender and receiver in the same application if requests arrive in bursts and you want to save them somewhere before they are processed, in case of a server crash. You typically still wouldn't use an embedded broker for that.
Embedded brokers are usually used for testing only.
You can, however, run an embedded broker that is accessible externally; simply fire up a BrokerService as you have, but the other app needs to connect with the tcp://... address, not the vm://....
EDIT
App1:
#SpringBootApplication
#RestController
public class So52654109Application {
public static void main(String[] args) {
SpringApplication.run(So52654109Application.class, args);
}
#Bean
public BrokerService broker() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:61616");
broker.setPersistent(false);
broker.start();
return broker;
}
#Autowired
private JmsTemplate template;
#RequestMapping(path = "/foo/{id}")
public String foo(#PathVariable String id) {
template.convertAndSend("someQueue", id);
return id + ": thank you for your request, we'll send an email to the address on file when complete";
}
}
App2:
application.properties
spring.activemq.broker-url=tcp://localhost:61616
and
#SpringBootApplication
public class So526541091Application {
public static void main(String[] args) {
SpringApplication.run(So526541091Application.class, args);
}
#JmsListener(destination = "someQueue")
public void process(String id) {
System.out.println("Processing request for id");
}
}
Clearly, for a simple app like this you might just run the listener in the first app.
However, since there is no persistence of messages with this configuration, you would likely use an external broker for a production app (or enable persistence).

Spring integartion LoggingHandler logs all messages to Error

I created a spring boot application that sends Messages through a PublishSubscribeChannel. This Channel is "autowired" as SubscribableChannel interface.
I am only subscribing one MessageHandler to this channel, a KafkaProducerMessageHandler.
My problem is that one additional MessageHandler is subscribed and this is an LoggingHandler. It is instantiated with ERROR level. So i see every message logged es error.
I want to know why and where this LoggingHandler is wired (instantiated) and why it is subscribed to the channel - i want to disable it.
(
I debugged around a bit but (was not really helpful):
The LoggingHandler is instantiated and subscribed after the KafkaHandler.
I see this chain EventdrivenConsumer.doStart()<-- ``ConsumerEndpointFactoryBean.initializeEndpoint()<-- ... until reflective calls
)
EDIT
As suggested in comments here is some code (i can't share the whole project). My problem is that the code can't explain the behavior. The LoggingHandler is beeing subscribed to my PublishSubscribeChannel for some unknown reason and it is instantiated with error as level for some unknown reason.
The class that subscribes the KafkaHandler:
#Component
public class EventRelay {
#Autowired
private EventRelay( SubscribableChannel eventBus, #Qualifier( KafkaProducerConfig.KAFKA_PRODUCER ) MessageHandler kafka ) {
eventBus.subscribe( kafka );
}
}
The class that send events is implementing an proprietary interface with many callback methods:
public class PropEvents implements PropClass.IEvents {
private SubscribableChannel eventBus;
private final ObjectMapper om;
private final String userId;
public PropEvents( SubscribableChannel eventBus, ObjectMapper om, String userId ) {
this.eventBus = eventBus;
this.om = om;
this.userId = userId;
}
#Override
public void onLogin( ) {
eventBus.send( new OnLoginMessage(... ) ) );
}
//many other onXYZ methods
}
Here is the Factory that produces instances of PropEvents:
#Configuration
public class EventHandlerFactory {
private final ObjectMapper om;
private final SubscribableChannel eventBus;
#Autowired
public EventHandlerFactory( ObjectMapper om, SubscribableChannel eventBus){
this.om = checkNotNull( om );
this.eventBus = checkNotNull( eventBus );
}
#Bean
#Scope( SCOPE_PROTOTYPE)
public IEvents getEvantHandler(String userId){
if(Strings.isNullOrEmpty(userId)){
throw new IllegalArgumentException( "user id must be set." );
}
return new PropEvents(eventBus, om, userId);
}
}
I appreciate any help with debugging or use tooling (e.g. Eclipse Spring tools does not show any hint to a LoggingHandler Bean) to find where and why a LoggingHandler is instantiated and subscribed to my autowired Channel.
My current workaround is to disable logging for LoggingHandler.
My question at a glance
Why spring instantiates an LoggingHandler with error level and subscribes it to my SubscribableChannel(provided by PublishSubscribeChannel)? How to disable this?
When you #Autowired SubscribableChannel, there should be one in the application context. That might be confusing a bit and mislead, but Spring Integration provides a PublishSubscribeChannel for the global errorChannel: https://docs.spring.io/spring-integration/docs/5.0.2.RELEASE/reference/html/messaging-channels-section.html#channel-special-channels
This one has a LoggingHandler to log error as a default subscriber.
I don't think that it is OK to make your logic based on the errorChannel.
You should consider to declare your own MessageChannel bean and inject it by the particular #Qualifier.

Which one to use RabbitTemplate or AmqpTemplate?

I have the following program written in Spring Boot which is working fine. However, the problem is that the I am not sure whether I should be using RabbitTemplate or AmqpTemplate. Some of the online examples/tutorials use RabbitTemplate while others use AmqpTemplate.
Please guide as to what is the best practice and which one should be used.
#SpringBootApplication
public class BasicApplication {
private static RabbitTemplate rabbitTemplate;
private static final String QUEUE_NAME = "helloworld.q";
//this definition of Queue is required in RabbitMQ. Not required in ActiveMQ
#Bean
public Queue queue() {
return new Queue(QUEUE_NAME, false);
}
public static void main(String[] args) {
try (ConfigurableApplicationContext ctx = SpringApplication.run(BasicApplication.class, args)) {
rabbitTemplate = ctx.getBean(RabbitTemplate.class);
rabbitTemplate.convertAndSend(QUEUE_NAME, "Hello World !");
}
}
}
In most cases, for Spring beans, I would advise using the interface, in case Spring creates a JDK proxy for any reason. That would be unusual for the RabbitTemplate so it doesn't really matter which you use.
In some cases, you might need methods on the RabbitTemplate that don't appear on the interface; which would be another case where you would need to use it.
In general, though, best practice is for user code to use interfaces so you don't have hard dependencies on implementations.
AmqpTemplate is an interface. RabbitTemplate is an implementation of the AmqpTemplate interface. You should use RabbitTemplate.

Spring 4.1 #JmsListener configuration

I would like to use the new annotations and features provided in Spring 4.1 for an application that needs a JMS listener.
I've carefully read the notes in the Spring 4.1 JMS improvements post but I continue to miss the relationship between #JmsListener and maybe the DestinationResolver and how I would setup the application to indicate the proper Destination or Endpoint.
Here is the suggested use of #JmsListener
#Component
public class MyService {
#JmsListener(containerFactory = "myContainerFactory", destination = "myQueue")
public void processOrder(String data) { ... }
}
Now, I can't use this in my actual code because the "myQueue" needs to be read from a configuration file using Environment.getProperty().
I can setup an appropriate myContainerFactory with a DestinationResolver but mostly, it seems you would just use DynamicDestinationResolver if you don't need JNDI to lookup a queue in an app server and didn't need to do some custom reply logic. I'm simply trying to understand how Spring wants me to indicate the name of the queue in a parameterized fashion using the #JmsListener annotation.
Further down the blog post, I find a reference to this Configurer:
#Configuration
#EnableJms
public class AppConfig implements JmsListenerConfigurer {
#Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setDefaultContainerFactory(defaultContainerFactory());
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setDestination("anotherQueue");
endpoint.setMessageListener(message -> {
// processing
});
registrar.registerEndpoint(endpoint);
}
Now, this makes some amount of sense and I could see where this would allow me to set a Destination at runtime from some external string, but this seems to be in conflict with using #JmsListener as it appears to be overriding the annotation in favor of endpoint.setMessageListener in the code above.
Any tips on how to specify the appropriate queue name using #JmsListener?
Also note that depending on use case you can already parameterize using properties file per environment and PropertySourcesPlaceholderConfigurer
#JmsListener(destinations = "${some.key}")
As per https://jira.spring.io/browse/SPR-12289
In case people are using #JmsListener with spring boot, you do not have to configure PropertySourcesPlaceholderConfigurer. It work's out the box
Sample:
class
#JmsListener(destination = "${spring.activemq.queue.name}")
public void receiveEntityMessage(final TextMessage message) {
// process stuff
}
}
application.properties
spring.activemq.queue.name=some.weird.queue.name.that.does.not.exist
Spring boot output
[26-Aug;15:07:53.475]-[INFO ]-[,]-[DefaultMes]-[o.s.j.l.DefaultMessageListenerContainer ]-[931 ]-Successfully refreshed JMS Connection
[26-Aug;15:07:58.589]-[WARN ]-[,]-[DefaultMes]-[o.s.j.l.DefaultMessageListenerContainer ]-[880 ]-Setup of JMS message listener invoker failed for destination 'some.weird.queue.name.that.does.not.exist' - trying to recover. Cause: User user is not authorized to read from some.weird.queue.name.that.does.not.exist
[26-Aug;15:07:59.787]-[INFO ]-[,]-[DefaultMes]-[o.s.j.l.DefaultMessageListenerContainer ]-[931 ]-Successfully refreshed JMS Connection
[26-Aug;15:08:04.881]-[WARN ]-[,]-[DefaultMes]-[o.s.j.l.DefaultMessageListenerContainer ]-[880 ]-Setup of JMS message listener invoker failed for destination 'some.weird.queue.name.that.does.not.exist' - trying to recover. Cause: User user is not authorized to read from some.weird.queue.name.that.does.not.exist
This proves that #JmsListener is able to pickup property values from application.properties without actually setting up any explicit PropertySourcesPlaceholderConfigurer
I hope this helps!
You could eventually do that right now but it's a bit convoluted. You can set a custom JmsListenerEndpointRegistry using JmsListenerConfigurer
#Configuration
#EnableJms
public class AppConfig implements JmsListenerConfigurer {
#Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setEndpointRegistry(customRegistry());
}
}
and then override the registerListenerContainer method, something like
public void registerListenerContainer(JmsListenerEndpoint endpoint, JmsListenerContainerFactory<?> factory) {
// resolve destination according to whatever -> resolvedDestination
((AbstractJmsListenerEndpoint)endpoint).setDestination(resolvedDestination);
super.registerListenerContainer(endpoint, factory);
}
But we could do better. Please watch/vote for SPR-12280

Resources