I'll use the example in my previous article, Advanced Event Notification Framework with Apache Camel. In short, we're going to make a Sensor publish notification events to two Displays.
Fortunately, there is very minimal code change required to implement it using Spring Integration. My code was much more portable than I thought. ;-)
The complete application source can be downloaded/explored from: http://github.com/ceefour/eventfx.si
Tweaking Listener Interfaces
I had made a minor portability mistake before, annotating the listener interfaces with Camel-specific annotations. We can leave them out, as we can configure Spring Integration to work with POJO classes, without any annotations. (FYI, Apache Camel does too)
SyncListener.java becomes:
package com.soluvas.samples.eventfx.si; import java.util.EventListener; public interface SyncListener<E, R> extends EventListener { R update(E event); }
AsyncListener.java becomes:
package com.soluvas.samples.eventfx.si; import java.util.EventListener; public interface AsyncListener<E> extends EventListener { void notify(E event); }
Spring Context Configuration
The most major change is the Spring Context Configuration file.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:si="http://www.springframework.org/schema/integration" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-1.0.xsd"> <context:component-scan base-package="com.soluvas.samples.eventfx.si" /> <bean class="com.soluvas.samples.eventfx.si.Sensor"> <property name="sensorSyncListener"> <si:gateway id="sensorSyncListener" service-interface="com.soluvas.samples.eventfx.si.SyncListener" default-request-channel="Sensor_SensorEvent_sync" /> </property> <property name="sensorAsyncListener"> <si:gateway id="sensorAsyncListener" service-interface="com.soluvas.samples.eventfx.si.AsyncListener" default-request-channel="Sensor_SensorEvent_async" /> </property> </bean> <bean id="display1" class="com.soluvas.samples.eventfx.si.Display"> <property name="name" value="Sony(sync)" /> </bean> <bean id="display2" class="com.soluvas.samples.eventfx.si.Display"> <property name="name" value="Samsung(async)" /> </bean> <si:channel id="Sensor_SensorEvent_sync" /> <si:publish-subscribe-channel id="Sensor_SensorEvent_async" /> <si:service-activator input-channel="Sensor_SensorEvent_sync" ref="display1" method="update" /> <si:chain input-channel="Sensor_SensorEvent_async"> <si:delayer default-delay="1200" /> <si:service-activator ref="display2" method="notify" /> </si:chain> </beans>
The differences between this Spring Integration version with the Apache Camel version are:
- CamelProxyFactoryBean's are replaced with si:gateway beans, which has a more concise syntax.
Camel 2.2.0 will support similarly concise camel:proxy element that can be used outside camelContext element. - Endpoint URIs are replaced with explicit declaration of Spring Integration Channel beans. Here I use direct channel (si:channel) for sync events and publish-subscribe channel (si:publish-subscribe-channel) for async events.
I think the the explicit publish-subscribe channel in Spring Integration is very useful. In Apache Camel 2.1 and earlier, the only way to do this is to use multicast. In Apache Camel 2.2, the SEDA component will have a multipleConsumers option that provides a lightweight publish-subscribe mechanism (dynamic multicasting). - I specify the "routing" to Display service-activators using the Spring XML DSL. Apache Camel's Spring DSL feels much more powerful.
The Main Application
The main application, App.java, can't get any simpler:
package com.soluvas.samples.eventfx.si; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { new ClassPathXmlApplicationContext("META-INF/spring/*.xml"); } }I'm using Spring Integration 2.0 with Spring Framework 3.0 for this project. For those who are curious, here's the project's Maven pom.xml :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.soluvas.samples</groupId> <artifactId>eventfx.si</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <name>eventfx.si</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-core</artifactId> <version>2.0.0.M2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestone</id> <name>Spring Portfolio Milestone Repository</name> <url>http://s3.amazonaws.com/maven.springframework.org/milestone</url> </repository> </repositories> </project>
Event Notification Framework with Spring Integration 2.0 in Action
The app now runs exactly same as the Camel Event Notification example. Here's the application output log:
Jan 5, 2010 3:00:19 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@12a54f9: startup date [Tue Jan 05 03:00:19 WIT 2010]; root of context hierarchy Jan 5, 2010 3:00:19 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions INFO: Loading XML bean definitions from file [/home/ceefour/Sandbox/eventfx.si/target/classes/META-INF/spring/eventfx-si.xml] Jan 5, 2010 3:00:20 AM org.springframework.integration.config.xml.DefaultConfiguringBeanFactoryPostProcessor registerErrorChannelIfNecessary INFO: No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created. Jan 5, 2010 3:00:20 AM org.springframework.integration.config.xml.DefaultConfiguringBeanFactoryPostProcessor registerTaskSchedulerIfNecessary INFO: No bean named 'taskScheduler' has been explicitly defined. Therefore, a default ThreadPoolTaskScheduler will be created. Jan 5, 2010 3:00:20 AM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@2f0df1: defining beans [sensorSimulator,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.integration.internalDefaultConfiguringBeanFactoryPostProcessor,com.soluvas.samples.eventfx.si.Sensor#0,display1,display2,Sensor_SensorEvent_sync,Sensor_SensorEvent_async,org.springframework.integration.handler.ServiceActivatingHandler#0,org.springframework.integration.config.ConsumerEndpointFactoryBean#0,org.springframework.integration.handler.DelayHandler#e06940,org.springframework.integration.handler.ServiceActivatingHandler#1aae94f,org.springframework.integration.handler.MessageHandlerChain#0,org.springframework.integration.config.ConsumerEndpointFactoryBean#1,nullChannel,errorChannel,org.springframework.integration.handler.LoggingHandler#0,org.springframework.integration.endpoint.EventDrivenConsumer#0,org.springframework.integration.channel.MessagePublishingErrorHandler#0,taskScheduler]; root of factory hierarchy Jan 5, 2010 3:00:21 AM org.springframework.scheduling.concurrent.ExecutorConfigurationSupport initialize INFO: Initializing ExecutorService 'taskScheduler' Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start INFO: started org.springframework.integration.gateway.SimpleMessagingGateway@1fe571f Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start INFO: started si:gateway#1c5fde0 Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start INFO: started org.springframework.integration.gateway.SimpleMessagingGateway@cf710e Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start INFO: started si:gateway#19e8329 Jan 5, 2010 3:00:21 AM com.soluvas.samples.eventfx.si.SensorSimulator initialize INFO: Sensor simulator initialized. Jan 5, 2010 3:00:21 AM com.soluvas.samples.eventfx.si.Display initialize INFO: Display Sony(sync) created. Jan 5, 2010 3:00:21 AM com.soluvas.samples.eventfx.si.Display initialize INFO: Display Samsung(async) created. Jan 5, 2010 3:00:21 AM org.springframework.scheduling.concurrent.ExecutorConfigurationSupport initialize INFO: Initializing ExecutorService Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start INFO: started org.springframework.integration.config.ConsumerEndpointFactoryBean#0 Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start INFO: started org.springframework.integration.config.ConsumerEndpointFactoryBean#1 Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start INFO: started org.springframework.integration.endpoint.EventDrivenConsumer#0 Jan 5, 2010 3:00:23 AM com.soluvas.samples.eventfx.si.Sensor updateText INFO: updateText: Something happens at 3:00:23 AM Jan 5, 2010 3:00:23 AM com.soluvas.samples.eventfx.si.Display update INFO: [Sony(sync)] is updated: 'Something happens at 3:00:23 AM' Jan 5, 2010 3:00:23 AM com.soluvas.samples.eventfx.si.Sensor fireSensor INFO: Response: Sony(sync) received Something happens at 3:00:23 AM Jan 5, 2010 3:00:23 AM org.springframework.integration.endpoint.AbstractEndpoint start INFO: started org.springframework.integration.endpoint.EventDrivenConsumer@1be2893 Jan 5, 2010 3:00:24 AM com.soluvas.samples.eventfx.si.Display notify INFO: [Samsung(async)] is notified: 'Something happens at 3:00:23 AM' Jan 5, 2010 3:00:25 AM com.soluvas.samples.eventfx.si.Sensor updateText INFO: updateText: Something happens at 3:00:25 AM Jan 5, 2010 3:00:25 AM com.soluvas.samples.eventfx.si.Display update INFO: [Sony(sync)] is updated: 'Something happens at 3:00:25 AM' Jan 5, 2010 3:00:25 AM com.soluvas.samples.eventfx.si.Sensor fireSensor INFO: Response: Sony(sync) received Something happens at 3:00:25 AM
Which One Is More Powerful?
Technically speaking, I think Apache Camel is more powerful than Spring Integration. And this is not just a "feel", see the Spring XML above to see it for real.
For simpler purposes, both Apache Camel and Spring Integration do their job very well.
For more complex purposes, currently they have their own strengths and weaknesses.
Apache Camel allows you to refer to endpoints either by name ("ref") or by URI (though an endpoints must specify a URI), and creating an endpoint automatically just by referring to its URI. Spring Integration can only refer to a channel by name, and useful (i.e. non-direct) channels need to be created explicitly.
Apache Camel routes are extremely flexible, and can be specified in a variety of DSLs. With Spring Integration it's either basic routing or custom processing. Spring Integration has XML DSL and annotations, that Apache Camel also has.
Spring Integration's concept of publish-subscribe channel is more convenient than Apache Camel's.
Learning More on Enterprise Integration
Spring Integration is all about decoupling messaging between components, which is part of enterprise integration. If you want to learn more about enterprise integration, I heartily recommend the book Enterprise Integration Patterns.
Nice post. I noticed that you are referring to the publish-subscribe-channel as "async", but I think you might want to add a "task-executor" reference there if it's really your intention to use a different thread for dispatching the Messages.
ReplyDelete