AMQP-117: brush up docs with namespace examples
This commit is contained in:
@@ -435,14 +435,14 @@ Message receive(String queueName) throws AmqpException;]]></programlisting>
|
||||
adapter implementation that is provided by the framework. This is often
|
||||
referred to as "Message-driven POJO" support. When using the adapter, you
|
||||
only need to provide a reference to the instance that the adapter itself
|
||||
should invoke.</para> <programlisting language="java"><![CDATA[MessageListener listener = new MessageListenerAdapter(somePojo);]]></programlisting>
|
||||
Now that you've seen the various options for the Message-listening
|
||||
callback, we can turn our attention to the container. Basically, the
|
||||
container handles the "active" responsibilities so that the listener
|
||||
callback can remain passive. The container is an example of a "lifecycle"
|
||||
component. It provides methods for starting and stopping. When configuring
|
||||
the container, you are essentially bridging the gap between an AMQP Queue
|
||||
and the <interfacename>MessageListener</interfacename> instance. You must
|
||||
should invoke.</para><programlisting language="java"><![CDATA[MessageListener listener = new MessageListenerAdapter(somePojo);]]></programlisting>Now
|
||||
that you've seen the various options for the Message-listening callback,
|
||||
we can turn our attention to the container. Basically, the container
|
||||
handles the "active" responsibilities so that the listener callback can
|
||||
remain passive. The container is an example of a "lifecycle" component. It
|
||||
provides methods for starting and stopping. When configuring the
|
||||
container, you are essentially bridging the gap between an AMQP Queue and
|
||||
the <interfacename>MessageListener</interfacename> instance. You must
|
||||
provide a reference to the
|
||||
<interfacename>ConnectionFactory</interfacename> and the queue name or
|
||||
Queue instance(s) from which that listener should consume Messages. Here
|
||||
@@ -450,17 +450,15 @@ Message receive(String queueName) throws AmqpException;]]></programlisting>
|
||||
<classname>SimpleMessageListenerContainer</classname> : <programlisting
|
||||
language="java"><![CDATA[SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
|
||||
container.setConnectionFactory(rabbitConnectionFactory);
|
||||
container.setQueueName("some.queue");
|
||||
container.setMessageListener(someListener);]]></programlisting> As an "active"
|
||||
component, it's most common to create the listener container with a bean
|
||||
definition so that it can simply run in the background. This can be done
|
||||
via XML: <programlisting language="xml"><![CDATA[<bean class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer">
|
||||
<property name="connectionFactory" ref="rabbitConnectionFactory"/>
|
||||
<property name="queueName" value="some.queue"/>
|
||||
<property name="messageListener" ref="someListener"/>
|
||||
</bean>]]></programlisting> Or, you may prefer to use the @Configuration style
|
||||
which will look very similar to the actual code snippet above:
|
||||
<programlisting language="java"><![CDATA[@Configuration
|
||||
container.setQueueNames("some.queue");
|
||||
container.setMessageListener(new MessageListenerAdapter(somePojo));]]></programlisting>
|
||||
As an "active" component, it's most common to create the listener
|
||||
container with a bean definition so that it can simply run in the
|
||||
background. This can be done via XML: <programlisting language="xml"><![CDATA[<rabbit:listener-container connection-factory="rabbitConnectionFactory">
|
||||
<rabbit:listener queues="some.queue" ref="somePojo" method="handle"/>
|
||||
</rabbit:listener-container>]]></programlisting> Or, you may prefer to use the
|
||||
@Configuration style which will look very similar to the actual code
|
||||
snippet above: <programlisting language="java"><![CDATA[@Configuration
|
||||
public class ExampleAmqpConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -699,68 +697,58 @@ Object receiveAndConvert(String queueName) throws AmqpException;]]></programlist
|
||||
|
||||
<para>The RabbitMQ implementation of this interface is RabbitAdmin which
|
||||
when configured using Spring XML would look like this:<programlisting
|
||||
language="xml"><![CDATA[<bean id="cf" class="org.springframework.amqp.rabbit.connection.SingleConnectionFactory">
|
||||
language="xml"><![CDATA[<bean id="cf" class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
|
||||
<constructor-arg value="localhost"/>
|
||||
<property name="username" value="guest"/>
|
||||
<property name="password" value="guest"/>
|
||||
</bean>
|
||||
|
||||
<bean id="amqpAdmin" class="org.springframework.amqp.rabbit.core.RabbitAdmin">
|
||||
<constructor-arg ref="cf"/>
|
||||
</bean>]]></programlisting></para>
|
||||
<rabbit:admin id="amqpAdmin" connection-factory="cf"/>]]></programlisting></para>
|
||||
|
||||
<para>The class AbstractAmqpConfiguration can also be used to configure an
|
||||
instance of the AmqpAmin interface. The base class
|
||||
AbstractAmqpConfiguration is located in the package
|
||||
org.springframework.amqp.config and is shown in part below.</para>
|
||||
<para>The <classname>RabbitAdmin</classname> implementation does automatic
|
||||
lazy declaration of <classname>Queues</classname>,
|
||||
<classname>Exchanges</classname> and <classname>Bindings</classname>
|
||||
declared in the same <classname>ApplicationContext</classname>. These
|
||||
components will be declared as son as a <classname>Connection</classname>
|
||||
is opened to the broker. There are some namespace features that make this
|
||||
very convenient, e.g. in the Stocks sample application we have:</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[@Configuration
|
||||
public abstract class AbstractAmqpConfiguration implements ApplicationContextAware, SmartLifecycle {
|
||||
<programlisting><![CDATA[<rabbit:queue id="tradeQueue" />
|
||||
|
||||
@Bean
|
||||
public abstract AmqpAdmin amqpAdmin();
|
||||
<rabbit:queue id="marketDataQueue" />
|
||||
|
||||
// the rest omitted for brevity.
|
||||
}]]></programlisting>
|
||||
<fanout-exchange name="broadcast.responses" xmlns="http://www.springframework.org/schema/rabbit">
|
||||
<bindings>
|
||||
<binding queue="tradeQueue" />
|
||||
</bindings>
|
||||
</fanout-exchange>
|
||||
|
||||
<para>The part that is omitted is related to the implementation of
|
||||
SmartLifecycle which is responsible for querying the container for all
|
||||
Queues, Exchanges, and Bindings that are defined in the DI container and
|
||||
declaring them to the broker. Thus if you are using the @Configuration
|
||||
style of configuration, you can simply create Queue, Exchange, and Binding
|
||||
bean definitions and they will be declared to the broker once the
|
||||
application has started.</para>
|
||||
<topic-exchange name="app.stock.marketdata" xmlns="http://www.springframework.org/schema/rabbit">
|
||||
<bindings>
|
||||
<binding queue="marketDataQueue" pattern="${stocks.quote.pattern}" />
|
||||
</bindings>
|
||||
</topic-exchange>]]></programlisting>
|
||||
|
||||
<para>If you are using RabbitMQ as your broker, the additional abstract
|
||||
@Configuration class can be used to bootstrap an implementation of
|
||||
AmqpAdmin as shown below</para>
|
||||
<para>In the example above we are using anonymous Queues (actually
|
||||
internally just Queues with names generated by the framework, not by the
|
||||
broker) and refer to them by ID. We can also declare Queues with explicit
|
||||
names, which also serve as identifiers for their bean definitions in the
|
||||
context. E.g.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[@Configuration
|
||||
public abstract class AbstractRabbitConfiguration extends AbstractAmqpConfiguration {
|
||||
<programlisting><![CDATA[<rabbit:queue name="stocks.trade.queue"/>]]></programlisting>
|
||||
|
||||
@Bean
|
||||
public abstract RabbitTemplate rabbitTemplate();
|
||||
|
||||
@Bean
|
||||
public AmqpAdmin amqpAdmin() {
|
||||
this.amqpAdmin = new RabbitAdmin(rabbitTemplate().getConnectionFactory());
|
||||
return this.amqpAdmin;
|
||||
}
|
||||
}]]></programlisting>
|
||||
|
||||
<para>This leaves it up to you to provide an implementation of the
|
||||
rabbitTemplate method in your application specific subclass. For example,
|
||||
using the Stock sample application, there is the @Configuration class
|
||||
<para>To see how to use Java to configure the AMQP infrastructure, look at
|
||||
the Stock sample application, there is the @Configuration class
|
||||
AbstractStockRabbitConfiguration which in turn has
|
||||
RabbitClientConfiguration and RabbitServerConfiguration subclasses. The
|
||||
code for AbstractStockRabbitConfiguration is show below</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[@Configuration
|
||||
public abstract class AbstractStockAppRabbitConfiguration extends AbstractRabbitConfiguration {
|
||||
public abstract class AbstractStockAppRabbitConfiguration {
|
||||
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory() {
|
||||
SingleConnectionFactory connectionFactory = new SingleConnectionFactory("localhost");
|
||||
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
|
||||
connectionFactory.setUsername("guest");
|
||||
connectionFactory.setPassword("guest");
|
||||
return connectionFactory;
|
||||
|
||||
@@ -7,39 +7,76 @@
|
||||
<section>
|
||||
<title>Introduction</title>
|
||||
|
||||
<para>The Spring AMQP project includes two sample applications. The first is a simple "Hello World" example that demonstrates both synchronous and asynchronous message reception. It provides an excellent starting point for acquiring an understanding of the essential components. The second sample is based on a stock-trading use case to demonstrate the types of interaction that would be common in real world applications. In this chapter, we will provide a quick walk-through of each sample so that you can focus on the most important components. The samples are available in the distribution, and they are both Maven-based, so you should be able to import them directly into any Maven-aware IDE (such as <ulink url="http://www.springsource.com/products/sts">SpringSource Tool Suite</ulink>).</para>
|
||||
<para>The Spring AMQP Samples project includes two sample applications.
|
||||
The first is a simple "Hello World" example that demonstrates both
|
||||
synchronous and asynchronous message reception. It provides an excellent
|
||||
starting point for acquiring an understanding of the essential components.
|
||||
The second sample is based on a stock-trading use case to demonstrate the
|
||||
types of interaction that would be common in real world applications. In
|
||||
this chapter, we will provide a quick walk-through of each sample so that
|
||||
you can focus on the most important components. The samples are available
|
||||
in the distribution, and they are both Maven-based, so you should be able
|
||||
to import them directly into any Maven-aware IDE (such as <ulink
|
||||
url="http://www.springsource.com/products/sts">SpringSource Tool
|
||||
Suite</ulink>).</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Hello World</title>
|
||||
|
||||
<para>The Hello World sample demonstrates both synchronous and asynchronous message reception. You can import the 'spring-rabbit-helloworld' sample into the IDE and then follow the discussion below.</para>
|
||||
<para>The Hello World sample demonstrates both synchronous and
|
||||
asynchronous message reception. You can import the
|
||||
'spring-rabbit-helloworld' sample into the IDE and then follow the
|
||||
discussion below.</para>
|
||||
|
||||
<section id="hello-world-sync">
|
||||
<title>Synchronous Example</title>
|
||||
|
||||
<para>Within the 'src/main/java' directory, navigate to the 'org.springframework.amqp.helloworld' package. Open the HelloWorldConfiguration class and notice that it contains the @Configuration annotation at class-level and some @Bean annotations at method-level. This is an example of Spring's Java-based configuration. You can read more about that <ulink url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/htmlsingle/spring-framework-reference.html#beans-java">here</ulink>.</para>
|
||||
<para>Within the 'src/main/java' directory, navigate to the
|
||||
'org.springframework.amqp.helloworld' package. Open the
|
||||
HelloWorldConfiguration class and notice that it contains the
|
||||
@Configuration annotation at class-level and some @Bean annotations at
|
||||
method-level. This is an example of Spring's Java-based configuration.
|
||||
You can read more about that <ulink
|
||||
url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/htmlsingle/spring-framework-reference.html#beans-java">here</ulink>.</para>
|
||||
|
||||
<para>You will see that the RabbitConfiguration class extends a framework-provided class called AbstractRabbitConfiguration. That forces it to implement the abstract rabbitTemplate() method while the base class itself then creates an 'amqpAdmin' bean. The "rabbitTemplate" bean in turn depends upon the bean that is created by the connectionFactory() method. There, we are providing the 'username' and 'password' properties as well as the 'hostname' constructor argument to an instance of SingleConnectionFactory.</para>
|
||||
<para>You will see that the RabbitConfiguration class extends a
|
||||
framework-provided class called AbstractRabbitConfiguration. That forces
|
||||
it to implement the abstract rabbitTemplate() method while the base
|
||||
class itself then creates an 'amqpAdmin' bean. The "rabbitTemplate" bean
|
||||
in turn depends upon the bean that is created by the connectionFactory()
|
||||
method. There, we are providing the 'username' and 'password' properties
|
||||
as well as the 'hostname' constructor argument to an instance of
|
||||
SingleConnectionFactory.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[@Bean
|
||||
public ConnectionFactory connectionFactory() {
|
||||
SingleConnectionFactory connectionFactory = new SingleConnectionFactory("localhost");
|
||||
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
|
||||
connectionFactory.setUsername("guest");
|
||||
connectionFactory.setPassword("guest");
|
||||
return connectionFactory;
|
||||
}]]></programlisting>
|
||||
|
||||
<para>The base class also provides a mechanism that will recognize any Exchange, Queue, or Binding bean definitions and then declare them on the broker. In fact, the "helloWorldQueue" bean that is generated in HelloWorldConfiguration is an example simply because it is an instance of Queue.</para>
|
||||
<para>The base class also provides a mechanism that will recognize any
|
||||
Exchange, Queue, or Binding bean definitions and then declare them on
|
||||
the broker. In fact, the "helloWorldQueue" bean that is generated in
|
||||
HelloWorldConfiguration is an example simply because it is an instance
|
||||
of Queue.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[@Bean
|
||||
public Queue helloWorldQueue() {
|
||||
return new Queue(this.helloWorldQueueName);
|
||||
}]]></programlisting>
|
||||
|
||||
<para>Looking back at the "rabbitTemplate" bean configuration, you will see that it has the helloWorldQueue's name set as its "queue" property (for receiving Messages) and for its "routingKey" property (for sending Messages).</para>
|
||||
<para>Looking back at the "rabbitTemplate" bean configuration, you will
|
||||
see that it has the helloWorldQueue's name set as its "queue" property
|
||||
(for receiving Messages) and for its "routingKey" property (for sending
|
||||
Messages).</para>
|
||||
|
||||
<para>Now that we've explored the configuration, let's look at the code that actually uses these components. First, open the Producer class from within the same package. It contains a main() method where the Spring ApplicationContext is created.</para>
|
||||
<para>Now that we've explored the configuration, let's look at the code
|
||||
that actually uses these components. First, open the Producer class from
|
||||
within the same package. It contains a main() method where the Spring
|
||||
ApplicationContext is created.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[public static void main(String[] args) {
|
||||
ApplicationContext context = new AnnotationConfigApplicationContext(RabbitConfiguration.class);
|
||||
@@ -48,9 +85,27 @@ public Queue helloWorldQueue() {
|
||||
System.out.println("Sent: Hello World");
|
||||
}]]></programlisting>
|
||||
|
||||
<para>As you can see in the example above, the AmqpTemplate bean is retrieved and used for sending a Message. Since the client code should rely on interfaces whenever possible, the type is AmqpTemplate rather than RabbitTemplate. Even though the bean created in HelloWorldConfiguration is an instance of RabbitTemplate, relying on the interface means that this code is more portable (the configuration can be changed independently of the code). Since the convertAndSend() method is invoked, the template will be delegating to its MessageConverter instance. In this case, it's using the default SimpleMessageConverter, but a different implementation could be provided to the "rabbitTemplate" bean as defined in HelloWorldConfiguration.</para>
|
||||
<para>As you can see in the example above, the AmqpTemplate bean is
|
||||
retrieved and used for sending a Message. Since the client code should
|
||||
rely on interfaces whenever possible, the type is AmqpTemplate rather
|
||||
than RabbitTemplate. Even though the bean created in
|
||||
HelloWorldConfiguration is an instance of RabbitTemplate, relying on the
|
||||
interface means that this code is more portable (the configuration can
|
||||
be changed independently of the code). Since the convertAndSend() method
|
||||
is invoked, the template will be delegating to its MessageConverter
|
||||
instance. In this case, it's using the default SimpleMessageConverter,
|
||||
but a different implementation could be provided to the "rabbitTemplate"
|
||||
bean as defined in HelloWorldConfiguration.</para>
|
||||
|
||||
<para>Now open the Consumer class. It actually shares the same configuration base class which means it will be sharing the "rabbitTemplate" bean. That's why we configured that template with both a "routingKey" (for sending) and "queue" (for receiving). As you saw in <xref linkend="amqp-template"/>, you could instead pass the 'routingKey' argument to the send method and the 'queue' argument to the receive method. The Consumer code is basically a mirror image of the Producer, calling receiveAndConvert() rather than convertAndSend().</para>
|
||||
<para>Now open the Consumer class. It actually shares the same
|
||||
configuration base class which means it will be sharing the
|
||||
"rabbitTemplate" bean. That's why we configured that template with both
|
||||
a "routingKey" (for sending) and "queue" (for receiving). As you saw in
|
||||
<xref linkend="amqp-template" />, you could instead pass the
|
||||
'routingKey' argument to the send method and the 'queue' argument to the
|
||||
receive method. The Consumer code is basically a mirror image of the
|
||||
Producer, calling receiveAndConvert() rather than
|
||||
convertAndSend().</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[public static void main(String[] args) {
|
||||
ApplicationContext context = new AnnotationConfigApplicationContext(RabbitConfiguration.class);
|
||||
@@ -58,16 +113,31 @@ public Queue helloWorldQueue() {
|
||||
System.out.println("Received: " + amqpTemplate.receiveAndConvert());
|
||||
}]]></programlisting>
|
||||
|
||||
<para>If you run the Producer, and then run the Consumer, you should see the message "Received: Hello World" in the console output.</para>
|
||||
|
||||
</section>
|
||||
<para>If you run the Producer, and then run the Consumer, you should see
|
||||
the message "Received: Hello World" in the console output.</para>
|
||||
</section>
|
||||
|
||||
<section id="hello-world-async">
|
||||
<title>Asynchronous Example</title>
|
||||
|
||||
<para>Now that we've walked through the synchronous Hello World sample, it's time to move on to a slightly more advanced but significantly more powerful option. With a few modifications, the Hello World sample can provide an example of asynchronous reception, a.k.a. <emphasis>Message-driven POJOs</emphasis>. In fact, there is a sub-package that provides exactly that: org.springframework.amqp.samples.helloworld.async.</para>
|
||||
<para>Now that we've walked through the synchronous Hello World sample,
|
||||
it's time to move on to a slightly more advanced but significantly more
|
||||
powerful option. With a few modifications, the Hello World sample can
|
||||
provide an example of asynchronous reception, a.k.a.
|
||||
<emphasis>Message-driven POJOs</emphasis>. In fact, there is a
|
||||
sub-package that provides exactly that:
|
||||
org.springframework.amqp.samples.helloworld.async.</para>
|
||||
|
||||
<para>Once again, we will start with the sending side. Open the ProducerConfiguration class and notice that it creates a "connectionFactory" and "rabbitTemplate" bean. This time, since the configuration is dedicated to the message sending side, we don't even need any Queue definitions, and the RabbitTemplate only has the 'routingKey' property set. Recall that messages are sent to an Exchange rather than being sent directly to a Queue. The AMQP default Exchange is a direct Exchange with no name. All Queues are bound to that default Exchange with their name as the routing key. That is why we only need to provide the routing key here.</para>
|
||||
<para>Once again, we will start with the sending side. Open the
|
||||
ProducerConfiguration class and notice that it creates a
|
||||
"connectionFactory" and "rabbitTemplate" bean. This time, since the
|
||||
configuration is dedicated to the message sending side, we don't even
|
||||
need any Queue definitions, and the RabbitTemplate only has the
|
||||
'routingKey' property set. Recall that messages are sent to an Exchange
|
||||
rather than being sent directly to a Queue. The AMQP default Exchange is
|
||||
a direct Exchange with no name. All Queues are bound to that default
|
||||
Exchange with their name as the routing key. That is why we only need to
|
||||
provide the routing key here.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[public RabbitTemplate rabbitTemplate() {
|
||||
RabbitTemplate template = new RabbitTemplate(connectionFactory());
|
||||
@@ -75,7 +145,13 @@ public Queue helloWorldQueue() {
|
||||
return template;
|
||||
}]]></programlisting>
|
||||
|
||||
<para>Since this sample will be demonstrating asynchronous message reception, the producing side is designed to continuously send messages (if it were a message-per-execution model like the synchronous version, it would not be quite so obvious that it is in fact a message-driven consumer). The component responsible for sending messages continuously is defined as an inner class within the ProducerConfiguration. It is configured to execute every 3 seconds.</para>
|
||||
<para>Since this sample will be demonstrating asynchronous message
|
||||
reception, the producing side is designed to continuously send messages
|
||||
(if it were a message-per-execution model like the synchronous version,
|
||||
it would not be quite so obvious that it is in fact a message-driven
|
||||
consumer). The component responsible for sending messages continuously
|
||||
is defined as an inner class within the ProducerConfiguration. It is
|
||||
configured to execute every 3 seconds.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[static class ScheduledProducer {
|
||||
|
||||
@@ -90,9 +166,17 @@ public Queue helloWorldQueue() {
|
||||
}
|
||||
}]]></programlisting>
|
||||
|
||||
<para>You don't need to understand all of the details since the real focus should be on the receiving side (which we will cover momentarily). However, if you are not yet familiar with Spring 3.0 task scheduling support, you can learn more <ulink url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/htmlsingle/spring-framework-reference.html#scheduling-annotation-support">here</ulink>. The short story is that the "postProcessor" bean in the ProducerConfiguration is registering the task with a scheduler.</para>
|
||||
<para>You don't need to understand all of the details since the real
|
||||
focus should be on the receiving side (which we will cover momentarily).
|
||||
However, if you are not yet familiar with Spring 3.0 task scheduling
|
||||
support, you can learn more <ulink
|
||||
url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/htmlsingle/spring-framework-reference.html#scheduling-annotation-support">here</ulink>.
|
||||
The short story is that the "postProcessor" bean in the
|
||||
ProducerConfiguration is registering the task with a scheduler.</para>
|
||||
|
||||
<para>Now, let's turn to the receiving side. To emphasize the Message-driven POJO behavior will start with the component that is reacting to the messages. The class is called HelloWorldHandler.</para>
|
||||
<para>Now, let's turn to the receiving side. To emphasize the
|
||||
Message-driven POJO behavior will start with the component that is
|
||||
reacting to the messages. The class is called HelloWorldHandler.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[public class HelloWorldHandler {
|
||||
|
||||
@@ -102,7 +186,13 @@ public Queue helloWorldQueue() {
|
||||
|
||||
}]]></programlisting>
|
||||
|
||||
<para>Clearly, that <emphasis>is</emphasis> a POJO. It does not extend any base class, it doesn't implement any interfaces, and it doesn't even contain any imports. It is being "adapted" to the MessageListener interface by the Spring AMQP MessageListenerAdapter. That adapter can then be configured on a SimpleMessageListenerContainer. For this sample, the container is created in the ConsumerConfiguration class. You can see the POJO wrapped in the adapter there.</para>
|
||||
<para>Clearly, that <emphasis>is</emphasis> a POJO. It does not extend
|
||||
any base class, it doesn't implement any interfaces, and it doesn't even
|
||||
contain any imports. It is being "adapted" to the MessageListener
|
||||
interface by the Spring AMQP MessageListenerAdapter. That adapter can
|
||||
then be configured on a SimpleMessageListenerContainer. For this sample,
|
||||
the container is created in the ConsumerConfiguration class. You can see
|
||||
the POJO wrapped in the adapter there.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[@Bean
|
||||
public SimpleMessageListenerContainer listenerContainer() {
|
||||
@@ -113,35 +203,87 @@ public SimpleMessageListenerContainer listenerContainer() {
|
||||
return container;
|
||||
}]]></programlisting>
|
||||
|
||||
<para>The SimpleMessageListenerContainer is a Spring lifecycle component and will start automatically by default. If you look in the Consumer class, you will see that its main() method consists of nothing more than a one-line bootstrap to create the ApplicationContext. The Producer's main() method is also a one-line bootstrap, since the component whose method is annotated with @Scheduled will also start executing automatically. You can start the Producer and Consumer in any order, and you should see messages being sent and received every 3 seconds.</para>
|
||||
<para>The SimpleMessageListenerContainer is a Spring lifecycle component
|
||||
and will start automatically by default. If you look in the Consumer
|
||||
class, you will see that its main() method consists of nothing more than
|
||||
a one-line bootstrap to create the ApplicationContext. The Producer's
|
||||
main() method is also a one-line bootstrap, since the component whose
|
||||
method is annotated with @Scheduled will also start executing
|
||||
automatically. You can start the Producer and Consumer in any order, and
|
||||
you should see messages being sent and received every 3 seconds.</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Stock Trading</title>
|
||||
|
||||
<para>The Stock Trading sample demonstrates more advanced messaging scenarios than the Hello World sample. However, the configuration is very similar - just a bit more involved. Since we've walked through the Hello World configuration in detail, here we'll focus on what makes this sample different. There is a server that pushes market data (stock quotes) to a Topic Exchange. Then, clients can subscribe to the market data feed by binding a Queue with a routing pattern (e.g. "app.stock.quotes.nasdaq.*"). The other main feature of this demo is a request-reply "stock trade" interaction that is initiated by the client and handled by the server. That involves a private "replyTo" Queue that is sent by the client within the order request Message itself.</para>
|
||||
<para>The Stock Trading sample demonstrates more advanced messaging
|
||||
scenarios than the Hello World sample. However, the configuration is very
|
||||
similar - just a bit more involved. Since we've walked through the Hello
|
||||
World configuration in detail, here we'll focus on what makes this sample
|
||||
different. There is a server that pushes market data (stock quotes) to a
|
||||
Topic Exchange. Then, clients can subscribe to the market data feed by
|
||||
binding a Queue with a routing pattern (e.g. "app.stock.quotes.nasdaq.*").
|
||||
The other main feature of this demo is a request-reply "stock trade"
|
||||
interaction that is initiated by the client and handled by the server.
|
||||
That involves a private "replyTo" Queue that is sent by the client within
|
||||
the order request Message itself.</para>
|
||||
|
||||
<para>The Server's core configuration is in the RabbitServerConfiguration class within the org.springframework.amqp.rabbit.stocks.config.server package. It extends the AbstractStockAppRabbitConfiguration. That is where the resources common to the Server and Client(s) are defined, including the market data Topic Exchange (whose name is 'app.stock.marketdata') and the Queue that the Server exposes for stock trades (whose name is 'app.stock.request'). In that common configuration file, you will also see that a JsonMessageConverter is configured on the RabbitTemplate.</para>
|
||||
<para>The Server's core configuration is in the RabbitServerConfiguration
|
||||
class within the org.springframework.amqp.rabbit.stocks.config.server
|
||||
package. It extends the AbstractStockAppRabbitConfiguration. That is where
|
||||
the resources common to the Server and Client(s) are defined, including
|
||||
the market data Topic Exchange (whose name is 'app.stock.marketdata') and
|
||||
the Queue that the Server exposes for stock trades (whose name is
|
||||
'app.stock.request'). In that common configuration file, you will also see
|
||||
that a JsonMessageConverter is configured on the RabbitTemplate.</para>
|
||||
|
||||
<para>The Server-specific configuration consists of 2 things. First, it configures the market data exchange on the RabbitTemplate so that it does not need to provide that exchange name with every call to send a Message. It does this within an abstract callback method defined in the base configuration class.</para>
|
||||
<para>The Server-specific configuration consists of 2 things. First, it
|
||||
configures the market data exchange on the RabbitTemplate so that it does
|
||||
not need to provide that exchange name with every call to send a Message.
|
||||
It does this within an abstract callback method defined in the base
|
||||
configuration class.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[public void configureRabbitTemplate(RabbitTemplate rabbitTemplate) {
|
||||
rabbitTemplate.setExchange(MARKET_DATA_EXCHANGE_NAME);
|
||||
}]]></programlisting>
|
||||
|
||||
<para>Secondly, the stock request queue is declared. It does not require any explicit bindings in this case, because it will be bound to the default no-name exchange with its own name as the routing key. As mentioned earlier, the AMQP specification defines that behavior.</para>
|
||||
<para>Secondly, the stock request queue is declared. It does not require
|
||||
any explicit bindings in this case, because it will be bound to the
|
||||
default no-name exchange with its own name as the routing key. As
|
||||
mentioned earlier, the AMQP specification defines that behavior.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[@Bean
|
||||
public Queue stockRequestQueue() {
|
||||
return new Queue(STOCK_REQUEST_QUEUE_NAME);
|
||||
}]]></programlisting>
|
||||
|
||||
<para>Now that you've seen the configuration of the Server's AMQP resources, navigate to the 'org.springframework.amqp.rabbit.stocks' package under the 'src/test/java' directory. There you will see the actual Server class that provides a main() method. It creates an ApplicationContext based on the 'server-bootstrap.xml' config file. In there you will see the scheduled task that publishes dummy market data. That configuration relies upon Spring 3.0's "task" namespace support. The bootstrap config file also imports a few other files. The most interesting one is 'server-messaging.xml' which is directly under 'src/main/resources'. In there you will see the "messageListenerContainer" bean that is responsible for handling the stock trade requests. Finally have a look at the "serverHandler" bean that is defined in "server-handlers.xml" (also in 'src/main/resources'). That bean is an instance of the ServerHandler class and is a good example of a Message-driven POJO that is also capable of sending reply Messages. Notice that it is not itself coupled to the framework or any of the AMQP concepts. It simply accepts a TradeRequest and returns a TradeResponse.</para>
|
||||
<para>Now that you've seen the configuration of the Server's AMQP
|
||||
resources, navigate to the 'org.springframework.amqp.rabbit.stocks'
|
||||
package under the 'src/test/java' directory. There you will see the actual
|
||||
Server class that provides a main() method. It creates an
|
||||
ApplicationContext based on the 'server-bootstrap.xml' config file. In
|
||||
there you will see the scheduled task that publishes dummy market data.
|
||||
That configuration relies upon Spring 3.0's "task" namespace support. The
|
||||
bootstrap config file also imports a few other files. The most interesting
|
||||
one is 'server-messaging.xml' which is directly under
|
||||
'src/main/resources'. In there you will see the "messageListenerContainer"
|
||||
bean that is responsible for handling the stock trade requests. Finally
|
||||
have a look at the "serverHandler" bean that is defined in
|
||||
"server-handlers.xml" (also in 'src/main/resources'). That bean is an
|
||||
instance of the ServerHandler class and is a good example of a
|
||||
Message-driven POJO that is also capable of sending reply Messages. Notice
|
||||
that it is not itself coupled to the framework or any of the AMQP
|
||||
concepts. It simply accepts a TradeRequest and returns a
|
||||
TradeResponse.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[public TradeResponse handleMessage(TradeRequest tradeRequest) { ... }]]></programlisting>
|
||||
|
||||
<para>Now that we've seen the most important configuration and code for the Server, let's turn to the Client. The best starting point is probably RabbitClientConfiguration within the 'org.springframework.amqp.rabbit.stocks.config.client' package. Notice that it declares two queues without providing explicit names.</para>
|
||||
<para>Now that we've seen the most important configuration and code for
|
||||
the Server, let's turn to the Client. The best starting point is probably
|
||||
RabbitClientConfiguration within the
|
||||
'org.springframework.amqp.rabbit.stocks.config.client' package. Notice
|
||||
that it declares two queues without providing explicit names.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[@Bean
|
||||
public Queue marketDataQueue() {
|
||||
@@ -153,7 +295,16 @@ public Queue traderJoeQueue() {
|
||||
return amqpAdmin().declareQueue();
|
||||
}]]></programlisting>
|
||||
|
||||
<para>Those are private queues, and unique names will be generated automatically. The first generated queue is used by the Client to bind to the market data exchange that has been exposed by the Server. Recall that in AMQP, consumers interact with Queues while producers interact with Exchanges. The "binding" of Queues to Exchanges is what instructs the broker to deliver, or route, messages from a given Exchange to a Queue. Since the market data exchange is a Topic Exchange, the binding can be expressed with a routing pattern. The RabbitClientConfiguration declares that with a Binding object, and that object is generated with the BindingBuilder's fluent API.</para>
|
||||
<para>Those are private queues, and unique names will be generated
|
||||
automatically. The first generated queue is used by the Client to bind to
|
||||
the market data exchange that has been exposed by the Server. Recall that
|
||||
in AMQP, consumers interact with Queues while producers interact with
|
||||
Exchanges. The "binding" of Queues to Exchanges is what instructs the
|
||||
broker to deliver, or route, messages from a given Exchange to a Queue.
|
||||
Since the market data exchange is a Topic Exchange, the binding can be
|
||||
expressed with a routing pattern. The RabbitClientConfiguration declares
|
||||
that with a Binding object, and that object is generated with the
|
||||
BindingBuilder's fluent API.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[@Value("${stocks.quote.pattern}")
|
||||
private String marketDataRoutingKey;
|
||||
@@ -164,11 +315,32 @@ public Binding marketDataBinding() {
|
||||
marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
|
||||
}]]></programlisting>
|
||||
|
||||
<para>Notice that the actual value has been externalized in a properties file ("client.properties" under src/main/resources), and that we are using Spring's @Value annotation to inject that value. This is generally a good idea, since otherwise the value would have been hardcoded in a class and unmodifiable without recompilation. In this case, it makes it much easier to run multiple versions of the Client while making changes to the routing pattern used for binding. Let's try that now.</para>
|
||||
<para>Notice that the actual value has been externalized in a properties
|
||||
file ("client.properties" under src/main/resources), and that we are using
|
||||
Spring's @Value annotation to inject that value. This is generally a good
|
||||
idea, since otherwise the value would have been hardcoded in a class and
|
||||
unmodifiable without recompilation. In this case, it makes it much easier
|
||||
to run multiple versions of the Client while making changes to the routing
|
||||
pattern used for binding. Let's try that now.</para>
|
||||
|
||||
<para>Start by running org.springframework.amqp.rabbit.stocks.Server and then org.springframework.amqp.rabbit.stocks.Client. You should see dummy quotes for NASDAQ stocks because the current value associated with the 'stocks.quote.pattern' key in client.properties is 'app.stock.quotes.nasdaq.*'. Now, while keeping the existing Server and Client running, change that property value to 'app.stock.quotes.nyse.*' and start a second Client instance. You should see that the first client is still receiving NASDAQ quotes while the second client receives NYSE quotes. You could instead change the pattern to get all stocks or even an individual ticker.</para>
|
||||
<para>Start by running org.springframework.amqp.rabbit.stocks.Server and
|
||||
then org.springframework.amqp.rabbit.stocks.Client. You should see dummy
|
||||
quotes for NASDAQ stocks because the current value associated with the
|
||||
'stocks.quote.pattern' key in client.properties is
|
||||
'app.stock.quotes.nasdaq.*'. Now, while keeping the existing Server and
|
||||
Client running, change that property value to 'app.stock.quotes.nyse.*'
|
||||
and start a second Client instance. You should see that the first client
|
||||
is still receiving NASDAQ quotes while the second client receives NYSE
|
||||
quotes. You could instead change the pattern to get all stocks or even an
|
||||
individual ticker.</para>
|
||||
|
||||
<para>The final feature we'll explore is the request-reply interaction from the Client's perspective. Recall that we have already seen the ServerHandler that is accepting TradeRequest objects and returning TradeResponse objects. The corresponding code on the Client side is RabbitStockServiceGateway in the 'org.springframework.amqp.rabbit.stocks.gateway' package. It delegates to the RabbitTemplate in order to send Messages.</para>
|
||||
<para>The final feature we'll explore is the request-reply interaction
|
||||
from the Client's perspective. Recall that we have already seen the
|
||||
ServerHandler that is accepting TradeRequest objects and returning
|
||||
TradeResponse objects. The corresponding code on the Client side is
|
||||
RabbitStockServiceGateway in the
|
||||
'org.springframework.amqp.rabbit.stocks.gateway' package. It delegates to
|
||||
the RabbitTemplate in order to send Messages.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[public void send(TradeRequest tradeRequest) {
|
||||
getRabbitTemplate().convertAndSend(tradeRequest, new MessagePostProcessor() {
|
||||
@@ -186,7 +358,10 @@ public Binding marketDataBinding() {
|
||||
});
|
||||
}]]></programlisting>
|
||||
|
||||
<para>Notice that prior to sending the message, it sets the "replyTo" address. It's providing the queue that was generated by the "traderJoeQueue" bean definition shown above. Here's the @Bean definition for the StockServiceGateway class itself.</para>
|
||||
<para>Notice that prior to sending the message, it sets the "replyTo"
|
||||
address. It's providing the queue that was generated by the
|
||||
"traderJoeQueue" bean definition shown above. Here's the @Bean definition
|
||||
for the StockServiceGateway class itself.</para>
|
||||
|
||||
<programlisting language="java"><![CDATA[@Bean
|
||||
public StockServiceGateway stockServiceGateway() {
|
||||
@@ -196,9 +371,9 @@ public StockServiceGateway stockServiceGateway() {
|
||||
return gateway;
|
||||
}]]></programlisting>
|
||||
|
||||
<note><para>We are planning to provide a 'sendAndReceive' as well as a 'convertSendAndReceive' directly in the AmqpTemplate in the Milestone 2 release of Spring AMQP. At that point, this client-side gateway implementation will be simplified considerably.</para></note>
|
||||
|
||||
<para>If you are no longer running the Server and Client, start them now. Try sending a request with the format of '100 TCKR'. After a brief artificial delay that simulates "processing" of the request, you should see a confirmation message appear on the Client.</para>
|
||||
|
||||
<para>If you are no longer running the Server and Client, start them now.
|
||||
Try sending a request with the format of '100 TCKR'. After a brief
|
||||
artificial delay that simulates "processing" of the request, you should
|
||||
see a confirmation message appear on the Client.</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
Reference in New Issue
Block a user