Monday, October 30, 2006

Scheduling an Ongoing Task in a Servlet

Recently, I had the need to create a task that had to run hourly, with the stats to be available to clients. I wanted the task to be distributed without having to worry about configuring a database store, etc.


The solution I came up with was to create a JAVA servlet and use a Timer object to schedule a timer task that runs every hour. The task then runs hourly and saves the results in memory.


Usually, creating threads (which is what a TimerTask object is) in a servlet container is frowned upon because the container cannot manage the resources used by the user spawned thread. In addition, whenever your web application is deployed, reloaded, or undeployed you have to ensure that you clean up after your thread.


The first thing that you will do is implement the ServletContextListener interface. This interface consist of 2 methods that you will have to implement, namely,


public void contextInitialized (ServletContextEvent servletContextEvent);

&

public void contextDestroyed (ServletContextEvent servletContextEvent);


The contextInitialized(...) method is invoked by the servlet container everytime the servlet is 'started up'. So upon initial deployment, this method is called. Everytime you reload your web application, this method is invoked.

The contextDestroyed(...) method is invoked everytime the web application is shut down, for example, when you reload or un-deploy the web application.

Ideally, anything that you need to do when the web application first starts up should be done in the contextInitialized(...) method, while anything to undo the initialization should be done in the contextDestroyed(...) method.


In the contextInitialized(...) method, the idea is to create a TimerTask object and using the Timer object, schedule it at the desired time interval. Once the scheduling is done, we save the Timer object as an attribute in the ServletContext.

For example, assuming that MyTimerTask extends TimerTask,

  
ServletContext servletContext = servletContextEvent.getServletContext();
try{
// create the timer and timer task objects
Timer timer = new Timer();
MyTimerTask task = new MyTimerTask();

// get a calendar to initialize the start time
Calendar calendar = Calendar.getInstance();
Date startTime = calendar.getTime();

// schedule the task to run hourly
timer.scheduleAtFixedRate(task, startTime, 1000 * 60 * 60);

// save our timer for later use
servletContext.setAttribute ("timer", timer);
} catch (Exception e) {
servletContext.log ("Problem initializing the task that was to run hourly: " + e.getMessage ());
}

In the contextDestroyed(...) method, the idea is to clean up after yourself. In our case, this means cancelling any tasks and removing the timer object.


ServletContext servletContext = servletContextEvent.getServletContext();

// get our timer from the Context
Timer timer = (Timer)servletContext.getAttribute ("timer");

// cancel all pending tasks in the timers queue
if (timer != null)
timer.cancel();

// remove the timer from the servlet context
servletContext.removeAttribute ("timer");

The final thing that we have to do now is make sure that our ServletContextListener is added to our web.xml file.

As a child element of the <web-app> element, add the following lines of code:


<listener>
<listener-class>your.package.declaration.MyServletContextListener</listener-class>
</listener>

Where, your.package.declaration is the package containing the class MyServletContextListener that implements ServletContextListener!

Now everytime the web application starts up and shuts down, our listener will be invoked. Not only did we create an on-going task, but we scheduled the task and made sure that we cleaned up after ourselves too!

12 comments:

Anonymous said...

Very nice. It's not technically JEE compliant, but it works, which is what I'm more concerned with. Thanks!

Anonymous said...

Having implemented ServletContextListener... any clue why my Tomcat (5.5.17) shuts down when I stop/reload my application?? Very strange and unwished behaviour!

Ed said...

When did this behaviour occur? What happens if you remove your context listener? Does it still shut down tomcat? Can we see your listener?

Anonymous said...

Error found:
One always running job in the scheduler (receiving telegrams from a DB) was finished by System.exit().
This shut down Tomcat, too.

Ed said...

Yeah, System.exit() will shut down tomcat, as you may have noticed!

Quintin said...

Thanks so much for this. Clean and simple.

Nice work.

Satyendra_rocks said...

nice work
it helped a lot

Anonymous said...

This was a great help, thank you very much.

Anonymous said...

Thanks, this helped alot. Thanks for keeping it on the web.

Stewart M. said...

Very nice! This is exactly what I am looking for in my app! I have a process that needs to run once daily. Perfect solution!

Ronak said...

Does init() and destory() methods of Servlet is not proper place to initialise and clenup Timer object?

Eddie said...

The key thing to take home is that the Context init parameters are available to the entire web application and not just to a single servlet like servlet init parameters.

So I guess it depends on what you are trying to accomplish.