一、首先了解一下Quartz
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。
二、Quartz的三大核心组件
调度器:Scheduler。
****任务:JobDetail。
**触发器:**Trigger,包括 SimpleTrigger 和 CronTrigger。
(1)**Job(任务):**是一个接口,有一个方法 void execute(JobExecutionContext context) ,可以通过实现该接口来定义需要执行的任务(具体的逻辑代码)。
JobDetail:Quartz每次执行Job时,都重新创建一个Job实例,会接收一个Job实现类,以便运行的时候通过newInstance()的反射调用机制去实例化Job。JobDetail是用来描述Job实现类以及相关静态信息,比如任务在scheduler中的组名等信息。
(2)Trigger(触发器):描述触发Job执行的时间触发规则实现类SimpleTrigger和CronTrigger可以通过crom表达式定义出各种复杂的调度方案。
Calendar:是一些日历特定时间的集合。一个Trigger可以和多个 calendar关联,比如每周一早上10:00执行任务,法定假日不执行,则可以通过calendar进行定点排除。
(3)Scheduler(调度器):代表一个Quartz的独立运行容器。Trigger和JobDetail可以注册到Scheduler中。Scheduler可以将Trigger绑定到某一JobDetail上,这样当Trigger被触发时,对应的Job就会执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。
三、简单实现
引入依赖:
<!-- SpringBoot 整合 Quartz 定时任务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
package com.pjb.job;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 同步用户信息Job
* @author pan_junbiao
**/
public class SyncUserJob extends QuartzJobBean
{
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext)
{
//获取JobDetail中传递的参数
String userName = (String) jobExecutionContext.getJobDetail().getJobDataMap().get("userName");
String blogUrl = (String) jobExecutionContext.getJobDetail().getJobDataMap().get("blogUrl");
String blogRemark = (String) jobExecutionContext.getJobDetail().getJobDataMap().get("blogRemark");
//获取当前时间
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//打印信息
System.out.println("用户名称:" + userName);
System.out.println("博客地址:" + blogUrl);
System.out.println("博客信息:" + blogRemark);
System.out.println("当前时间:" + dateFormat.format(date));
System.out.println("----------------------------------------");
}
}
package com.pjb.config;
import com.pjb.job.SyncUserJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Quartz定时任务配置类
* @author pan_junbiao
**/
@Configuration
public class QuartzConfig
{
private static String JOB_GROUP_NAME = "PJB_JOBGROUP_NAME";
private static String TRIGGER_GROUP_NAME = "PJB_TRIGGERGROUP_NAME";
/**
* 定时任务1:
* 同步用户信息Job(任务详情)
*/
@Bean
public JobDetail syncUserJobDetail()
{
JobDetail jobDetail = JobBuilder.newJob(SyncUserJob.class)
.withIdentity("syncUserJobDetail",JOB_GROUP_NAME)
.usingJobData("userName", "pan_junbiao的博客") //设置参数(键值对)
.usingJobData("blogUrl","https://blog.csdn.net/pan_junbiao")
.usingJobData("blogRemark","您好,欢迎访问 pan_junbiao的博客")
.storeDurably() //即使没有Trigger关联时,也不需要删除该JobDetail
.build();
return jobDetail;
}
/**
* 定时任务1:
* 同步用户信息Job(触发器)
*/
@Bean
public Trigger syncUserJobTrigger()
{
//每隔5秒执行一次
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
//创建触发器
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(syncUserJobDetail())//关联上述的JobDetail
.withIdentity("syncUserJobTrigger",TRIGGER_GROUP_NAME)//给Trigger起个名字
.withSchedule(cronScheduleBuilder)
.build();
return trigger;
}
}
执行结果:
四、在若依中实现
对应后台代码:
controller:
/**
* 新增保存调度
*/
@Log(title = "定时任务", businessType = BusinessType.INSERT)
@RequiresPermissions("monitor:job:add")
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(@Validated SysJob job) throws SchedulerException, TaskException
{
if (!CronUtils.isValid(job.getCronExpression()))
{
return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
}
else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
}
else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
}
job.setCreateBy(getLoginName());
return toAjax(jobService.insertJob(job));
}
对应实现;
/**
* 新增任务
*
* @param job 调度信息 调度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int insertJob(SysJob job) throws SchedulerException, TaskException
{
job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
int rows = jobMapper.insertJob(job);
if (rows > 0)
{
ScheduleUtils.createScheduleJob(scheduler, job);
}
return rows;
}
具体看这个方法:
ScheduleUtils.createScheduleJob(scheduler, job);
具体进入getQuartzJobClass方法:
这里分了两个类,一个是可以异步执行,另一个是不可以异步执行(也就是同一个job对象,不能同时进行,需要等待,一般不会这么用);
其实两个类基本一样,都是继承了AbstractQuartzJob类(这个类实现了Job,指向具体干什么,也就是实现Job的doExecute()方法),不同之处就是禁止并发使用了@DisallowConcurrentExecution注解;
这个invokeMethod()方法不说了,就是用反射,执行我们指定的类、方法;
整体流程就是这么简单,具体的暂停、激活,使用scheduler类中的方法,结合我们定义的jobKey去操作,类似于下面:
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
五、程序启动时加载数据库中的定时任务
就是使用@PostConstruct,在springboot启动后立刻执行方法:
/**
* 项目启动时,初始化定时器
* 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
*/
@PostConstruct
public void init() throws SchedulerException, TaskException
{
scheduler.clear();
List<SysJob> jobList = jobMapper.selectJobAll();
for (SysJob job : jobList)
{
ScheduleUtils.createScheduleJob(scheduler, job);
}
}
引入别人的图片: