Using Perlock with Spring
Written on
Perlock - a simple Java path watching library I have written - is available in Maven Central since a few weeks now. I thought it would be nice to provide another demo application to show how it can be used with Spring. It is provided along with Perlock on Github in examples/perlock-spring-demo.
Demo Scenario
The demo application implements a XML file processing application. Clients put XML files in an input directory (in the example, it’s just the /tmp directory), a MessageDispatcher
listens for new XML files in the directory with Perlock, and dispatches the file processing to a MessageProcessor
which simulates some work. Below is an overview of the involved classes:
The most interesting part of the demo application is MessageDispatcher
(slightly simplified below):
public class MessageDispatcher extends AbstractPathChangeListener {
private final Executor executor;
public MessageDispatcher(Executor executor) {
this.executor = executor;
}
@Override
public void onPathCreated(Path path) {
if (path.toString().endsWith("xml")) {
executor.execute(new MessageProcessor(path));
}
}
}
It is notified by Perlock whenever a path is created (callback #onPathCreated(Path)
) and decides whether a corresponding message needs to be processed. As message processing might by very time-consuming, the MessageDispatcher
executes each MessageProcessor
in an Executor
. Thus, the MessageDispatcher
returns very quickly allowing the application to stay responsive.
Bootstraping Perlock with Spring
The Perlock API provides three key API elements: We have seen the first one already: PathChangeListener
, which listens for changes on a file system. It is always associated to one PathWatcher
, which allows applications to control the path watching life cycle. A PathWatcher
is created by a PathWatcherFactory
. For details refer to the README on Github or my introductory post on Perlock. The Perlock-Spring integration consists of two FactoryBean
implementations; one to create the PathWatcherFactory
and another one to create PathWatcher
instances:
This might look verbose at first glance, but keep in mind that an application should only create one instance of PathWatcherFactory
but may create multiple instances of PathWatcher
to watch different paths, so the separation of the FactoryBeans
makes sense.
Trying the Spring Demo
To try the demo yourself, follow these steps:
- Clone the repo:
git clone https://github.com/danielmitterdorfer/perlock.git
- Create a JAR with all dependencies:
gradle fatJar
- Issue:
java -jar examples/perlock-spring-demo/build/libs/perlock-spring-demo-0.2.0.jar
You’ll now see this output:
TRACE 2014-02-19 21:04:15,298 [main] (PathWatcherFactory.java:114) - Submitting 'PathWatcher for '/tmp'' to executor service.
INFO 2014-02-19 21:04:15,302 [main] (SpringPathWatcherDemo.java:29) - Press any key to stop the demo
TRACE 2014-02-19 21:04:15,302 [pathWatchExecutor-1] (PathWatcherFactory.java:140) - About to run 'PathWatcher for '/tmp''.
TRACE 2014-02-19 21:04:15,308 [pathWatchExecutor-1] (AbstractRegistrationStrategy.java:33) - Registering Path: '/tmp'
TRACE 2014-02-19 21:04:15,309 [pathWatchExecutor-1] (WatchServicePathWatcher.java:91) - Waiting for file system events
cd /tmp
in a new terminal window and issue touch hello{1,2}.xml && sleep 2 && touch hello{3,4}.xml
:
You’ll now see output like:
TRACE 2014-02-19 21:07:01,425 [pathWatchExecutor-1] (WatchServicePathWatcher.java:124) - Received watch event with kind 'ENTRY_CREATE'
INFO 2014-02-19 21:07:01,426 [pathWatchExecutor-1] (MessageDispatcher.java:31) - Starting handling path '/tmp/hello1.xml'
TRACE 2014-02-19 21:07:01,428 [pathWatchExecutor-1] (WatchServicePathWatcher.java:124) - Received watch event with kind 'ENTRY_CREATE'
TRACE 2014-02-19 21:07:01,428 [messageHandlingExecutor-1] (MessageProcessor.java:24) - Simulating work on '/tmp/hello1.xml'
INFO 2014-02-19 21:07:01,428 [pathWatchExecutor-1] (MessageDispatcher.java:31) - Starting handling path '/tmp/hello2.xml'
TRACE 2014-02-19 21:07:01,428 [messageHandlingExecutor-1] (MessageProcessor.java:27) - Processing '/tmp/hello1.xml'. 5 seconds left...
TRACE 2014-02-19 21:07:01,429 [pathWatchExecutor-1] (WatchServicePathWatcher.java:124) - Received watch event with kind 'ENTRY_CREATE'
TRACE 2014-02-19 21:07:01,429 [messageHandlingExecutor-2] (MessageProcessor.java:24) - Simulating work on '/tmp/hello2.xml'
TRACE 2014-02-19 21:07:02,429 [messageHandlingExecutor-1] (MessageProcessor.java:27) - Processing '/tmp/hello1.xml'. 4 seconds left...
# ... more output here ...
TRACE 2014-02-19 21:07:05,438 [messageHandlingExecutor-3] (MessageProcessor.java:27) - Processing '/tmp/hello3.xml'. 1 seconds left...
TRACE 2014-02-19 21:07:06,433 [messageHandlingExecutor-1] (MessageProcessor.java:34) - Done. Cleaning up... '/tmp/hello1.xml'
TRACE 2014-02-19 21:07:06,434 [messageHandlingExecutor-2] (MessageProcessor.java:34) - Done. Cleaning up... '/tmp/hello2.xml'
TRACE 2014-02-19 21:07:06,439 [messageHandlingExecutor-3] (MessageProcessor.java:34) - Done. Cleaning up... '/tmp/hello3.xml'
TRACE 2014-02-19 21:07:06,439 [messageHandlingExecutor-4] (MessageProcessor.java:34) - Done. Cleaning up... '/tmp/hello4.xml'
Note how all MessageProcessor
s run interleaved in their own thread. This allows the application to receive new events while still processing older messages.
What have we done?
The takeaways of this post are:
- Perlock provides an easy integration of Perlock into Spring based on two
FactoryBean
implementations. - The Spring demo application shows a very small working example of how the integration of Perlock and Spring can be achieved.
- Remember to offload heavy-lifting to a dedicated thread and implement only very lightweight
PathChangeListener
s.
If you need path watching in a Spring-based application go and get the new Perlock-Spring integration! In Gradle it is as easy as:
dependencies {
compile group: 'name.mitterdorfer.perlock', name: 'perlock-spring', version: '0.2.0'
}
or alternatively via Maven.
<dependency>
<groupId>name.mitterdorfer.perlock</groupId>
<artifactId>perlock-spring</artifactId>
<version>0.2.0</version>
</dependency>
Questions or comments?
Just ping me on Twitter