In this post, I propose several solutions to create CRON with frameworksand component JDK timer, Quartz, ServletContextListener, ApplicationContextAware….
Cron, crontab, push, pull, pool…what are they?
First, some definitions:
- The crontab is the configuration table containing the crons. So cron (or job) is a process or a program launched at predefined times.
- The push mode is a client-server communication model in which the dialogue is initiated by a cron/process from the server to a process on client. So, in a producer/consumer model, the push mode comes from the push of the product from producer to the consumer by different techniques.
- The pull mode is the classical client-server communication model in which the dialogue is initiated by a cron/process from the client to a service on server.This model corresponds to the classical working of web transactions where the client opens the dialogue to server, and pulls to him the informations (pull).
- A pool is a set of reusable resources (cron, job) managed with a common manner in order to do the same goals.
Create cron with frameworks
So, after these above definitions, we will focus on the scheduling of CRONs due to JDK Timer with Spring support. However, there are several solutions to create crons with:
- the popular scheduler framework Quartz without Spring support (see the very good post Quartz scheduler example on mkyong),
- the popular scheduler framework Quartz with Spring support (see the very good post Spring + Quartz scheduler example on mkyong),
- an other component of JDK the ScheduledExecutorService (see the official documentation). In a nutshell, this executor can schedule commands to run after a given delay, or to execute periodically.
We have used this component ScheduledExecutorService in the post about the A concrete example of Queue’s utilization: Multi Servers Dispatching (Part8) in order to implement a runnable MyHostConnectionProxyAutoCloseConnection to detect a connection’s timeout and close the connection:
// Contains a reference to the executor that can schedule commands to run after a given delay, or to execute periodically. protected static ScheduledExecutorService autoCloseConnectionSvc = Executors.newScheduledThreadPool(10); ... // Contains a reference to the future triggering the current Runnable. // Creates and executes a one-shot action that becomes enabled after the given delay. Future scheduledFuture = autoCloseConnectionSvc.schedule(this, 5*60, TimeUnit.SECONDS); // 5 minutes ... // Cancel the execution of this task scheduledFuture.cancel(false);
- an other “solution”, we could use the org.springframework.context.ApplicationContextAware simply to create CRON.
The ApplicationContextAware has the following method:
– public void setApplicationContext(ApplicationContext applicationContext): method to set the ApplicationContext that this object runs in.Then, following the source code of ApplicationContextAware:
public class ApplicationContextAwareInit implements ApplicationContextAware { private ExecutorService writerExecutorService; // This semaphore is used to serialize the execution of this task if there are multiple instances of the thread. private static final Semaphore executionSemaphore = new Semaphore(1, true); // Internal runnable TASK private class ConnectorWriterTask implements Runnable { private ApplicationContext context; public ConnectorWriterTask(ApplicationContext context){ this.context = context; } public synchronized void run() { long timeBetweenExecution = 5*60*1000; try { // Acquisition of the semaphore executionSemaphore.acquire(); // Business to run in the CRON // .... use the Spring context for example // .... // .... // .... } catch (Throwable th) { th.printStackTrace(); }finally { try { // Release of the semaphore executionSemaphore.release(); } catch (Throwable e) { e.printStackTrace(); } try { // WAITING TIME BETWEEN 2 EXECUTIONS Thread.currentThread().sleep(timeBetweenExecution); } catch (Throwable e) { e.printStackTrace(); } // Sometimes, the server couldn't stop a local thread in VM. // Here, we check if the Spring context is still active // to launch a new instance of current thread. if(((AbstractApplicationContext)context).isActive() == true){ // SUBMIT NEW THREAD writerExecutorService.submit(new ConnectorWriterTask(context)); }else{ // STOP thread try { Thread.currentThread().stop(); Thread.currentThread().destroy(); } catch (Throwable e) { } // end-try } } } } // --------------- PUBLIC METHODS public void setApplicationContext(ApplicationContext context) throws BeansException { try { // Spring Context for example // Wait the loading of spring Context while(!((AbstractApplicationContext)context).isActive()) { Thread.currentThread().sleep(1000*2); } // end-while writerExecutorService.submit(new ConnectorWriterTask(context)); } catch (Throwable th) { th.printStackTrace(); } } }
- an other “solution”, we could use the javax.servlet.ServletContextListener simply to create CRON.
The ServletContextListener has the following two methods:
– public void contextInitialized(ServletContextEvent event),
– public void contextDestroyed(ServletContextEvent event),The following example shows how implement CRON with the ServletContextListener:
Configuration needed in web.xml file:<listener> <listener-class>com.ho.listener.ConnectorWriterStarter</listener-class> </listener>
Then, following the source code of listener:
public class ConnectorWriterStarter implements ServletContextListener { private ExecutorService writerExecutorService; // This semaphore is used to serialize the execution of this task if there are multiple instances of the thread. private static final Semaphore executionSemaphore = new Semaphore(1, true); // Internal runnable TASK private class ConnectorWriterTask implements Runnable { private ApplicationContext context; public ConnectorWriterTask(ApplicationContext context){ this.context = context; } public synchronized void run() { long timeBetweenExecution = 5*60*1000; try { // Acquisition of the semaphore executionSemaphore.acquire(); // Business to run in the CRON // .... use the Spring context for example // .... // .... // .... } catch (Throwable th) { th.printStackTrace(); }finally { try { // Release of the semaphore executionSemaphore.release(); } catch (Throwable e) { e.printStackTrace(); } try { // WAITING TIME BETWEEN 2 EXECUTIONS Thread.currentThread().sleep(timeBetweenExecution); } catch (Throwable e) { e.printStackTrace(); } // Sometimes, the server couldn't stop a local thread in VM. // Here, we check if the Spring context is still active // to launch a new instance of current thread. if(((AbstractApplicationContext)context).isActive() == true){ // SUBMIT NEW THREAD writerExecutorService.submit(new ConnectorWriterTask(context)); }else{ // STOP thread try { Thread.currentThread().stop(); Thread.currentThread().destroy(); } catch (Throwable e) { } // end-try } } } } // ------------------ CONSTRUCTORS public ConnectorWriterStarter() { this.writerExecutorService = Executors.newCachedThreadPool(); } // --------------- PUBLIC METHODS /** * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent) */ public void contextDestroyed(ServletContextEvent arg0) { } /** * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent) */ public void contextInitialized(ServletContextEvent arg0) { try { // Spring Context for example ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(arg0.getServletContext()); // Wait the loading of spring Context while(!((AbstractApplicationContext)context).isActive()) { Thread.currentThread().sleep(1000*2); } // end-while writerExecutorService.submit(new ConnectorWriterTask(context)); } catch (Throwable th) { th.printStackTrace(); } } }
- an other “solution” (because this solution is more adapted to pool) with Spring the org.springframework.core.task.TaskExecutor: Spring 2.0 introduces a new abstraction for dealing with executors. Executors are the Java 5 name for the concept of thread pools. The “executor” naming is due to the fact that there is no guarantee that the underlying implementation is actually a pool; an executor may be single-threaded or even synchronous. Spring’s abstraction hides implementation details between Java SE 1.4, Java SE 5 and Java EE environments.
There are several implementations supported by Spring: SimpleAsyncTaskExecutor, SyncTaskExecutor, ConcurrentTaskExecutor, SimpleThreadPoolTaskExecutor, ThreadPoolTaskExecutor, TimerTaskExecutor, WorkManagerTaskExecutor. Here, an example of use of the most commonly used implementation ThreadPoolTaskExecutor which is pool implementation. However, if we create a pool TaskExecutor containing a unique Runnable, we can define rules to decide when the task gets executed.
So, the following example shows how implement CRON with the TaskExecutor:
import org.springframework.core.task.TaskExecutor; public class MyTaskExecutor{ private TaskExecutor taskExecutor; public MyTaskExecutor(TaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } public void sayHello() { // Add Runnable to the queue and the TaskExecutor uses its internal rules to decide WHEN the task gets executed taskExecutor.execute(new MyHelloTask()); } private class MyHelloTask implements Runnable { private ExecutorService writerExecutorService; // This semaphore is used to serialize the execution of this task if there are multiple instances of the thread. private static final Semaphore executionSemaphore = new Semaphore(1, true); public MyHelloTask() {} public void run() { long timeBetweenExecution = 5*60*1000; try { // Acquisition of the semaphore executionSemaphore.acquire(); // Business to run in the CRON // .... // .... // .... // .... } catch (Throwable th) { th.printStackTrace(); }finally { try { // Release of the semaphore executionSemaphore.release(); } catch (Throwable e) {e.printStackTrace();} try { // WAITING TIME BETWEEN 2 EXECUTIONS Thread.currentThread().sleep(timeBetweenExecution); } catch (Throwable e) { e.printStackTrace(); } // SUBMIT NEW THREAD taskExecutor.execute(new MyHelloTask()); } }//end-run-method }//end-task }
The TaskExecutor’s rules are configured like simple bean properties:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="1" /> <property name="maxPoolSize" value="1" /> <property name="queueCapacity" value="1" /> </bean> <bean id="myTaskExecutor" class="MyTaskExecutor"> <constructor-arg ref="taskExecutor" /> </bean>
- the Spring’s Scheduler API (JDK Timers with Spring support) is an other way to schedule crons/jobs. So, it is possible to create custom timers or use the timer to invoke methods by using the TimerFactoryBean.
First, we will create customer TimerTask named MyTimerTask (similar to Quartz jobs):
public class MyTimerTask extends TimerTask { private MyService myService; @Override public void run() { try{ myService.doBusiness(); }catch(Throwable e){ e.printStackTrace(); try { Thread.sleep(5 * 60 * 1000); // Sleep 5 minutes } catch (InterruptedException e1) { e1.printStackTrace(); } } } public MyService getMyService() { return myService; } public void setMyService(MyService myService) { this.myService = myService; } }
Then, following the needed Spring’s configuration:
<!-- ################### ScheduledTimerTask & TimerTask ################### --> <bean id="scheduledMyTimerTaskTimerTask" class="org.springframework.scheduling.timer.ScheduledTimerTask"> <!-- wait 1 seconds before starting repeated execution --> <property name="delay" value="1000" /> <!-- run every 90 seconds --> <property name="period" value="90000"/> <property name="timerTask" ref="myTimerTask" /> </bean> <bean id="myTimerTask" class="com.ho.cron.MyTimerTask"> <property name="myService" ref="myService" /> </bean> <!-- ################### TIMER FACTORY ################### --> <bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean"> <property name="scheduledTimerTasks"> <list> <ref bean="scheduledMyTimerTaskTimerTask" /> </list> </property> </bean>
- the last way is also based on the Spring’s Scheduler API via the component MethodInvokingTimerTaskFactoryBean. Indeed, Spring comes with a MethodInvokingTimerTaskFactoryBean that allows the definition of the target scheduler object and method to call here.
First, we will create a task named MyTimerTask (similar to Quartz jobs):
public class MyTimerTask{ private MyService myService; public void theMethodToRun() { try{ myService.doBusiness(); }catch(Throwable e){ e.printStackTrace(); try { Thread.sleep(5 * 60 * 1000); // Sleep 5 minutes } catch (InterruptedException e1) { e1.printStackTrace(); } } } public MyService getMyService() { return myService; } public void setMyService(MyService myService) { this.myService = myService; } }
Then, following the needed Spring’s configuration:
<!-- ################### ScheduledTimerTask & TimerTask ################### --> <bean id="scheduledMyTimerTaskTimerTask" class="org.springframework.scheduling.timer.ScheduledTimerTask"> <!-- wait 1 seconds before starting repeated execution --> <property name="delay" value="1000" /> <!-- run every 90 seconds --> <property name="period" value="90000"/> <property name="timerTask" ref="myTimerTask" /> </bean> <bean id="myTimerTask" class="com.ho.cron.MyTimerTask"> <property name="myService" ref="myService" /> </bean> <!-- ################### TIMER FACTORY ################### --> <bean id="schedulerTask" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean"> <property name="targetObject" ref="myTimerTask" /> <property name="targetMethod" value="theMethodToRun" /> </bean>
That’s all!!!!
Best regards,
Huseyin OZVEREN
Source: http://static.springsource.org/spring/docs/2.5.x/reference/scheduling.html