一、首先了解一下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
{@Overrideprotected 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(任务详情)*/@Beanpublic 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(触发器)*/@Beanpublic 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")@ResponseBodypublic 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和任务组名,否则会导致脏数据)*/@PostConstructpublic void init() throws SchedulerException, TaskException{scheduler.clear();List<SysJob> jobList = jobMapper.selectJobAll();for (SysJob job : jobList){ScheduleUtils.createScheduleJob(scheduler, job);}}
引入别人的图片: