Quartz Overview
Quartz is an open-source project specializing in job scheduling. It can be used standalone or integrated with the Spring framework, with the latter being the preferred approach in production environments. Quartz enables development of single or multiple scheduled tasks, each with configurable execution patterns such as hourly execution, monthly execution at specific times, or end-of-month execution.
Official website: http://www.quartz-scheduler.org/
Maven dependencies:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
Basic Quartz Integration Example
This example demonstrates integrating Quartz with Spring. The implementation steps are as follows:
Step 1: Create a Maven project and add Quartz and Spring dependencies
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>scheduler-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
</dependencies>
</project>
Step 2: Create a custom job class
package com.example.scheduler;
public class ScheduledTask {
public void execute() {
System.out.println("Scheduled task is running...");
}
}
Step 3: Create Spring configuration file (scheduler-config.xml) to configure the job, job detail, trigger, and scheduler factory
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Register custom job bean -->
<bean id="scheduledTask" class="com.example.scheduler.ScheduledTask"></bean>
<!-- Register JobDetail for reflection-based job invocation -->
<bean id="taskJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="scheduledTask"/>
<property name="targetMethod" value="execute"/>
</bean>
<!-- Register trigger with cron expression -->
<bean id="taskTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="taskJobDetail"/>
<property name="cronExpression">
<value>0/10 * * * * ?</value>
</property>
</bean>
<!-- Register scheduler factory for task management -->
<bean id="schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="taskTrigger"/>
</list>
</property>
</bean>
</beans>
Step 4: Create main class for testing
package com.example.app;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Application {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("scheduler-config.xml");
}
}
Running this application will display output every 10 seconds, confirming the scheduled task executes as configured.
Cron Expression Syntax
The expression 0/10 * * * * ? used above is a cron expression that precisely defines execution timing. Cron expressions consist of seven fields separated by spaces, with the last field (year) being optional. Each field has specific allowed values and special characters.
Special characters explained:
-
Comma (,): Specifies a list of values, e.g.,
1,4,5,7in the month field means January, April, May, and July -
Hyphen (-): Specifies a range, e.g.,
3-6in the hour field means 3am through 6am -
Asterisk (*): Includes all valid values for the field, e.g.,
*in month means every month -
Slash (/): Specifies increments, e.g.,
0/15in seconds means every 15 seconds -
Question mark (?): Used only in day-of-month and day-of-week fields, cannot be used in both simultaneously, indicates no specific value
-
Hash (#): Used only in day-of-week field to specify which week of the month, e.g.,
6#3means the third Friday of the month -
L: Represents the last value, used only in day-of-month and day-of-week. In day-of-month, it means the last day of the specified month. In day-of-week, it means Saturday
-
W: Represents weekdays (Monday through Friday), used only in day-of-month, specifies the nearest weekday to the specified date
Online Cron Expression Generators
Writing cron expressions manulaly can be challenging. Online tools are available to generate expressions based on requirements:
Practical Application: Scheduled Cleanup of Orphan Images
In a health management system, package information and images are submitted separately when adding new packages. Users upload images to cloud storage first, then submit additional information. If users upload images but never complete the submission process, those images become orphan files with no database records.
The solution involves scheduled cleanup of these orphan images. The approach uses Redis sets to track uploaded images: one set stores all uploaded images, another set stores images associated with saved database records. The difference between these sets identifies orphan images.
This section implements a Quartz-based scheduled task to identify and remove orphan images by calculating the Redis set difference.
Implementaiton steps:
-
Create a Maven project with war packaging, add Quartz and related dependencies
-
Create the orphan image cleanup job class
package com.example.jobs;
import com.example.constant.RedisKeys;
import com.example.util.CloudStorageUtil;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.JedisPool;
import java.util.Set;
public class OrphanImageCleanupJob {
@Autowired
private JedisPool jedisPool;
public void cleanupOrphanImages() {
Set<String> orphanImages =
jedisPool.getResource().sdiff(RedisKeys.UPLOADED_IMAGES,
RedisKeys.PERSISTED_IMAGES);
if (orphanImages != null) {
for (String imageName : orphanImages) {
CloudStorageUtil.removeFile(imageName);
jedisPool.getResource().srem(RedisKeys.UPLOADED_IMAGES, imageName);
}
}
}
}
The job compares the two Redis sets, identifies images that exist in the uploaded set but not in the persisted set, removes them from the cloud storage, and updates the Redis tracking sets accordingly.