185 lines
9.1 KiB
XML
185 lines
9.1 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
|
<chapter id="samples">
|
|
<title>Spring Integration Samples</title>
|
|
|
|
<section id="samples-cafe">
|
|
<title>The Cafe Sample</title>
|
|
<para>
|
|
In this section, we will review a sample application that is included in the Spring Integration
|
|
distribution. This sample is inspired by one of the samples featured in Gregor Hohpe's
|
|
<ulink url="http://www.eaipatterns.com/ramblings.html">Ramblings</ulink>.
|
|
</para>
|
|
<para>
|
|
The domain is that of a Cafe, and the basic flow is depicted in the following diagram:
|
|
</para>
|
|
<para>
|
|
<mediaobject>
|
|
<imageobject>
|
|
<imagedata align="center" fileref="images/cafe-demo.png" format="PNG"/>
|
|
</imageobject>
|
|
</mediaobject>
|
|
</para>
|
|
<para>
|
|
The <classname>DrinkOrder</classname> object may contain multiple <classname>Drinks</classname>. Once the order
|
|
is placed, a <emphasis>Splitter</emphasis> will break the composite order message into a single message per
|
|
drink. Each of these is then processed by a <emphasis>Router</emphasis> that determines whether the drink is hot
|
|
or cold (checking the <classname>Drink</classname> object's 'isIced' property). Finally the
|
|
<classname>Barista</classname> prepares each drink, but hot and cold drink preparation are handled by two
|
|
distinct methods: 'prepareHotDrink' and 'prepareColdDrink'.
|
|
</para>
|
|
<para>
|
|
Here is the XML configuration:
|
|
<programlisting language="xml"><![CDATA[<beans:beans xmlns="http://www.springframework.org/schema/integration"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:beans="http://www.springframework.org/schema/beans"
|
|
xmlns:context="http://www.springframework.org/schema/context"
|
|
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
|
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
|
|
http://www.springframework.org/schema/integration
|
|
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
|
|
http://www.springframework.org/schema/context
|
|
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
|
|
|
|
<message-bus/>
|
|
<annotation-driven/>
|
|
|
|
<context:component-scan base-package="org.springframework.integration.samples.cafe"/>
|
|
|
|
<channel id="orders"/>
|
|
<channel id="drinks"/>
|
|
<channel id="coldDrinks"/>
|
|
<channel id="hotDrinks"/>
|
|
|
|
<service-activator input-channel="coldDrinks" ref="barista" method="prepareColdDrink"/>
|
|
|
|
<service-activator input-channel="hotDrinks" ref="barista" method="prepareHotDrink"/>
|
|
|
|
<beans:bean id="cafe" class="org.springframework.integration.samples.cafe.Cafe">
|
|
<beans:property name="orderChannel" ref="orders"/>
|
|
</beans:bean>
|
|
|
|
</beans:beans>]]></programlisting>
|
|
Notice that the Message Bus is defined. It will automatically detect and register all channels and endpoints.
|
|
The 'annotation-driven' element will enable the detection of the splitter and router - both of which carry
|
|
the <interfacename>@MessageEndpoint</interfacename> annotation. That annotation extends Spring's
|
|
"stereotype" annotations (by relying on the @Component meta-annotation), and so all classes carrying the
|
|
endpoint annotation are capable of being detected by the component-scanner.
|
|
<programlisting language="java"><![CDATA[@MessageEndpoint(input="orders", output="drinks")
|
|
public class OrderSplitter {
|
|
|
|
@Splitter
|
|
public List<Drink> split(DrinkOrder order) {
|
|
return order.getDrinks();
|
|
}
|
|
}]]></programlisting>
|
|
<programlisting language="java"><![CDATA[@MessageEndpoint(input="drinks")
|
|
public class DrinkRouter {
|
|
|
|
@Router
|
|
public String resolveDrinkChannel(Drink drink) {
|
|
return (drink.isIced()) ? "coldDrinks" : "hotDrinks";
|
|
}
|
|
}]]></programlisting>
|
|
</para>
|
|
<para>
|
|
Now turning back to the XML, you see that there are two <service-activator> elements. Each of these
|
|
is delegating to the same <classname>Barista</classname> instance but different methods. The 'barista' could
|
|
have been defined in the XML, but instead the <interfacename>@Component</interfacename> annotation is applied:
|
|
<programlisting language="java"><![CDATA[@Component
|
|
public class Barista {
|
|
|
|
private long hotDrinkDelay = 5000;
|
|
private long coldDrinkDelay = 1000;
|
|
|
|
private AtomicInteger hotDrinkCounter = new AtomicInteger();
|
|
private AtomicInteger coldDrinkCounter = new AtomicInteger();
|
|
|
|
public void setHotDrinkDelay(long hotDrinkDelay) {
|
|
this.hotDrinkDelay = hotDrinkDelay;
|
|
}
|
|
|
|
public void setColdDrinkDelay(long coldDrinkDelay) {
|
|
this.coldDrinkDelay = coldDrinkDelay;
|
|
}
|
|
|
|
public void prepareHotDrink(Drink drink) {
|
|
try {
|
|
Thread.sleep(this.hotDrinkDelay);
|
|
System.out.println(Thread.currentThread().getName()
|
|
+ " prepared hot drink #" + hotDrinkCounter.incrementAndGet() + ": " + drink);
|
|
} catch (InterruptedException e) {
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
}
|
|
|
|
public void prepareColdDrink(Drink drink) {
|
|
try {
|
|
Thread.sleep(this.coldDrinkDelay);
|
|
System.out.println(Thread.currentThread().getName()
|
|
+ " prepared cold drink #" + coldDrinkCounter.incrementAndGet() + ": " + drink);
|
|
} catch (InterruptedException e) {
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
}
|
|
}]]></programlisting>
|
|
</para>
|
|
<para>
|
|
As you can see from the code excerpt above, the barista methods have different delays (the hot
|
|
drinks take 5 times as long to prepare). This simulates work being completed at different rates.
|
|
When the <classname>CafeDemo</classname> 'main' method runs, it will loop 100
|
|
times sending a single hot drink and a single cold drink each time.
|
|
<programlisting language="java"><![CDATA[public static void main(String[] args) {
|
|
AbstractApplicationContext context = null;
|
|
if(args.length > 0) {
|
|
context = new FileSystemXmlApplicationContext(args);
|
|
}
|
|
else {
|
|
context = new ClassPathXmlApplicationContext("cafeDemo.xml", CafeDemo.class);
|
|
}
|
|
context.start();
|
|
Cafe cafe = (Cafe) context.getBean("cafe");
|
|
DrinkOrder order = new DrinkOrder();
|
|
Drink hotDoubleLatte = new Drink(DrinkType.LATTE, 2, false);
|
|
Drink icedTripleMocha = new Drink(DrinkType.MOCHA, 3, true);
|
|
order.addDrink(hotDoubleLatte);
|
|
order.addDrink(icedTripleMocha);
|
|
for (int i = 0; i < 100; i++) {
|
|
cafe.placeOrder(order);
|
|
}
|
|
}]]></programlisting>
|
|
</para>
|
|
<para>
|
|
To run this demo, go to the "samples" directory within the root of the Spring Integration distribution. On
|
|
Unix/Mac you can run 'cafeDemo.sh', and on Windows you can run 'cafeDemo.bat'. Each of these will by default
|
|
create a Spring <interfacename>ApplicationContext</interfacename> from the 'cafeDemo.xml' file that is
|
|
in the "spring-integration-samples" JAR and hence on the classpath (it is the same as the XML above). However, a
|
|
copy of that file is also available within the "samples" directory, so that you can provide the file name as a
|
|
command line argument to either 'cafeDemo.sh' or 'cafeDemo.bat'. This will allow you to experiment with the
|
|
configuration and immediately run the demo with your changes. It is probably a good idea to first copy the
|
|
original file so that you can make as many changes as you want and still refer back to the original to compare.
|
|
</para>
|
|
<para>
|
|
When you run cafeDemo, you will see that all 100 cold drinks are prepared in roughly the same amount of time as
|
|
only 20 of the hot drinks. This is to be expected based on their respective delays of 1000 and 5000 milliseconds.
|
|
However, by configuring the endpoint concurrency, you can dramatically change the results. For example, to level
|
|
the playing field, you could add a concurrency interceptor with 5 workers for the hot drink barista:
|
|
<programlisting language="xml"><![CDATA[<service-activator input-channel="coldDrinks" ref="barista" method="prepareColdDrink"/>
|
|
|
|
<service-activator input-channel="hotDrinks" ref="barista" method="prepareHotDrink">
|
|
<interceptors>
|
|
]]><emphasis><![CDATA[<concurrency-interceptor max="5"/>]]></emphasis><![CDATA[
|
|
</interceptors>
|
|
</service-activator>]]></programlisting>
|
|
</para>
|
|
<para>
|
|
Also, notice that the worker thread name is displayed with each invocation. You should see that most of
|
|
the hot drinks are prepared by the concurrency-interceptor threads, but that occassionally it throttles
|
|
the input by forcing the message-bus (the caller) to invoke the operation. In addition to experimenting
|
|
with the 'concurrency' settings, you can also try adding the 'schedule' sub-element as described in
|
|
<xref linkend="namespace-endpoint"/>. If you want to explore the sample in more detail, the source JAR
|
|
is available in the "src" directory:
|
|
'org.springframework.integration.samples-sources-1.0.0.M4.jar'.
|
|
</para>
|
|
</section>
|
|
</chapter> |