本文主要包含以下内容:
springboot
项目整合quartz
- 若以定时任务的功能实现
若以定时任务是采用 quartz
实现的,涉及的库表有 quartz.sql
中的表以及 sys_job
表,前者是 springboot
项目整合 quartz
所需要的持久化必备的,后者是自己定义的 job
任务。
springboot 整合 quartz
quartz中的基本概念
job
:表示一个任务、要执行的具体内容。通常由我们去实现 job
接口或者继承 QuartzJobBean
类。
JobDetail
:表示一个具体的可执行的调度程序,Job
是这个可执行程调度程序所要执行的内容,定义了job
的名称、所属组等基本信息,另外 JobDetail
还包含了这个任务调度的方案和策略。
JobDataMap
:
向作业实例传递参数和数据 :通过将数据放入 JobDataMap
,可以将数据传递给job
实例。
在触发器中设置作业相关的数据 :您还可以在 Trigger
中设置 JobDataMap
,这样在触发器触发时,可以将数据传递给作业实例。这使得可以在触发作业之前对作业实例进行配置或准备
Trigger:触发器,触发任务的时刻
Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。他们之间的关系可以用下图来表示:
示例代码:
第一步:创建 springboot
项目并引入依赖。
xml
<!-- 实现对 Quartz 的自动化配置 -->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.7.11</version>
</dependency>
第二步:创建任务,这里以实现job接口为例。
java
@Slf4j
public class QuartzJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String jobMsg = (String) context.getJobDetail().getJobDataMap().get("msg");
String triggerMsg = (String) context.getTrigger().getJobDataMap().get("msg");
log.info("jobMsg:{},triggerMsg:{}", jobMsg, triggerMsg);
}
}
Job
接口中,需要我们实现 execute()
方法,这个方法就是我们执行业务逻辑的地方。
第三步:创建 JobDetail
并配置调度器和触发器
java
@Configuration
@Slf4j
public class QuartzConfig {
private static final String ID = "QUARTZ";
@Bean
public JobDetail jobDetail1() {
return JobBuilder.newJob(QuartzJob.class)
.withIdentity(ID + " 01")
.usingJobData("msg", "Hello Quartz -- jobDetail")
.storeDurably()
.build();
}
@Bean
public Trigger trigger1() {
// 简单的调度计划的构造器
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10) // 频率
.repeatForever(); // 次数
return TriggerBuilder.newTrigger()
.forJob(jobDetail1())
.withIdentity(ID + " 01Trigger")
.usingJobData("msg", "Hello Quartz -- trigger")
.withSchedule(scheduleBuilder)
.build();
}
}
不需要进行其他额外的配置,直接启动项目,即可得到如下的运行日志:
这就是 SpringBoot
中整合 Quzrtz
的简单示例。
若以定时任务实现
若以在项目中也引入的 quartz
来实现定时任务,分析一下在若以中是如何使用的。首先是在数据库中执行quartz.sql
中的语句。
数据库表分析:在若以系统中,表 sys_job
用来持久化系统中任务。sys_job_log
用来记录定时任务的执行。
sys_job
分析:
java
private Long jobId;
/** 任务名称 */
private String jobName;
/** 任务组名 */
private String jobGroup;
/** 调用目标字符串 */
private String invokeTarget;
/** cron执行表达式 */
private String cronExpression;
/** cron计划策略 */
private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT;
/** 是否并发执行(0允许 1禁止) */
private String concurrent;
/** 任务状态(0正常 1暂停) */
private String status;
这里面要额外注意的字段为invokeTarget
和cronExpression
,前者通过反射机制找到对应的执行方法,后者则是任务的调度方式。
在工具类 JobInvokeUtil
中,通过如下代码执行:
java
public static void invokeMethod(SysJob sysJob) throws Exception
{
String invokeTarget = sysJob.getInvokeTarget();
String beanName = getBeanName(invokeTarget);
String methodName = getMethodName(invokeTarget);
List<Object[]> methodParams = getMethodParams(invokeTarget);
if (!isValidClassName(beanName))
{
// 通过bean名称获取bean
Object bean = SpringUtils.getBean(beanName);
invokeMethod(bean, methodName, methodParams);
}
else
{
// 通过类全限定名称的方式获取bean
Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
invokeMethod(bean, methodName, methodParams);
}
}
private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
{
Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
method.invoke(bean, getMethodParamsValue(methodParams));
}
else
{
Method method = bean.getClass().getMethod(methodName);
method.invoke(bean);
}
}
若以中是通过抽象类AbstractQuartzJob
实现接口Job
接口来定义的。通过子类来实现doExecute()
方法。
在 AbstractQuartzJob
类中,execute()
方法实现如下:
java
@Override
public void execute(JobExecutionContext context) throws JobExecutionException
{
SysJob sysJob = new SysJob();
BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
try
{
before(context, sysJob);
if (sysJob != null)
{
doExecute(context, sysJob);
}
after(context, sysJob, null);
}
catch (Exception e)
{
log.error("任务执行异常 - :", e);
after(context, sysJob, e);
}
}
子类中实现 doExecute()
方法:
java
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
{
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
{
System.out.println("禁止并发执行");
JobInvokeUtil.invokeMethod(sysJob);
}
}
public class QuartzJobExecution extends AbstractQuartzJob
{
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
{
System.out.println("任务执行:" + sysJob.getJobName() + ",任务分组:" + sysJob.getJobGroup());
JobInvokeUtil.invokeMethod(sysJob);
}
}
@DisallowConcurrentExecution
注解是quartz
提供的,标识任务是否可以并发执行。在这里完成了第一步 Job
接口的实现。接下来就是 JobDetail
以及调度器和触发器相关的绑定。
在com.ruoyi.quartz.service.impl.SysJobServiceImpl
接口中,定义了如下代码:
java
@PostConstruct
public void init() throws SchedulerException, TaskException
{
scheduler.clear();
List<SysJob> jobList = jobMapper.selectJobAll();
for (SysJob job : jobList)
{
ScheduleUtils.createScheduleJob(scheduler, job);
}
}
在项目启动时,会执行此方法:
- 首先查询
sys_job
表中的所有任务, - 循环查出来的结果,执行
ScheduleUtils.createScheduleJob(scheduler, job);
方法
java
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
{
Class<? extends Job> jobClass = getQuartzJobClass(job);
// 构建job信息
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
// 创建jobDetail
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
// 表达式调度构建器---调度器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
// 按新的cronExpression表达式构建一个新的trigger---触发器
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
.withSchedule(cronScheduleBuilder).build();
// 放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
// 判断是否存在
if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
{
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(getJobKey(jobId, jobGroup));
}
// 判断任务是否过期
if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
{
// 执行调度任务
scheduler.scheduleJob(jobDetail, trigger);
}
// 暂停任务
if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
{
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
}
以上便是若以中是如何实现定时任务的逻辑,除此之外,在新增和删除以及更新相关sys_job
记录时,都有对应的操作。具体的请参看源码。