1. Overview
We saw in a previous article how Spring Remoting could be used to provide RPC on top of an asynchronous channel as an AMQP queue. However, we can obtain the same result using JMS too.
In this article, we’ll, in fact, explore how to set up remote invocation using Spring Remoting JMS and Apache ActiveMQ as a messaging middleware.
2. Starting an Apache ActiveMQ Broker
Apache ActiveMQ is an open source message broker that enables applications to exchange information asynchronously, and it is entirely compatible with the Java Message Service API.
To run our experiment, we firstly need to set up a running instance of ActiveMQ. We can choose among several ways: following the steps described in the official guide, embedding it in a Java application or more simply spinning up a Docker container with the following command:
docker run -p 61616:61616 -p 8161:8161 rmohr/activemq:5.14.3
This will start an ActiveMQ container that exposes on port 8081 a simple administration web GUI, through which we can check the available queues, connected clients, and other administrative information. JMS clients will need to use port 61616 to connect to the broker and exchange messages instead.
3. Maven Dependencies
As in the previous articles covering Spring Remoting, we are going to set up a server and a client Spring Boot applications to show how JMS Remoting works.
As usually we carefully choose the Spring Boot starter dependencies, as explained here:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency>
We explicitly excluded the spring-boot-starter-tomcat in order not to have the Tomcat related .jar files in the classpath.
This, in turn, will prevent the Spring Boot‘s autoconfiguration mechanism to launch an embedded web server when the application starts up since we don’t need it.
4. Server Application
4.1. Expose the Service
We’ll set up a server application that exposes the CabBookingService that clients will be able to invoke.
The first step is to declare a bean that implements the interface of the service we want to expose to the clients. This is the bean that will execute the business logic on the server:
@Bean CabBookingService bookingService() { return new CabBookingServiceImpl(); }
Let’s then define the queue from which the server will retrieve invocations, specifying its name in the constructor:
@Bean Queue queue() { return new ActiveMQQueue("remotingQueue"); }
As we already know from the previous articles, one of the main concepts of Spring Remoting is the Service Exporter, the component that collects the invocation requests from some source, in this case, an ApacheMQ queue ─ and invokes the desired method on the service implementation.
To work with JMS, we define a JmsInvokerServiceExporter:
@Bean JmsInvokerServiceExporter exporter(CabBookingService implementation) { JmsInvokerServiceExporter exporter = new JmsInvokerServiceExporter(); exporter.setServiceInterface(CabBookingService.class); exporter.setService(implementation); return exporter; }
Finally, we need to define a listener that has the responsibility to consume messages. The listener acts as a bridge between ApacheMQ and the JmsInvokerServiceExporter, it listens to invocation messages available on the queue, forwards the invocation to the service exporter and serializes back the results:
@Bean SimpleMessageListenerContainer listener( ConnectionFactory factory, JmsInvokerServiceExporter exporter) { SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setConnectionFactory(factory); container.setDestinationName("remotingQueue"); container.setConcurrentConsumers(1); container.setMessageListener(exporter); return container; }
4.2. Configuration
Let’s remember to set up the application.properties file to allow Spring Boot to configure some basic objects, as for instance the ConnectionFactory needed by the listener. The values of the various parameters mainly depend on the way
The values of the various parameters mainly depend on the way ApacheMQ has been installed, and the following one is a reasonable configuration for our Docker container running on the same machine where we’ll run these examples:
spring.activemq.broker-url=tcp://localhost:61616 spring.activemq.packages.trusted=org.springframework.remoting.support,java.lang,com.baeldung.api
The spring.activemq.broker-url parameter is a reference to the AMQ port. A deeper explanation is needed for spring.activemq.packages.trusted parameter instead. Since version 5.12.2 ActiveMQ refuses by default any message of type
Starting with version 5.12.2 ActiveMQ refuses by default any message of type ObjectMessage, used to exchange serialized Java object, because it is considered a potential vector for a security attack in some contexts.
Anyhow, it is possible to instruct AMQ to accept serialized objects in specified packages. org.springframework.remoting.support is the package that contains the main messages that represent the invocation of a remote method and its result. The package
The package com.baeldung.api contains the parameters and the results of our service. java.lang is added because the object that represents the result of the cab booking references a String, so we need to serialize that too.
5. Client Application
5.1. Invoke the Remote Service
Let’s tackle the client now. Again, we need to define the queue where invocation messages will be written to. We need to double-check that both client and server use the same name.
@Bean Queue queue() { return new ActiveMQQueue("remotingQueue"); }
We then need to set up an exporter:
@Bean FactoryBean invoker(ConnectionFactory factory, Queue queue) { JmsInvokerProxyFactoryBean factoryBean = new JmsInvokerProxyFactoryBean(); factoryBean.setConnectionFactory(factory); factoryBean.setServiceInterface(CabBookingService.class); factoryBean.setQueue(queue); return factoryBean; }
We can now use the remote service as if it was declared as a local bean:
CabBookingService service = context.getBean(CabBookingService.class); out.println(service.bookRide("13 Seagate Blvd, Key Largo, FL 33037"));
5.2. Run the Example
Also for the client application, we have to choose the values in the application properly.properties file. In a common setup, those would exactly match the ones used on the server side.
This should be enough to demonstrate the remote invocation through Apache AMQ. So, let’s first start ApacheMQ, then the server application, and finally the client application that will invoke the remote service.
6. Conclusion
In this quick tutorial, we saw how we could use Spring Remoting to provide RPC on top of a JMS system as AMQ.
Spring Remoting keeps on demonstrating how it is easy to quickly set up asynchronous call regardless of the underlying channel.
As usual, you’ll find the sources over on GitHub.