前言:最近学习了目前主流的若依框架,这是一个非常优秀的开源项目,故此我这边把它的源码全部剖析了一遍,简化了它的框架,我会通过这篇博客详细讲解我是如何进行推敲和从0到1搭建这个项目的流程。
目录
一、Quartz简介
二、项目整体结构图
三、代码实现
3.1、导入pom.xml依赖
3.2、定时任务实体类
3.3、撰写定时任务调度信息接口和实现类
3.4、撰写定时任务工具类
3.5、撰写AbstractQuartzJob抽象类
3.6、撰写开启/禁止并发执行任务处理类
3.7、撰写执行定时任务方法类和获取Bean的工具类
四、运行测试
4.1、撰写任务一
4.2、撰写任务二
4.3、启动
五、Gitee源码地址
六、总结
一、Quartz简介
Quartz是一个完全由Java编写的开源作业调度框架,在Java应用程序中进行作业调度提供了强大功能,以下是Quartz的四个核心概念。
1、Job(接口):它只有一个execute方法需要被重写,重写的内容就是咱们需要执行的具体内容。
2、JobDetail(调度信息):表示一个具体的可执行的调度程序,Job是这个可执行调度程序中所需要执行的具体内容,另外JobDetail还包含了这个任务的调度方案和策略。
3、Trigger(触发器):代表一个调度参数的配置,动态去执行咱们的定时任务。
4、Scheduler(任务调度器):Scheduler就是任务调度控制器,需要把JobDetail和Trigger注册到Schedule中,才可以执行。
咱们废话不多说,直接上代码!
二、项目整体结构图
这边我先把我整个从0到1搭建好的框架先展示出来。
三、代码实现
这边我会详细阐述这个项目是如何完整搭建起来的,跟着文章的步骤一步步走就可以了。
3.1、导入pom.xml依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--quartz定时任务依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><!--常用工具类 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><!--lombok依赖 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
3.2、定时任务实体类
package com.ithuang.quartz.domain;import lombok.Data;/*** 定时任务调度表 sys_job** @author HTT*/
@Data
public class SysJob
{/*** 定时任务ID*/private String jobId;/*** 定时任务名称*/private String jobName;/*** 定时任务组*/private String jobGroup;/*** 目标bean名*/private String beanTarget;/*** 目标bean的方法名*/private String beanMethodTarget;/*** 执行表达式*/private String cronExpression;/*** 是否并发*/private String concurrent;}
3.3、撰写定时任务调度信息接口和实现类
撰写定时任务调度的接口,主要分为初始化、新增、更新、暂停,恢复和删除定时任务,代码如下,这个不多说,主要详细讲解一下它的实现类。
package com.ithuang.quartz.service;import com.ithuang.quartz.domain.SysJob;
import org.quartz.SchedulerException;import java.lang.reflect.InvocationTargetException;/*** 定时任务调度信息* @author HTT*/
public interface SysJobService {/*** 项目启动时,初始化定时器*/void init() throws SchedulerException, NoSuchMethodException, InvocationTargetException, IllegalAccessException;/*** 新增任务** @param job 调度信息* @return 结果*/public int insertJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException;/*** 更新任务** @param job 调度信息* @return 结果*/public int updateJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException;/*** 暂停任务** @param job 调度信息* @return 结果*/public int pauseJob(SysJob job) throws SchedulerException;/*** 恢复任务** @param job 调度信息* @return 结果*/public int resumeJob(SysJob job) throws SchedulerException;/*** 删除任务后,所对应的trigger也将被删除** @param job 调度信息* @return 结果*/public int deleteJob(SysJob job) throws SchedulerException;}
如下代码是上述接口的实现类:
1、首先initTaskList这个方法我这边做了偷懒,直接写了一个List集合在项目中,正确的配置应该是从数据库去查询一共有哪些定时任务需要程序去执行的,以及对应的配置信息应该如何去执行。
2、其次在init方法中,@PostConstruct这个注解会在项目启动的时候帮我们进行初始化,其次我调用了clear方法进行清空定时任务,最后使用循环获取到需要所有定时任务集合,通过createScheduleJob方法去创建,接下来我就会详细讲解createScheduleJob方法中的代码逻辑是如何实现的。
3、剩下的方法就是新增一个任务、修改一个任务,暂停一个任务,恢复一个任务和删除一个任务,这边不再多做阐述。
package com.ithuang.quartz.service.impl;import com.ithuang.quartz.domain.SysJob;
import com.ithuang.quartz.service.SysJobService;
import com.ithuang.quartz.utils.ScheduleUtils;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;/*** 定时任务调度服务* @author HTT*/
@Service
public class SysJobServiceImpl implements SysJobService {@Resourceprivate Scheduler scheduler;/*** 模拟从数据库获取数据,这边偷懒了* @return*/public List<SysJob> initTaskList(){List<SysJob> list = new ArrayList<>();SysJob job = new SysJob();job.setJobId(UUID.randomUUID().toString());job.setJobGroup("system");job.setConcurrent("1");job.setCronExpression("0/5 * * * * ?");job.setBeanTarget("task1");job.setBeanMethodTarget("handle");list.add(job);job = new SysJob();job.setJobId(UUID.randomUUID().toString());job.setJobGroup("system");job.setConcurrent("1");job.setCronExpression("0/10 * * * * ?");job.setBeanTarget("task2");job.setBeanMethodTarget("handle");list.add(job);return list;}/*** 初始化定时任务* @throws SchedulerException* @throws NoSuchMethodException* @throws InvocationTargetException* @throws IllegalAccessException*/@PostConstruct@Overridepublic void init() throws SchedulerException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {scheduler.clear();List<SysJob> list = initTaskList();for (int i = 0 ; i < list.size() ; i ++){SysJob job = list.get(i);ScheduleUtils.createScheduleJob(scheduler, job);}}@Overridepublic int insertJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {ScheduleUtils.createScheduleJob(scheduler, job);return 1;}@Overridepublic int updateJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {// 判断是否存在JobKey jobKey = JobKey.jobKey(job.getJobId(),job.getJobGroup());if (scheduler.checkExists(jobKey)) {scheduler.deleteJob(jobKey);}ScheduleUtils.createScheduleJob(scheduler, job);return 1;}@Overridepublic int pauseJob(SysJob job) throws SchedulerException {scheduler.pauseJob(JobKey.jobKey(job.getJobId(),job.getJobGroup()));return 1;}@Overridepublic int resumeJob(SysJob job) throws SchedulerException {scheduler.resumeJob(JobKey.jobKey(job.getJobId(),job.getJobGroup()));return 1;}@Overridepublic int deleteJob(SysJob job) throws SchedulerException {scheduler.deleteJob(JobKey.jobKey(job.getJobId(),job.getJobGroup()));return 1;}}
3.4、撰写定时任务工具类
这边我一共封装了两个方法,getQuartzJobClass这个方法是主要通过sysJob实体类当中会通过动态参数获取任务类并决定当前任务是否并发执行(0代表并发执行,1代表禁止并发执行),最后createScheduleJob方法也就是我们最主要的方法了,它的逻辑总共6个步骤如下:
1、得到任务类(是否并发执行)
2、构建job信息
3、构件表达式调度构建器
4、按新的cronExpression表达式构建一个新的trigger
5、放入参数,运行时的方法可以获取
6、执行调度任务
package com.ithuang.quartz.utils;import com.ithuang.quartz.domain.QuartzDisallowConcurrentExecution;
import com.ithuang.quartz.domain.QuartzJobExecution;
import com.ithuang.quartz.domain.SysJob;
import org.quartz.*;
import org.springframework.stereotype.Component;import java.lang.reflect.InvocationTargetException;/*** 定时任务工具类* @author HTT*/
@Component
public class ScheduleUtils {/*** 得到quartz任务类** @param sysJob 执行计划* @return 具体执行任务类*/private static Class<? extends Job> getQuartzJobClass(SysJob sysJob){boolean isConcurrent = "0".equals(sysJob.getConcurrent());return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;}/*** 创建定时任务*/public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {Class<? extends Job> jobClass = getQuartzJobClass(job);// 构建job信息String cornExpression = job.getCronExpression();JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(job.getJobId(),job.getJobGroup()).build();// 表达式调度构建器CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cornExpression);// 按新的cronExpression表达式构建一个新的triggerCronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobId(),job.getJobGroup()).withSchedule(cronScheduleBuilder).build();// 放入参数,运行时的方法可以获取jobDetail.getJobDataMap().put("param", job);// 执行调度任务scheduler.scheduleJob(jobDetail, trigger);}}
3.5、撰写AbstractQuartzJob抽象类
大家看到这肯定会很好奇上面的getQuartzJobClass方法它是如何动态获取对应的任务类的,这边我们首先定义了一个抽象类并实现了Job接口,同时会重写execute方法,这个方法是定时任务执行的核心方法,其次在这个抽象类当中我们还定义了一个doExecute方法,主要就是为了写我们定时任务的执行逻辑,这边巧妙的用到了Java的设计模式-模板方法模式,关于什么是模板方法模式,可以看我前几期的博客。
package com.ithuang.quartz.domain;import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;import java.lang.reflect.InvocationTargetException;/*** 抽象quartz调用* 这里我采用了设计模式中的模板方法模式* @author HTT*/
public abstract class AbstractQuartzJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {try {doExecute(jobExecutionContext);} catch (Exception e) {throw new RuntimeException(e);}}protected abstract void doExecute(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException;
}
3.6、撰写开启/禁止并发执行任务处理类
这边我们继承了上述的抽象类,通过@DisallowConcurrentExecution这个注解来禁止咱们的定时任务的并发执行,如果不加则是默认允许并发执行,最后我们重写了doExecute方法去执行我们定时任务的处理逻辑。
不允许并发执行:
package com.ithuang.quartz.domain;import com.ithuang.quartz.utils.SpringUtils;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;import static com.ithuang.quartz.utils.JobExecuteUtils.executeMethod;/*** 定时任务处理(禁止并发执行)* @author HTT*/
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
{@Overrideprotected void doExecute(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {executeMethod(jobExecutionContext);}
}
允许并发执行:
package com.ithuang.quartz.domain;import com.ithuang.quartz.utils.JobExecuteUtils;
import com.ithuang.quartz.utils.SpringUtils;
import org.quartz.JobExecutionContext;
import org.springframework.beans.BeanUtils;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;import static com.ithuang.quartz.utils.JobExecuteUtils.executeMethod;
import static org.springframework.beans.BeanUtils.*;/*** 定时任务处理(允许并发执行)* @author HTT*/
public class QuartzJobExecution extends AbstractQuartzJob
{@Overrideprotected void doExecute(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {executeMethod(jobExecutionContext);}
}
3.7、撰写执行定时任务方法类和获取Bean的工具类
这边我们就对上述的doExecute中的方法进行讲解,逻辑如下:
1、我们通过jobExecutionContext.getMergedJobDataMap().get("param");这个方法去获取咱们的SysJob参数信息。
2、默认获取的是Object类型,所以我们需要通过BeanUtils.copyProperties(param,sysJob);这个方法进行实体类的转换和拷贝,注意参数顺序别写反了。
3、然后我们通过SpringUtils封装好的getBean方法,通过拷贝好的实体类中的参数去动态进行获取被Spring托管的Bean。
4、获取到Bean之后通过 bean.getClass().getMethod(sysJob.getBeanMethodTarget());这个代码去获取Bean中需要执行的方法。
5、最后通过method.invoke(bean);进行执行Bean中对应的方法内容。
package com.ithuang.quartz.utils;import com.ithuang.quartz.domain.SysJob;
import org.quartz.JobExecutionContext;
import org.springframework.beans.BeanUtils;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** 执行定时任务的方法* @author HTT*/
public class JobExecuteUtils {/*** 获取bean并执行对应的方法* @param jobExecutionContext* @throws NoSuchMethodException* @throws InvocationTargetException* @throws IllegalAccessException*/public static void executeMethod(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {Object param = jobExecutionContext.getMergedJobDataMap().get("param");SysJob sysJob = new SysJob();BeanUtils.copyProperties(param,sysJob);Object bean = SpringUtils.getBean(sysJob.getBeanTarget());Method method = bean.getClass().getMethod(sysJob.getBeanMethodTarget());method.invoke(bean);}
}
获取Bean的工具类
package com.ithuang.quartz.utils;import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;/*** 获取Bean的工具类* @author HTT*/
@Component
public final class SpringUtils implements BeanFactoryPostProcessor
{/** Spring应用上下文环境 */private static ConfigurableListableBeanFactory beanFactory;@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{SpringUtils.beanFactory = beanFactory;}/*** 获取对象** @param name* @return Object 一个以所给名字注册的bean的实例* @throws BeansException**/@SuppressWarnings("unchecked")public static <T> T getBean(String name) throws BeansException{return (T) beanFactory.getBean(name);}}
四、运行测试
以上就是整个定时任务框架实现的核心逻辑,这边我们直接运行测试一下.
4.1、撰写任务一
task1就是我们任务一Bean的名称,handle就是任务二需要执行的具体方法。
package com.ithuang.quartz.task;import org.springframework.stereotype.Component;import java.util.Date;@Component("task1")
public class Task1 {public void handle() throws InterruptedException {Date date = new Date();System.out.println("task1"+date+"开始");Thread.sleep(10000);System.out.println("task1"+date+"结束");}}
4.2、撰写任务二
task2就是我们任务二Bean的名称,handle就是任务二需要执行的具体方法。
package com.ithuang.quartz.task;import org.springframework.stereotype.Component;import java.util.Date;@Component("task2")
public class Task2 {public void handle(){Date date = new Date();System.out.println("task2"+date+"开始");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("task2"+date+"结束");}}
4.3、启动
这边我默认采用并发执行了,反正参数都可以动态进行配置 。
这是不采用并发执行的效果。
代码运行成功!
五、Gitee源码地址
springboot整合quartz定时任务框架
六、总结
这边就是我自己从0到1搭建了一个比较成熟的定时任务框架,参考的是目前主流的若依框架,我在他的框架上做了一些简化,不过也能应付大部分的场景了,非常的方便,功能也非常完善,整个项目的源码已经全部吃透,这边做一个总结,收获满满!