AMQP-117: brush up docs with namespace examples

This commit is contained in:
Dave Syer
2011-03-31 12:27:51 +01:00
parent 1956dca3e8
commit f9d4d6f4f3
2 changed files with 258 additions and 95 deletions

View File

@@ -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;

View File

@@ -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>