栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

使用Spring的quartz实现一个简单定时任务调度管理系统,附原理(简单易懂)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

使用Spring的quartz实现一个简单定时任务调度管理系统,附原理(简单易懂)

使用Spring的quartz实现一个简单定时任务调度管理系统

效果图主要功能功能实现使用方法原理总结 1.效果图(前端做的太low,所以结合了swagger2框架调试接口) 1.1:页面效果图 1.2:接口文档图: 2.主要功能

    定时任务状态的启动、停止、恢复、立即执行一次。定时任务执行周期的动态修改。定时任务的增删改。
3.功能实现 3.3:数据库表结构:(暂时没加日志)
CREATE TABLE `job` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `job_name` varchar(255) NOT NULL COMMENT '任务名称',
  `job_group` varchar(255) NOT NULL COMMENT '任务组名',
  `status` int(255) NOT NULL COMMENT '任务状态:0=正常,1=暂停',
  `trigger_name` varchar(255) NOT NULL COMMENT 'trigger名称',
  `trigger_group` varchar(255) NOT NULL COMMENT 'trigger组名',
  `invoke_target` varchar(255) NOT NULL COMMENT '调用目标字符串',
  `concurrent` varchar(255) NOT NULL COMMENT '并发执行 0=允许,1=禁止',
  `cron_expression` varchar(255) NOT NULL COMMENT 'cron执行表达式',
  `misfire_policy` varchar(255) NOT NULL COMMENT '计划策略 0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
3.2:添加依赖

(项目是使用springboot构建,以下只展示主要用到的依赖)

		
        
            org.quartz-scheduler
            quartz
            2.3.2
        
        
            org.springframework
            spring-context-support
            5.3.12
        
        
        
            commons-io
            commons-io
            2.6
        
        
            org.apache.commons
            commons-lang3
            3.10
        
        
        
            io.springfox
            springfox-swagger2
            2.9.2
        
        
            io.springfox
            springfox-swagger-ui
            2.9.2
        
        
        
            com.github.xiaoymin
            swagger-bootstrap-ui
            1.9.6
        
3.2:代码实现

省略Controller、mapper、实体类和接口代码

3.2.1: 任务调度通用常量代码
public class ScheduleConstants {
    
    public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";

    
    public static final String TASK_PROPERTIES = "TASK_PROPERTIES";

    
    public static final String MISFIRE_DEFAULT = "0";

    
    public static final String MISFIRE_IGNORE_MISFIRES = "1";

    
    public static final String MISFIRE_FIRE_AND_PROCEED = "2";

    
    public static final String MISFIRE_DO_NOTHING = "3";
}
3.2.2: 调用quartz抽象类
public abstract class AbstractQuartzJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        MyJob myJob = new MyJob();
        copyBeanProp(myJob, jobExecutionContext.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
        try {
            //执行定时任务
            doExecute(jobExecutionContext,myJob);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    protected abstract void doExecute(JobExecutionContext context, MyJob myJob) throws Exception;

    
    public static void copyBeanProp(Object dest, Object src) {
        try {
            copyProperties(src, dest);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
3.2.3: job任务类

Job任务累分为俩种:允许并发执行和禁止并非执行

@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
{
    @Override
    protected void doExecute(JobExecutionContext context, MyJob job) throws Exception
    {
        JobInvokeUtil.invokeMethod(job);
    }
}

public class QuartzJobExecution extends AbstractQuartzJob
{
    @Override
    protected void doExecute(JobExecutionContext context, MyJob myJob) throws Exception
    {
        JobInvokeUtil.invokeMethod(myJob);
    }
}
3.2.4: Spring工具类
@Component
public final class SpringUtils implements BeanFactoryPostProcessor{
	
    private static ConfigurableListableBeanFactory beanFactory;
    
    
    @SuppressWarnings("unchecked")
    public static  T getBean(String name) throws BeansException {
        return (T) beanFactory.getBean(name);
    }
3.2.5: 任务执行工具工具类
public class JobInvokeUtil {

    
    private static void invokeMethod(Object bean, String methodName, List methodParams)
            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        if (isNotNull(methodParams) && methodParams.size() > 0) {
            Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
            method.invoke(bean, getMethodParamsValue(methodParams));
        } else {
            Method method = bean.getClass().getDeclaredMethod(methodName);
            method.invoke(bean);
        }
    }

    
    public static void invokeMethod(MyJob job) throws Exception {
        String invokeTarget = job.getInvokeTarget();
        //获取bean的名称
        String beanName = getBeanName(invokeTarget);
        //获取方法名
        String methodName = getMethodName(invokeTarget);
        List methodParams = getMethodParams(invokeTarget);

        if (!isValidClassName(beanName)) {
            Object bean = SpringUtils.getBean(beanName);
            invokeMethod(bean, methodName, methodParams);
        } else {
            Object bean = Class.forName(beanName).newInstance();
            invokeMethod(bean, methodName, methodParams);
        }
    }

    
    public static String getBeanName(String invokeTarget) {
        String beanName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringBeforeLast(beanName, ".");
    }

    
    public static String getMethodName(String invokeTarget) {
        String methodName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringAfterLast(methodName, ".");
    }

    
    public static boolean isValidClassName(String invokeTarget) {
        return StringUtils.countMatches(invokeTarget, ".") > 1;
    }

    
    public static List getMethodParams(String invokeTarget) {
        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
        if (StringUtils.isEmpty(methodStr)) {
            return null;
        }
        String[] methodParams = methodStr.split(",");
        List classs = new linkedList<>();
        for (int i = 0; i < methodParams.length; i++) {
            String str = StringUtils.trimToEmpty(methodParams[i]);
            // String字符串类型,包含'
            if (StringUtils.contains(str, "'")) {
                classs.add(new Object[]{StringUtils.replace(str, "'", ""), String.class});
            }
            // boolean布尔类型,等于true或者false
            else if (StringUtils.equals(str, "true") || StringUtils.equalsIgnoreCase(str, "false")) {
                classs.add(new Object[]{Boolean.valueOf(str), Boolean.class});
            }
            // long长整形,包含L
            else if (StringUtils.containsIgnoreCase(str, "L")) {
                classs.add(new Object[]{Long.valueOf(StringUtils.replaceIgnoreCase(str, "L", "")), Long.class});
            }
            // double浮点类型,包含D
            else if (StringUtils.containsIgnoreCase(str, "D")) {
                classs.add(new Object[]{Double.valueOf(StringUtils.replaceIgnoreCase(str, "D", "")), Double.class});
            }
            // 其他类型归类为整形
            else {
                classs.add(new Object[]{Integer.valueOf(str), Integer.class});
            }
        }
        return classs;
    }

    
    public static Object[] getMethodParamsValue(List methodParams) {
        Object[] classs = new Object[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams) {
            classs[index] = (Object) os[0];
            index++;
        }
        return classs;
    }

    
    public static Class[] getMethodParamsType(List methodParams) {
        Class[] classs = new Class[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams) {
            classs[index] = (Class) os[1];
            index++;
        }
        return classs;
    }

    
    public static boolean isNotNull(Object object) {
        return object == null;
    }
}
3.2.6: Schedule工具类
public class ScheduleUtil {

    
    private static Class getQuartzJobClass(MyJob myJob) {
        boolean isConcurrent = "0".equals(myJob.getConcurrent());
        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
    }


    public static void intSchedule(Scheduler scheduler, MyJob myJob) throws Exception {

        //1.创建JobDetail
        JobDetail jobDetail = JobBuilder.newJob(getQuartzJobClass(myJob))
                .withIdentity(getJobKey(myJob.getId(), myJob.getJobGroup()))
                .build();

        //1.1绑定传递参数
        jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, myJob);

        //2.cron表达式调度构建器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(myJob.getCronexpression());
        cronScheduleBuilder = handleCronScheduleMisfirePolicy(myJob, cronScheduleBuilder);

        
        //3.按照cronexpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(getTriggerKey(myJob.getId(),
                        myJob.getJobGroup()))
                .withSchedule(cronScheduleBuilder)
                .build();

        // 4.判断是否存在
        if (scheduler.checkExists(getJobKey(myJob.getId(), myJob.getJobGroup()))) {
            // 防止创建时存在数据问题 先移除,然后在执行创建操作
            scheduler.deleteJob(getJobKey(myJob.getId(), myJob.getJobGroup()));
        }

        // 5.注册任务和定时器
        scheduler.scheduleJob(jobDetail, trigger);

        // 6.暂停任务
        //判断定时任务状态 状态为1 暂停
        if (myJob.getStatus().equals("1")) {
            scheduler.pauseJob(getJobKey(myJob.getId(), myJob.getJobGroup()));
        }
    }

    
    public static JobKey getJobKey(Long jobId, String jobGroup) {
        return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
    }

    
    public static TriggerKey getTriggerKey(Long jobId, String jobGroup) {
        return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
    }

    
    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(MyJob myJob, CronScheduleBuilder cb)
            throws Exception {
        switch (myJob.getMisfirePolicy()) {
            case ScheduleConstants.MISFIRE_DEFAULT:
                return cb;
            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                return cb.withMisfireHandlingInstructionIgnoreMisfires();
            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                return cb.withMisfireHandlingInstructionFireAndProceed();
            case ScheduleConstants.MISFIRE_DO_NOTHING:
                return cb.withMisfireHandlingInstructionDoNothing();
            default:
                throw new Exception("参数有问题" + myJob.getMisfirePolicy());
        }
    }
}
3.2.6: JobService类
@Service
public class JobServiceImpl implements JobService {

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private JobMapper jobMapper;

    
    @PostConstruct
    public void intJob() throws SchedulerException {
        List myJobs = jobMapper.queryQuartz();
        //先清理缓存
        scheduler.clear();
        //创建任务
        myJobs.forEach(myJob -> {
            try {
                ScheduleUtil.intSchedule(scheduler,myJob);
            } catch (SchedulerException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    
    @Override
    public void runJob(Long id) throws SchedulerException {
        MyJob myJob = jobMapper.queryJobById(id);
        JobDataMap dataMap = new JobDataMap();
        //设置参数
        dataMap.put(ScheduleConstants.TASK_PROPERTIES, myJob);
        scheduler.triggerJob(ScheduleUtil.getJobKey(myJob.getId(), myJob.getJobGroup()), dataMap);
    }

    
    @Override
    public void stopJob(Long id) throws SchedulerException {
        MyJob myJob = jobMapper.queryJobById(id);
        scheduler.pauseJob(ScheduleUtil.getJobKey(myJob.getId(), myJob.getJobGroup()));
    }

    
    @Override
    public void resumeJob(Long id) throws SchedulerException {
        MyJob myJob = jobMapper.queryJobById(id);
        scheduler.resumeJob(ScheduleUtil.getJobKey(myJob.getId(), myJob.getJobGroup()));
    }

    
    @Override
    public void deleteJob(Long id) throws SchedulerException {
    	//删除任务
        MyJob myJob = jobMapper.queryJobById(id);
        scheduler.deleteJob(ScheduleUtil.getJobKey(myJob.getId(), myJob.getJobGroup()));
    	//删除数据
    	MyJob myJob = scheduleMapper.deleteQuartzById(id);
        //更新任务
        List jobs = jobMapper.queryJob();
        //先清理缓存
        scheduler.clear();
        jobs.forEach(j -> {
            try {
                ScheduleUtil.intSchedule(scheduler,j);
            } catch (SchedulerException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
	
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSchedulerJob(MyJob myJob) throws Exception {
    	//更新数据
        scheduleMapper.updateQuartzById(myJob.getId());
        // 判断是否存在
        JobKey jobKey = ScheduleUtil.getJobKey(myJob.getId(), myJob.getJobGroup());
        if (scheduler.checkExists(jobKey)){
            // 防止创建时存在数据问题 先移除,然后在执行创建操作
            scheduler.deleteJob(jobKey);
        }
        ScheduleUtil.intSchedule(scheduler,myJob);
    }
}
4.使用方法

在数据库中增加两条如下数据

编写TaskService

//注意这里的bean名称
@Service("test")
public class TaskService {
	//注意这里的方法名称 这俩个对应数据库中invoke_target字段
    public void test(){
        System.err.println("task测试成功");
    }
    public void testParam(String params){
        System.out.println(params);
        System.err.println("task测试成功");
    }
}

任务会通过反射调用这里的方法,具体看原理如下分析

5.原理

数据库字段的使用地方:
id、job_name、job_group、trigger_name、trigger_group、concurrent、cron_expression、misfire_policy这些字段在定时任务初始化时被当作生成jobkey和其他条件使用。
iid、job_name、nvoke_target这些字段在定时任务调度时被当作生成jobkey和调用自建方法的条件使用

第一步:动态初始化定时任务


初始化的每个任务都会根据数据库的数据生成自己的JobKey。(JobKey根据id和常量生成的)
getJobKey()是生成jobkey的方法

第二步:动态定时任务的调度

当用户在管理平台操作执行一次定时任务时,会拿着id传到后端,后端将任务数据查出来后,并使用数据生成对于的JobKey,执行对应jobkey的任务。

接下来定时任务会调用我们实现的Job接口中的execute方法


最后一步就是根据数据通过反射调用当时写好的方法

注意:上面方法处理的数据是数据库中的invoke_target字段,小数点前缀是Bean名称,后缀是方法名

5.总结

通过以上的学习,进一步加强了对spring的认知,太强大了。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/749307.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号