Tạo scheduler Jobs trong Spring sử dụng Quartz Scheduler

Trong bài này tôi sẽ giới thiệu cách tạo scheduler Jobs trong Spring sử dụng Quartz Scheduler.

Quartz_Logo_large.jpg

Công cụ và môi trường phát triển:

  • Spring 4.0.6.RELEASE
  • Quartz 2.2.1
  • Maven 3
  • JDK 1.8
  • Eclipse Neon Release (4.6.0)

Cấu trúc project:

Screenshot from 2016-10-31 01:27:41.png

1. Thư viện sử dụng trong project

    <properties>
		<springframework.version>4.0.6.RELEASE</springframework.version>
		<quartz.version>2.2.1</quartz.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>${springframework.version}</version>
		</dependency>
		<!-- Transaction dependency is required with Quartz integration -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${springframework.version}</version>
		</dependency>

		<!-- Quartz framework -->
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
			<version>${quartz.version}</version>
		</dependency>
    </dependencies>

2. Configure Jobs trong Quartz Scheduler

Có 2 cách để configure job trong Spring Quartz

  • Sử dụng MethodInvokingJobDetailFactoryBean

Đây là cách đơn giản nhất trong 2 cách để gọi 1 method đối với 1 bean cụ thể

 <!-- For times when you just need to invoke a method on a specific object -->
	<bean id="simpleJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
		<property name="targetObject" ref="firstBean" />
		<property name="targetMethod" value="printMessage" />
	</bean>

Trong job configuration trên ta đơn giản chỉ gọi printMessage method trong firstBean.

  • Sử dụng JobDetailFactoryBean

Đây là cách làm khi ta cần thiết lập nâng cao cho việc setting job, pass data một cách flexible cho job.

<!-- For times when you need more complex processing, passing data to the scheduled job -->
	<bean name="complexJobDetail" 	class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
		<property name="jobClass" value="demo.spring.quartz.ScheduledJob" />
		<property name="jobDataMap">
			<map>
				<entry key="secondBean" value-ref="secondBean" />
			</map>
		</property>
		<property name="durability" value="true" />
	</bean>

Trong configuration trên ta đã configure jobClass refer tới cách extends QuartzJobBean, implement Quartz job interface. Trong cách gọi job này, method executeInternal đã được gọi. jobDataMap cho phép ta truyền data cho bean job. Trong trường hợp này, ta đã truyền secondBean sễ sử dụng bởi ScheduledJob.

Dưới đây là referred jobclass cài đặt ScheduledJob

package demo.spring.quartz;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import demo.spring.scheduling.SecondBean;

public class ScheduledJob extends QuartzJobBean {

	private SecondBean secondBean;

	@Override
	protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
		secondBean.printAnotherMessage();
	}

	public void setAnotherBean(SecondBean secondBean) {
		this.secondBean = secondBean;
	}
}

3. Configure Triggers cho việc sử dụng Quartz Scheduler

Trigger xác dịnh khoảng thời gian cho 1 scheduler job. Ta có 2 cách để kích hoạt trigger:

  • Simple Trigger : Các đơn giản nhất là sử dụng SimpleTriggerFactoryBean

Chúng ta có thể chỉ cụ thể về thời gian bắt đầu, khoảng thời gian delay giữa các triggers và khoảng thời gian lập lại 1 job.

	<!-- Run the job every 2 seconds with initial delay of 1 second -->
	<bean id="simpleTrigger"  class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
		<property name="jobDetail" ref="simpleJobDetail" />
		<property name="startDelay" value="1000" />
		<property name="repeatInterval" value="2000" />
	</bean>
  • Cron Trigger : sử dụng CronTriggerFactoryBean

Cách này flexible hơn cách 1. Nó cho phép ta chọn 1 intance job cụ thể (time, day, date...) và tần suất trong tương lai.

<!-- Run the job every 5 seconds only on weekends -->
	<bean id="cronTrigger"  class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
		<property name="jobDetail" ref="complexJobDetail" />
		<property name="cronExpression" value="0/5 * * ? * SAT-SUN" />
	</bean>

4. Configure SchedulerFactoryBean để tạo và configure Quartz Scheduler

SchedulerFactoryBean kết gắn giữa 2 thành phần jobDetails và triggers để configure Quartz Scheduler

<!-- Scheduler factory bean to glue together jobDetails and triggers to Configure Quartz Scheduler -->
	<bean  class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="jobDetails">
			<list>
				<ref bean="simpleJobDetail" />
				<ref bean="complexJobDetail" />
			</list>
		</property>

		<property name="triggers">
			<list>
				<ref bean="simpleTrigger" />
				<ref bean="cronTrigger" />
			</list>
		</property>
	</bean>

Dưới đây là file tổng hợp configure quartz demo

<?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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        					http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

	<context:component-scan base-package="demo.spring" />

	<!-- For times when you just need to invoke a method on a specific object -->
	<bean id="simpleJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
		<property name="targetObject" ref="firstBean" />
		<property name="targetMethod" value="printMessage" />
	</bean>

	<!-- For times when you need more complex processing, passing data to the scheduled job -->
	<bean name="complexJobDetail" 	class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
		<property name="jobClass" value="demo.spring.quartz.ScheduledJob" />
		<property name="jobDataMap">
			<map>
				<entry key="secondBean" value-ref="secondBean" />
			</map>
		</property>
		<property name="durability" value="true" />
	</bean>

	<!-- Run the job every 2 seconds with initial delay of 1 second -->
	<bean id="simpleTrigger"  class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
		<property name="jobDetail" ref="simpleJobDetail" />
		<property name="startDelay" value="1000" />
		<property name="repeatInterval" value="2000" />
	</bean>

	<!-- Run the job every 5 seconds only on weekends -->
	<bean id="cronTrigger"  class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
		<property name="jobDetail" ref="complexJobDetail" />
		<property name="cronExpression" value="0/5 * * ? * SAT-SUN" />
	</bean>

	<!-- Scheduler factory bean to glue together jobDetails and triggers to Configure Quartz Scheduler -->
	<bean  class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="jobDetails">
			<list>
				<ref bean="simpleJobDetail" />
				<ref bean="complexJobDetail" />
			</list>
		</property>

		<property name="triggers">
			<list>
				<ref bean="simpleTrigger" />
				<ref bean="cronTrigger" />
			</list>
		</property>
	</bean>

</beans>

5. Tạo các POJO đơn giản cho Task Bean

 package demo.spring.scheduling;

import org.springframework.stereotype.Component;

@Component("firstBean")
public class FirstBean {

	public void printMessage() {
		System.out.println("FirstBean: I am called by MethodInvokingJobDetailFactoryBean using SimpleTriggerFactoryBean");
	}

}
package demo.spring.scheduling;

import org.springframework.stereotype.Component;

@Component("secondBean")
public class SecondBean {

	public void printAnotherMessage(){
		System.out.println("secondBean: I am called by Quartz jobBean using CronTriggerFactoryBean");
	}

}

6. Tạo Main và Run application

 package demo.spring;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AppMain {
	public static void main(String args[]){
		AbstractApplicationContext context = new ClassPathXmlApplicationContext("quartz-context.xml");
	}

}

Run application, output:

INFO: Starting Quartz Scheduler now
FirstBean: I am called by MethodInvokingJobDetailFactoryBean using SimpleTriggerFactoryBean
secondBean: I am called by Quartz jobBean using CronTriggerFactoryBean
FirstBean: I am called by MethodInvokingJobDetailFactoryBean using SimpleTriggerFactoryBean
FirstBean: I am called by MethodInvokingJobDetailFactoryBean using SimpleTriggerFactoryBean
secondBean: I am called by Quartz jobBean using CronTriggerFactoryBean
FirstBean: I am called by MethodInvokingJobDetailFactoryBean using SimpleTriggerFactoryBean
FirstBean: I am called by MethodInvokingJobDetailFactoryBean using SimpleTriggerFactoryBean