在web项目中,我们经常会遇到一些需要定时执行的任务,比如定时从某个服务器上下载文件、定时删除服务器上的某些文件、定时发送一些消息等等的操作,都需要定时任务。这里在springboot项目中使用到的一个定时任务的框架Quartz。这也是我在项目中使用到的定时任务框架,下面对该框架做一个简单的使用简介。
简而言之,Quartz是一种任务调度计划,它是由OpenSymphony提供的、开源的、java编写的强大任务调度框架。不管是小型项目,还是大型项目,集群项目,Quartz都可以完美地解决其中的任务调度计划问题。
springboot整合Quartz
首先在pom文件中添加Quartz的starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
Quartz关键概念
Quartz主要有两个核心组件,一个是Job,另一个是Trigger。Job表示要执行的任务是什么,也就是要被调度的任务。Trigger表示什么时候触发该任务。在这里不去过多追究源码,我们先把框架用起来再说。那么在代码中怎么实现呢,下面我以一个定时下载的任务为例说明Quartz的使用。我们先创建两个核心组件的类,一个是关于Job的,一个是关于Trigger的。
创建任务 Job
我在项目下创建任务的包,在包下创建Job类。
这里我创建EphDWJob类,用来定时从服务器上下载文件。
package meicius.ori.quartzJobs;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class EphDWJob implements Job {
Logger logger = LoggerFactory.getLogger(EphDWJob.class);
String ephDownLoadUrlPrefix;
/**
* 要下载的文件名
* **/
String ephFileName;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
this.executeMain();
}
public void executeMain(){
try {
FtpDownload.downLoadFtpFile(ephDownLoadUrlPrefix, 21, "anonymous", "");
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们创建的Job要实现Quartz中Job接口,该接口中只有一个execute()方法,就是在这函数中执行我们的任务。
创建 Trigger
为了区分,我在项目下创建关于Trigger的包,在包下创建Trigger类。
接下来在该包下创建Trigger类。在这里完成任务调度创建和启动任务调度。我在类的构造函数中完成任务调度的创建,把任务调度的创建和任务调度的启动分开。
package meicius.ori.quartzScheduler;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class EphDWScheduler {
Logger logger = LoggerFactory.getLogger(EphDWScheduler.class);
public SchedulerFactory schedulerFactory;
public Scheduler scheduler;
public JobDetail jobDetail;
public CronTrigger cronTrigger;
public TriggerKey triggerKey;
public JobKey jobKey;
/**
* 在构造函数中创建调度器,加载任务
* **/
public EphDWScheduler() throws SchedulerException {
//1 创建调度器
this.schedulerFactory = new StdSchedulerFactory();
this.scheduler = schedulerFactory.getScheduler();
//2 创建JobDetail实例,并与PrintWordsJob类绑定(Job执行的内容)
this.jobDetail = JobBuilder.newJob(EphDWJob.class)
.withIdentity("ephJobDetailDataDownLoadIdentity", "ephJobDetailDataDownLoadIdentityGroup1")
.usingJobData("ephJobDetailDataDownLoadJobData", "ephJobDetailDataDownLoadValue")
.build();
//3 构建触发trigger实例
/**
* corn 表达式
* **/
this.cronTrigger = TriggerBuilder.newTrigger()
.withIdentity("ephTriggerDownLoadIdentity", "ephTriggerDownLoadIdentityGroup1")
.usingJobData("ephTriggerDownLoadJobData","ephTriggerDownLoadDataValue")
.withSchedule(CronScheduleBuilder.cronSchedule("0 15 */2 * * ?")) //每隔两个小时的第15分钟执行一次
.build();
this.triggerKey = TriggerKey.triggerKey("ephTriggerDownLoadIdentity", "ephTriggerDownLoadIdentityGroup1");
this.jobKey = JobKey.jobKey("ephJobDetailDataDownLoadIdentity", "ephJobDetailDataDownLoadIdentityGroup1");
}
/**
* 调度执行
* **/
public String startJob(JobDetail jobDetail, CronTrigger cronTrigger) throws SchedulerException {
try{
Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
return "下载任务启动成功";
}catch (Exception e){
return "下载任务启动失败" + e.getMessage();
}
}
}
这里需要做的是
1)先创建任务调度器工厂schedulerFactory,然后再创建任务调度器scheduler。
2)创建任务详情JobDetail,将要被调度的任务添加进来。这里定义了在Quartz中任务所在的组名称和任务名称,供接下来调度使用。
3)创建trigger实例,也就是如何去调度,比如多久调度一次,什么时候开始调度等等。这里需要编写corn 表达式,就是用这个表达式来表明如何调度,Quartz提供了完美的表达式书写规则,可以满足任何的调度规则。这里写几个我用到的调度规则
每个小时的第0,5,10,15,20,25,30,35,40,45,50,55分钟的第0秒执行
(CronScheduleBuilder.cronSchedule("0 0,5,10,15,20,25,30,35,40,45,50,55 * * * ?"))
每10分钟执行一次
(CronScheduleBuilder.cronSchedule("* */10 * * * ?"))
每3秒钟执行一次
(CronScheduleBuilder.cronSchedule("*/3 * * * * ?"))
每隔两个小时的第15分钟执行一次
(CronScheduleBuilder.cronSchedule("0 15 */2 * * ?"))
每隔1分钟的第10秒钟执行一次
(CronScheduleBuilder.cronSchedule("10 */1 * * * ?"))
每3秒钟执行一次
(CronScheduleBuilder.cronSchedule("*/3 * * * * ?"))
4)启动调度任务,根据任务所在组和任务名称
至此,就完成了一个Quartz的简单任务调度。
值得一说的是,在Quartz的Job中是无法注入Spring容器中的类的。这是因为Job是在Quartz框架中,Job实现类不接受Spring容器的管理。这里有两种方法解决此问题,一种是使用Quartz提供的JobFactory接口,就可以自定义实现创建Job的逻辑,并将jobFactory交给spring容器管理。另一种是直接从Spring容器中根据类或者类名查找要使用的类。我在项目中就使用的是第二种方法。
Spring容器中bean的获取
创建一个类,用来获取容器中的类。
package meicius.ori.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextUtil.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext(){
return applicationContext;
}
public static Object getBean(String beanName){
return applicationContext.getBean(beanName);
};
//通过class获取Bean.
public static <T> T getBeanByClass(Class<T> clazz){
return applicationContext.getBean(clazz);
}
}
这样就可以完美地在Spring中使用Quartz了。