Tutorial on Spring Remoting with JMS
...
This tutorial was kindly donated to Apache Camel by Martin Gilday
...
.
Preface
This tutorial aims to guide the reader through the stages of creating a project which uses Camel to facilitate the routing of messages from a JMS queue to a Spring service. The route works in a synchronous fashion returning a response to the client.
Table of Contents |
---|
TODOs
- Embellish the turorial with more detail, explaining at each stage what is happening.
- Link to specific sections of Camel documentation when referring to components.
- Attach completed example project.
- Explain in more detail what is happening when the JMS component is being defined in camel-server.xml
- Show how logging can be introduced to monitor exchange body contents.
- Detail how time-outs can be configured.
- Show how to catch lost messages.
- Show how to use a wiretap.
- Can we make this ActiveMQ embedded so that it does not require the reader to download and start it manually?
Prerequisites
This tutorial uses ActiveMQ as the JMS broker. Download and extract it, then start the broker using script in the bin directory.
It is assumed that the reader is familiar with Spring (including v2.5 features) and Maven.
Create the Camel Project
Info |
---|
For the purposes of the tutorial a single Maven project will be used for both the client and server. Ideally you would break your application down into the appropriate components. |
Code Block |
---|
mvn archetype:create -DgroupId=org.example -DartifactId=CamelWithJmsAndSpring
|
Update the POM with Dependencies
...
Prerequisites
This tutorial uses Maven to setup the Camel project and for dependencies for artifacts.
Distribution
This sample is distributed with the Camel distribution as examples/camel-example-spring-jms
.
About
This tutorial is a simple example that demonstrates more the fact how well Camel is seamless integrated with Spring to leverage the best of both worlds. This sample is client server solution using JMS messaging as the transport. The sample has two flavors of servers and also for clients demonstrating different techniques for easy communication.
The Server is a JMS message broker that routes incoming messages to a business service that does computations on the received message and returns a response.
The EIP patterns used in this sample are:
Pattern | Description |
---|---|
We need a channel so the Clients can communicate with the server. | |
The information is exchanged using the Camel Message interface. | |
This is where Camel shines as the message exchange between the Server and the Clients are text based strings with numbers. However our business service uses int for numbers. So Camel can do the message translation automatically. | |
It should be easy to send messages to the Server from the the clients. This is achieved with Camel's powerful Endpoint pattern that even can be more powerful combined with Spring remoting. The tutorial has clients using each kind of technique for this. | |
The client and server exchange data using point to point using a JMS queue. | |
The JMS broker is event driven and is invoked when the client sends a message to the server. |
We use the following Camel components:
Component | Description |
---|---|
We use Apache ActiveMQ as the JMS broker on the Server side | |
We use the bean binding to easily route the messages to our business service. This is a very powerful component in Camel. | |
In the AOP enabled Server we store audit trails as files. | |
Used for the JMS messaging |
Create the Camel Project
...
For the purposes of the tutorial a single Maven project will be used for both the client and server. Ideally you would break your application down into the appropriate components.
...
Update the POM with Dependencies
First we need to have dependencies for the core Camel jars, spring, jms components, and finally ActiveMQ as the message broker.
...
Writing the Server
Create the Spring Service
...
For this example the Spring service (our business service) on the server will be a simple multiplier which trebles in the received value. The classes should reside in the package org.example.server
.
...
...
public interface Multiplier {
/**
* Multiplies the given number by a pre-defined constant.
*
* @param originalNumber The number to be multiplied
* @return The result of the multiplication
*/
int multiply(int originalNumber);
}
Define the Camel Routes
Configure Spring
The Spring config file is placed under META-INF/spring
as this is the default location used by the Camel Maven Plugin, which we will later use to run our server.
First we need to do the standard scheme declarations in the top. In the camel-server.xml we are using spring beans as the default bean: namespace and springs context:. For configuring ActiveMQ we use broker: and for Camel we of course have camel:. Notice that we don't use version numbers for the camel-spring schema. At runtime the schema is resolved in the Camel bundle. If we use a specific version number such as 1.4 then its IDE friendly as it would be able to import it and provide smart completion etc. See Xml Reference for further details.
Code Block | ||
---|---|---|
| ||
@Service(value="multiplier")
public class Treble implements Multiplier {
/* (non-Javadoc)
* @see org.example.server.Multiplier#multiply(int)
*/
public int multiply(final int originalNumber) {
return originalNumber * 3;
}
}
|
Using Spring annotations the bean is defined with the name multiplier.
Define the Camel Routes
Code Block | ||
---|---|---|
| ||
public class ServerRoutes extends RouteBuilder {
/* (non-Javadoc)
* @see org.apache.camel.builder.RouteBuilder#configure()
*/
@Override
public void configure() throws Exception {
from("jms:queue:numbers").beanRef("multiplier", "multiply");
}
}
|
This defines a Camel route from the JMS queue named numbers to the Spring bean named multiplier. Camel will create a consumer to the JMS queue which forwards all received messages onto the the Spring bean, using the method named multiply.
Configure Spring
The Spring config file is placed under META-INF/spring
as this is the default location used by the Camel Maven Plugin, which we will later use to run our server.
...
...
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:camel="http://activemq.apache.org/camel/schema/spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/context
http://schemas.leadx.com/spring/spring-context-2.5.xsd
http://activemq.apache.org/camel/schema/spring
http://activemq.apache.org/camel/schema/spring/camel-spring-1.3.0.xsd">
<context:component-scan base-package="org.example.server" />
<camel:camelContext id="camel">
<camel:package>org.example.server</camel:package>
</camel:camelContext>
<bean id="jms" class="org.apache.camel.component.jms.JmsComponent">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
</property>
</bean>
</beans>
Notice that we also have enabled the JMXAgent so we will be able to introspect the Camel Server with a JMX Console.from="jmsbroker:queue:numbers).to("multiplier");
We use the vm protocol to connect to the ActiveMQ server as its embedded in this application.
component-scan | Defines the package to be scanned for Spring stereotype annotations, in this case, to load the "multiplier" bean |
camel-context | Defines the package to be scanned for Camel routes. Will find the |
jms bean | Creates the Camel JMS component |
Run the Server
The Server is started using the org.apache.camel.spring.Main
class that can start camel-spring application out-of-the-box. The Server can be started in several flavors:
- as a standard java main application - just start the
org.apache.camel.spring.Main
class - using maven jave:exec
- using camel:run
In this sample as there are two servers (with and without AOP) we have prepared some profiles in maven to start the Server of your choice.
The server is started with:
mvn compile exec:java -PCamelServer
Writing The Clients
This sample has three clients demonstrating different Camel techniques for communication
- CamelClient using the ProducerTemplate for Spring template style coding
- CamelRemoting using Spring Remoting
- CamelEndpoint using the Message Endpoint EIP pattern using a neutral Camel API
Client Using The ProducerTemplate
component-scan | Defines the package to be scanned for Spring stereotype annotations, in this case, to load the "multiplier" bean |
camel-context | Defines the package to be scanned for Camel routes. Will find the |
jms bean | Creates the Camel JMS component |
Run the Server
Code Block | ||
---|---|---|
| ||
public class CamelServer {
public static void main(final String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("camel-server.xml");
}
}
|
The main
method can then be executed to start the server.
...
We will initially create a client by directly using CamelTemplate ProducerTemplate
. We will later create a client which uses Spring remoting to hide the fact that messaging is being used.
Code Block | ||||
---|---|---|---|---|
| ||||
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:camel="http://activemq.apache.org/camel/schema/spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://activemq.apache.org/camel/schema/spring
http://activemq.apache.org/camel/schema/spring/camel-spring-1.3-SNAPSHOT.xsd">
<camel:camelContext id="camel" />
<camel:template id="camelTemplate" />
<bean id="jms" class="org.apache.camel.component.jms.JmsComponent">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
</property>
</bean>
</beans>
|
The client will not use the Camel Maven Plugin so the Spring XML has been placed in src/main/resources so not to conflict with the server configs.
camelContext | The Camel context is defined but does not contain any routes |
tempate | The CamelTemplate will be used to place messages onto the JMS queue |
jms bean | This initialises the Camel JMS component, allowing us to place messages onto the queue |
Code Block | ||
---|---|---|
| ||
public class CamelClient {
public static void main(final String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("camel-client.xml");
CamelTemplate<JmsExchange> camelTemplate = (CamelTemplate) context.getBean("camelTemplate");
int response = (Integer)camelTemplate.sendBody("jms:queue:numbers",
ExchangePattern.InOut,
22
);
Assert.assertEquals(66, response);
System.out.println(response);
}
}
|
The CamelTemplate is retrieved from a Spring ApplicationContext and used to manually place a message on the numbers JMS queue. The exchange pattern (ExchangePattern.InOut) states that the call should be synchronous, and that we will recieve a response. We then assert that the response is three times the value of the original.
Before running the client be sure that both the ActiveMQ broker and the CamelServer are running.
Using Spring Remoting
Spring Remoting "eases the development of remote-enabled services". It does this by allowing you to invoke remote services through your regular Java interface, masking that a remote service is being called.
Code Block | ||||
---|---|---|---|---|
| ||||
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:camel="http://activemq.apache.org/camel/schema/spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://activemq.apache.org/camel/schema/spring
http://activemq.apache.org/camel/schema/spring/camel-spring-1.3-SNAPSHOT.xsd">
<camel:camelContext id="camel" />
<camel:proxy
id="multiplier"
serviceInterface="org.example.server.Multiplier"
serviceUrl="jms:queue:numbers"
/>
<bean id="jms" class="org.apache.camel.component.jms.JmsComponent">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
</property>
</bean>
</beans>
|
First we create a new Spring config file. This has a few changes made from camel-client. Firstly the Camel template has been removed, as it will not be used. Secondly a proxy is defined. This will create a proxy service bean for you to use to make the remote invokations. serviceInterface details which Java interface is to be implemented by the proxy. serviceUrl defines where messages sent to this proxy bean will be directed. Here we define the JMS endpoint with the numbers queue we used when working with Camel template directly. id is the name that will be the given to the bean when it is exposed through the Spring ApplicationContext. We will use this name to retrieve the service in our client. I have named the bean multiplierProxy simply to highlight that it is not the same multiplier bean as is being used by CamelServer. They are in completly independent contexts and have no knowledge of each other. As you are trying to mask the fact that remoting is being used in a real application you would generally not include proxy in the name.
Code Block | ||
---|---|---|
| ||
public class CamelClientRemoting {
public static void main(final String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("camel-client-remoting.xml");
Multiplier multiplier = (Multiplier) context.getBean("multiplierProxy");
int response = multiplier.multiply(22);
Assert.assertEquals(66, response);
System.out.println(response);
}
}
|
Again the client is similar to the original client, but with some important differences.
- The Spring context is created with the new camel-client-remoting.xml
- We retrieve the proxy bean instead of a CamelTemplate. In a non-trivial example you would have the bean injected as in the standard Spring manner.
- The multiply method is then called directy. In the client we are now working to an interface. There is no mention of Camel or JMS inside our Java code.
TODO: Using the Camel Maven Plugin
The Camel Maven Plugin.
TODO: Detail how to use the Maven plugin as an alternative to CamelServer
TODO: Testing
...
camelContext | The Camel context is defined but does not contain any routes |
template | The |
jms bean | This initialises the Camel JMS component, allowing us to place messages onto the queue |
And the CamelClient source code:ProducerTemplate
is retrieved from a Spring ApplicationContext
and used to manually place a message on the "numbers" JMS queue. The requestBody
method will use the exchange pattern InOut, which states that the call should be synchronous, and that the caller expects a response.
Before running the client be sure that both the ActiveMQ broker and the CamelServer
are running.
Client Using Spring Remoting
Spring Remoting "eases the development of remote-enabled services". It does this by allowing you to invoke remote services through your regular Java interface, masking that a remote service is being called.
The proxy will create a proxy service bean for you to use to make the remote invocations. The serviceInterface property details which Java interface is to be implemented by the proxy. The serviceUrl defines where messages sent to this proxy bean will be directed. Here we define the JMS endpoint with the "numbers" queue we used when working with Camel template directly. The value of the id property is the name that will be the given to the bean when it is exposed through the Spring ApplicationContext
. We will use this name to retrieve the service in our client. I have named the bean multiplierProxy simply to highlight that it is not the same multiplier bean as is being used by CamelServer
. They are in completely independent contexts and have no knowledge of each other. As you are trying to mask the fact that remoting is being used in a real application you would generally not include proxy in the name.
And the Java client source code:
- The Spring context is created with the new camel-client-remoting.xml
- We retrieve the proxy bean instead of a
ProducerTemplate
. In a non-trivial example you would have the bean injected as in the standard Spring manner. - The multiply method is then called directly. In the client we are now working to an interface. There is no mention of Camel or JMS inside our Java code.
Client Using Message Endpoint EIP Pattern
This client uses the Message Endpoint EIP pattern to hide the complexity to communicate to the Server. The Client uses the same simple API to get hold of the endpoint, create an exchange that holds the message, set the payload and create a producer that does the send and receive. All done using the same neutral Camel API for all the components in Camel. So if the communication was socket TCP based you just get hold of a different endpoint and all the java code stays the same. That is really powerful.
Okay enough talk, show me the code!"mina:tcp://localhost:61610"
then its just a matter of getting hold of this endpoint instead of the JMS and all the rest of the java code is exactly the same.
Run the Clients
The Clients is started using their main class respectively.
- as a standard java main application - just start their main class
- using maven jave:exec
In this sample we start the clients using maven:
mvn compile exec:java -PCamelClient
mvn compile exec:java -PCamelClientRemoting
mvn compile exec:java -PCamelClientEndpoint
Also see the Maven pom.xml
file how the profiles for the clients is defined.
Using the Camel Maven Plugin
The Camel Maven Plugin allows you to run your Camel routes directly from Maven. This negates the need to create a host application, as we did with Camel server, simply to start up the container. This can be very useful during development to get Camel routes running quickly.
...
All that is required is a new plugin definition in your Maven POM. As we have already placed our Camel config in the default location (camel-server.xml has been placed in META-INF/spring/) we do not need to tell the plugin where the route definitions are located. Simply run mvn camel:run
.
Using Camel JMX
Camel has extensive support for JMX and allows us to inspect the Camel Server at runtime. As we have enabled the JMXAgent in our tutorial we can fire up the jconsole and connect to the following service URI: service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi/camel
. Notice that Camel will log at INFO level the JMX Connector URI:
...
In the screenshot below we can see the route and its performance metrics: