Task Scheduling Simplifications in Spring 3.0

Continuing the Spring 3.0 "simplification series" started by Keith and Chris, I would like to provide a quick overview of simplifications in scheduling and task execution enabled by Spring 3.0.
I will be walking through a basic sample application that you can checkout from the spring-samples Subversion repository. It has been designed to be as simple as possible while showcasing both annotation-driven and XML-based approaches to scheduling tasks in Spring 3.0.
Let's begin with the annotation-driven approach. You can run it directly via the main() method in AnnotationDemo. If you take a look, you'll see that it's nothing more than a bootstrap for a Spring ApplicationContext:
public static void main(String[] args) {
new ClassPathXmlApplicationContext("config.xml", AnnotationDemo.class);
}
The reason nothing else is necessary is that the ApplicationContext contains an "active" component, which we will see in just a moment. Because of that component, the main() method will not exit. The config.xml is also minimal, containing only two elements:
<context:component-scan base-package="org/springframework/samples/task/basic/annotation"/> <task:annotation-driven/>
The "component-scan" element points to the package that contains our "beans". There are two of them: ScheduledProcessor and AsyncWorker. We'll look at those momentarily, but first take a look at the "annotation-driven" element. That's the one that is new in Spring 3.0, and it drives two annotations: @Scheduled and @Async. You could provide references to a Spring TaskScheduler and TaskExecutor with the "scheduler" and "executor" attributes respectively, but for this sample, we'll just rely on the defaults.
The ScheduledProcessor contains the @Scheduled annotation on a method and is therefore the "active" component I mentioned above. Since the 'annotation-driven' element exists in the configuration, this method will be registered with a Spring TaskScheduler instance that will execute the method periodically with a fixed delay of 30 seconds.
@Service
public class ScheduledProcessor implements Processor {
private final AtomicInteger counter = new AtomicInteger();
@Autowired
private Worker worker;
@Scheduled(fixedDelay = 30000)
public void process() {
System.out.println("processing next 10 at " + new Date());
for (int i = 0; i < 10; i++) {
worker.work(counter.incrementAndGet());
}
}
}
As you can see in the previous code excerpt, the Worker is invoked by the ScheduledProcessor within a loop. However, the AsyncWorker implementation contains the @Async annotation on its work(..) method, and due to the 'annotation-driven' element in the configuration, this will be wrapped in a proxy so that the method is actually invoked by a TaskExecutor instance. To verify that, the current thread name is displayed within that method. Likewise, to clarify that work is being performed concurrently, a sleep(..) call is made to simulate time-consuming work.
@Component
public class AsyncWorker implements Worker {
@Async
public void work(int i) {
String threadName = Thread.currentThread().getName();
System.out.println(" " + threadName + " beginning work on " + i);
try {
Thread.sleep(5000); // simulates work
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(" " + threadName + " completed work on " + i);
}
}
If you run the AnnotationDemo main() method, the output should look something like this:
SimpleAsyncTaskExecutor-1 beginning work on 1
SimpleAsyncTaskExecutor-2 beginning work on 2
SimpleAsyncTaskExecutor-3 beginning work on 3
SimpleAsyncTaskExecutor-5 beginning work on 5
SimpleAsyncTaskExecutor-4 beginning work on 4
SimpleAsyncTaskExecutor-6 beginning work on 6
SimpleAsyncTaskExecutor-7 beginning work on 7
SimpleAsyncTaskExecutor-8 beginning work on 8
SimpleAsyncTaskExecutor-9 beginning work on 9
SimpleAsyncTaskExecutor-10 beginning work on 10
SimpleAsyncTaskExecutor-1 completed work on 1
SimpleAsyncTaskExecutor-2 completed work on 2
SimpleAsyncTaskExecutor-3 completed work on 3
SimpleAsyncTaskExecutor-5 completed work on 5
SimpleAsyncTaskExecutor-6 completed work on 6
SimpleAsyncTaskExecutor-7 completed work on 7
SimpleAsyncTaskExecutor-8 completed work on 8
SimpleAsyncTaskExecutor-4 completed work on 4
SimpleAsyncTaskExecutor-10 completed work on 10
SimpleAsyncTaskExecutor-9 completed work on 9
There are a few things to notice about that output. First, the processing of 10 rows at a time will repeat every 30 seconds (due to @Scheduled). Second, the work items are being processed concurrently by different threads (due to @Async). There should be a pause of roughly 5 seconds between the last "beginning work" message and first "completed work" message. If the workers were all running in a single thread, we would instead see sequential beginning/completed pairs, and the entire process would take about 50 seconds. Of course, the temporal aspect cannot be captured here in the blog entry, so you really should download and run the sample yourself for the full effect (the project can be imported directly into SpringSource Tool Suite or another Eclipse-based environment with Maven support).
The last thing I want to show is the XML-based alternative for the @Scheduled annotation. The sample includes another class, SimpleProcessor, that does not contain @Scheduled on its process() method:
@Service
public class SimpleProcessor implements Processor {
private final AtomicInteger counter = new AtomicInteger();
public void process() {
System.out.println("processing next 10 at " + new Date());
for (int i = 0; i < 10; i++) {
System.out.println(" processing " + counter.incrementAndGet());
}
}
}
The XML is only slightly more verbose than the annotation-driven version, because Spring 3.0 now provides a "task" namespace to keep the configuration concise.
<context:component-scan base-package="org/springframework/samples/task/basic/xml"/>
<task:scheduled-tasks>
<task:scheduled ref="simpleProcessor" method="process" cron="3/10 * * * * ?"/>
</task:scheduled-tasks>
If you run the main() method in XmlDemo, you will see that process execute every 10 seconds. For variety, this one uses a cron expression instead of the simple fixed-delay. As a result, you will notice that the timing is based on a 3 second offset (:13, :23, etc), but of course cron expressions can be much more powerful than that (I just didn't want to create a sample that would only run on certain days or times of day). It's worth pointing out that the support for cron-based scheduling is included directly within Spring 3.0 itself.
Be sure to check out the Task Execution and Scheduling chapter of the Spring 3.0 Reference Manual to learn more about the new TaskScheduler abstraction and Trigger strategies that provide the foundation for what you've seen here. The reference manual also discusses additional elements provided by the "task" namespace for configuring TaskScheduler and TaskExecutor instances with specific thread pool settings.
I hope this post has provided a useful overview of these new features. Stay tuned to the SpringSource team blog for more Spring 3.0 related content.
Similar Posts
- Spring Integration Samples
- Request-Reply JMS with Spring 2.0
- Using JPA in Spring without referencing Spring
- A Bridge Too Far
- Spring Integration: a new addition to the Spring portfolio











James Hoare says:
Added on January 5th, 2010 at 11:06 amThanks for the post Mark, but could you add an example for @Async and Futures? I'm trying to get an example working where my @Async method returns a future but I never get a value after calling Future.get()?
It would be great if you could provide an example for this.
Thanks
Peter Veentjer says:
Added on January 5th, 2010 at 11:09 amOr you can create a few lines of normal Java code using the ScheduledExecutor. Takes a lot less space and is much better understandable. The whole annotation thing is making code hard to understand.
ex-spring enthusiast.
Mark Fisher (blog author) says:
Added on January 5th, 2010 at 11:11 am@James I probably should have mentioned the support for Future return values, but I was trying to keep the post as short and simple as possible, hence the use of void-returning methods only.
When you have a Future return type, it should be quite straightforward. Maybe you can post your particular issue (with the get() call failing) in the Spring Forum and provide a link here?
You also might want to have a look at the AsyncResult implementation of Future that is provided in Spring 3.0. That makes it very easy to wrap your value.
Eko Kurniawan Khannedy says:
Added on January 5th, 2010 at 11:53 amyeah!!!
I Love Spring 3
John says:
Added on January 5th, 2010 at 12:09 pmWhat's the long term vision to enhance spring task scheduling? Could it be a replacement for Quartz?
Ron DiFrango says:
Added on January 5th, 2010 at 1:27 pmThis all looks great, but what about support for a clustered configuration where you only want one of the jobs in the cluster to fire?
Dave Syer (blog author) says:
Added on January 5th, 2010 at 2:01 pmA lot of use cases where we see people using Quartz are covered by the features provided by Spring Framework. For example, many people have used Quartz *only* for cron support, and not for clustering or persistent jobs, so "yes" to that part. For the sake of clarity: we do not plan to extend Spring Frameworks's role beyond scheduling/triggers, but there are other Spring projects with intersting features in this area. Spring Batch has persistence, so there is a class of use cases for which Spring Spring Batch is already a good alternative to Spring Quartz. Some of the more advanced features of Quartz (like cluster support) can be implemented using a combination of Spring and Spring Batch, but are not supported out of the box. Spring Batch Admin (which I blogged about a few weeks ago) fills in some of the blanks, and I expect in the future could be more interesting for more sophisticated users of Quartz.
Ron DiFrango says:
Added on January 5th, 2010 at 2:55 pmI would agree, that until my last client the Spring 3.0 implementation of Tasks would have been more than sufficient and saved me a ton of time. It would be nice to see it extended to support clustering via some sort of Quartz annotation or configuration mechanism so you get the best of both worlds.
Alberto Flores says:
Added on January 6th, 2010 at 9:51 amSimply awesome! – Thanks!
Isaac says:
Added on January 6th, 2010 at 10:47 pmMy main motivation for using Quartz in addition to the TaskExecutor is that when using Quartz, I have the ability to have my schedules 'filtered' by a Calendar, so that I can exclude blocks of time. I generally use Quartz Calendars in combination with CronTriggers to express something like "every hour on non-holidays during working hours". Would love a similar functionality independent of Quartz.
Atul says:
Added on January 7th, 2010 at 12:04 pmThere is no exceptional handling strategy defined for asynchronous method call.
when using @Async and return type is void,there is no clue in this framework to handle the exception.
In case method annotated with @Async returns future task,we can still call a get method on Futuretask and check if the thread is complete successsfully or throws concurrent exception.
any thoughts how to catch the exception when method using @Aysnc with returns type as void
Grzegorz Borkowski says:
Added on January 25th, 2010 at 11:41 amI've played with this new scheduling support. However, after switching to this new style, and using "task:scheduled-tasks" element in my application context file, I'm no longer able to shut down Tomcat on which application is deployed. I have to kill tomcat process. I guess this element creates some non-daemon thread which will stop VM from exiting.
Looks definitely like a bug.
Mark Fisher (blog author) says:
Added on January 25th, 2010 at 12:13 pm@Grzegorz there are non-daemon threads running within Spring's TaskScheduler. However, that implements Lifecycle and DisposableBean so the normal shutdown hook behavior should apply. Can you please post more information about your example in the forum? http://forum.springsource.org/
Thank you,
Mark
Grzegorz Borkowski says:
Added on January 25th, 2010 at 4:04 pmOk, I described it here: http://forum.springsource.org/showthread.php?p=280269#post280269
Krishna says:
Added on February 8th, 2010 at 4:15 pmCan Spring Integration 1.0.3 RELEASE run on Spring 3.0 ?
Thanks
James Heggs says:
Added on March 10th, 2010 at 10:38 amHi Mark,
I may have found a potential bug when using annotation based task scheduling and in particular:
@Scheduled(cron="* 10/1 * * * * ?")
After I have done a bit more testing if I find it is still an issue should I raise it on JIRA?
James Heggs says:
Added on March 10th, 2010 at 11:16 amPlease ignore my previous comment it looks like it was an issue with my setup.