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

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).

Related

Spring Boot web socket has issues while sending messages for the first time

I am trying to create a chat application with spring boot web socket. The implementation is completed with spring boot and my Angular 7 app is connecting to this. The issue I face is, when i connect to the socket for the very first time after server reboot or so, the first 5-6 messages to the socket are not sent. From then on it works flawlessly and super fast. What is it that I am missing?
I am implementing WebSocketConfigurer and trying to use registerWebSocketHandlers to connect to /socket// where i pass my user id and conversation id to initiate the chat similar to /socket/1/2.
#Configuration
#EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {
Logger logger = LogManager.getLogger(WebSocketConfiguration.class);
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new
ServletServerContainerFactoryBean();
container.setMaxBinaryMessageBufferSize(1024000);
return container;
}
#Bean
public SessionHandler sessionHandler() {
return new SessionHandler();
}
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry
registry){
registry.addHandler(sessionHandler(),
"/socket/*/*").setAllowedOrigins("*");
}
}
Expect the system to transport messages flawlessly from message 1. But it is perfect after the first few messages.

Spring Cloud Stream topic per message for different consumers

The topology I am looking for is
So far I have not seen a way to define the topic per message in Cloud Stream. I understand that the consumers will be bound to specific topic but how does the producer sets the topic per message before sending the message to the exchange?
source.output().send(MessageBuilder.withPayload(myMessage).build());
Does not provide any way to set the topic for the exchange to route to the proper consumer.
Or maybe I don't understand something correctly?
UPDATE
I would expect not to receive the message in the consumer due to the bindingRoutingKey being 2222 and I am sending with routeTo 1111. But I still receive it on the consumer.
Producer Properties:
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.cloud.stream.bindings.output.content-type=application/json
spring.cloud.stream.bindings.output.destination=messageExchange
spring.cloud.stream.rabbit.bindings.output.producer.routing-key-expression=headers['routeTo']
#EnableBinding(Source.class)
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Sender:
source.output().send(MessageBuilder.withPayload(mo).setHeader("routeTo", "1111").build());
And the Consumer:
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.cloud.stream.bindings.input.destination=messageExchange
spring.cloud.stream.rabbit.bindings.input.consumer.bindingRoutingKey=2222
Application:
#SpringBootApplication
#EnableBinding(Sink.class)
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#StreamListener(Sink.INPUT)
public void ReceiveMo(String moDTO) {
log.info("Message received moDTO: {}", moDTO);
}
}
SECOND UPDATE
With the suggestions in the accepted answer below. I was able to make it work. Needed to remove the exchanges and queues from RabbitMQ using its UI and restart the RabbitMQ docker image.
The the routingKeyExpression rabbitmq producer property.
e.g. ...producer.routing-key-expression=headers['routeTo']
then
source.output().send(MessageBuilder.withPayload(myMessage)
.setHeader("routeTo", "Booking.new")
.build());
Note that the destination is the exchange name. By default, the binder expects a Topic exchange. If you wish to use a Direct exchange instead, you must set the exchangeType property.

Activemq web console in Spring

I am creating an embedded ActiveMQ broker in Spring application like this:
#EnableJms
#Configuration
public class MqConfig {
#Bean
public BrokerService broker() throws Exception {
BrokerService broker = new BrokerService();
broker.setBrokerName("ETL");
broker.addConnector("tcp://localhost:61616");
broker.start();
return broker;
}
}
How can I embed the ActiveMQ Admin Web console also in the same spring application? Searched the net for 2-3 hours, but found no useful answer. The only thing that ActiveMQ site mentions is this http://activemq.apache.org/web-console.html, however is not useful with Spring

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

Declaration of exchanges and queues in Spring AMQP

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.

Resources