热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java定时任务管理,利用Spring动态对Quartz定时任务管理

在开发时我们会常常遇到定时任务可以由客户进行管理在什么时候去执行或者甚至不再执行该定时任务。而Spring中所提供的定时任务组件却只能够通过修改trigger的配置才能够控制定时的

在开发时我们会常常遇到定时任务可以由客户进行管理在什么时候去执行或者甚至不再执行该定时任务。而Spring中所提供的定时任务组件却只能够通过修改trigger的配置才能够控制定时的时间以及是否启用定时任务,为此我搜索了网上的一些解决方法,发现还是不能够很好的解决这个问题。所以干脆仔仔细细的研究了一把Quartz和Spring中相关的源码,我们发现当我们在Spring通过如下声明定时任务时:

Java代码 a4c26d1e5885305701be709a3d33442f.png

"yourJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">

"targetObject" ref="yourJobBean"/>

"targetMethod" value="yourJobMethod"/>

"concurrent" value="false"/>

"yourCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean" >

"jobDetail" ref="yourobDetail"/>

"cronExpression">

0 0 2 * * ?

"schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

"triggers">

"yourCronTrigger"/>

bean>

所生成的Quartz的JobDetail并不是你定义的Bean,因为JobDetail并不能够直接存放一个实例,具体可以参考JobDetail的构造函数:

Java代码 a4c26d1e5885305701be709a3d33442f.png

JobDetail(String name, String group, Class jobClass)

Create a JobDetail with the given name, group and class, and the default settings of all the other properties.

Spring将其转换为MethodInvokingJob或者StatefulMethodInvokingJob类型,其实这两个类都是从QuartzJobBean类继承而来,那么我们来看看QuartzJobBean类的代码:

Java代码 a4c26d1e5885305701be709a3d33442f.png

public abstract class QuartzJobBean implements Job {

public final void execute(JobExecutionContext context) throws JobExecutionException {

try {

BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);

MutablePropertyValues pvs = new MutablePropertyValues();

pvs.addPropertyValues(context.getScheduler().getContext());

pvs.addPropertyValues(context.getMergedJobDataMap());

bw.setPropertyValues(pvs, true);

}

catch (SchedulerException ex) {

throw new JobExecutionException(ex);

}

executeInternal(context);

}

protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;

}

看到这个你或许已经明白了Spring对Quartz的封装原理了。是的,Spring通过这种方式最后就可以在Job真正执行的时候可以反调用到我们所注入的类和方法,具体的代码在MethodInvokingJobDetailFactoryBean类中,如下:

Java代码 a4c26d1e5885305701be709a3d33442f.png

public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException {

prepare();

// Use specific name if given, else fall back to bean name.

String name = (this.name != null ? this.name : this.beanName);

// Consider the concurrent flag to choose between stateful and stateless job.

Class jobClass = (this.concurrent ? (Class) MethodInvokingJob.class : StatefulMethodInvokingJob.class);

// Build JobDetail instance.

this.jobDetail = new JobDetail(name, this.group, jobClass);

this.jobDetail.getJobDataMap().put("methodInvoker", this);

this.jobDetail.setVolatility(true);

this.jobDetail.setDurability(true);

// Register job listener names.

if (this.jobListenerNames != null) {

for (int i = 0; i 

this.jobDetail.addJobListener(this.jobListenerNames[i]);

}

}

postProcessJobDetail(this.jobDetail);

}

其实主要是这句:

Java代码 a4c26d1e5885305701be709a3d33442f.png

this.jobDetail.getJobDataMap().put("methodInvoker", this);

这样在调用的时候那就可以调用到我们所注入的类和方法了。

那好我们理解了Spring的原理后就可以动手修改成我们自己的了,所以在设计的时候我们考虑到以下两个方面:

精简Spring的配置,添加一个定时任务至少有10几行配置代码,太麻烦了,最好只声明一个Bean就可以了;

客户不但能够控制执行的时间,还可以能够启用/禁用某个定时任务。

在上面的思路下,我们决定保留Spring的这个配置:

Xml代码 a4c26d1e5885305701be709a3d33442f.png

bean>

只不过不需要注入trigger这个属性了,我们仅仅利用Spring来帮我们构造一个Scheduler。

为了简化后续的处理,我们决定定义一个自己Job的接口,方便进行控制及精简代码。

Java代码 a4c26d1e5885305701be709a3d33442f.png

public interface MyJob {

public void execute() throws JobException;

}

接下来我们还是需要一个类似QuartzJobBean类,因此参考Spring的更改如下:

Java代码 a4c26d1e5885305701be709a3d33442f.png

public class QuartzJobBean implements Job {

public void execute(JobExecutionContext context) throws JobExecutionException {

String targetBeanId = (String)context.getMergedJobDataMap().get("targetObjectId");

if(StringUtils.isNullString(targetBeanId))

return;

Object targetBean = SpringUtils.getBean(targetBeanId);

if(null == targetBean)

return;

// 判断是否是实现了MyJob接口

if(!(targetBean instanceof MyJob))

return;

// 执行相应的任务

((MyJob)targetBean).execute();

}

}

这个类处理逻辑就是通过获取我在创建JobDetail任务是设定的目标任务Bean的,即targetObjectId的值,然后通过SpringUtils获取该Bean,最后转换成MyJob接口执行。

如何创建JobDetail呢,因为我们前面已经说明不需要在Spring配置那么麻烦而且客户还可以进行配置,因此我们将任务的定时信息存放在数据库中,相应的Domain定义如下:

Java代码 a4c26d1e5885305701be709a3d33442f.png

public class SchedulingJob extends Entry {

public static final int JS_ENABLED = 0; // 任务启用状态

public static final int JS_DISABLED = 1; // 任务禁用状态

public static final int JS_DELETE = 2; // 任务已删除状态

private String jobId; // 任务的Id,一般为所定义Bean的ID

private String jobName; // 任务的描述

private String jobGroup; // 任务所属组的名称

private int jobStatus; // 任务的状态,0:启用;1:禁用;2:已删除

private String cronExpression; // 定时任务运行时间表达式

private String memos; // 任务描述

// 省略getter和setter... ...

public String getTriggerName(){

return this.getJobId() + "Trigger";

}

}

这时候我们就可以对定时任务进行控制了,具体控制代码如下:

Java代码 a4c26d1e5885305701be709a3d33442f.png

protected void enabled(Context context, SchedulingJob schedulingJob) {

try {

CronTrigger trigger = (CronTrigger)this.scheduler.getTrigger(schedulingJob.getTriggerName(), schedulingJob.getJobGroup());

if (null == trigger) {

// Trigger不存在,那么创建一个

JobDetail jobDetail = new JobDetail(schedulingJob.getJobId(), schedulingJob.getJobGroup(), QuartzJobBean.class);

jobDetail.getJobDataMap().put("targetObjectId", schedulingJob.getJobId());

trigger = new CronTrigger(schedulingJob.getTriggerName(), schedulingJob.getJobGroup(), schedulingJob.getCronexpression_r());

this.scheduler.scheduleJob(jobDetail, trigger);

}else{

// Trigger已存在,那么更新相应的定时设置

trigger.setCronexpression_r(schedulingJob.getCronexpression_r());

this.scheduler.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);

}

} catch (SchedulerException e) {

e.printStackTrace();

// TODO

} catch (ParseException e) {

e.printStackTrace();

// TODO

}

}

protected void disabled(Context context, SchedulingJob schedulingJob) {

try {

Trigger trigger = this.scheduler.getTrigger(schedulingJob.getTriggerName(), schedulingJob.getJobGroup());

if (null != trigger) {

this.scheduler.deleteJob(schedulingJob.getJobId(), schedulingJob.getJobGroup());

}

} catch (SchedulerException e) {

e.printStackTrace();

// TODO

}

}

再加上前台处理页面(这个俺就不写了,估计大家都会

a4c26d1e5885305701be709a3d33442f.png) 这样我们就可以在控制定时任务的定时时间及启用和禁用了。

最后我们的配置代码如下:

Xml代码 a4c26d1e5885305701be709a3d33442f.png

P.S.

这个定时任务组件是有局限性的,比如所设置的SchedulingJob.jobID必须是和Spring中定时任务Bean的id一致,否则这个定时任务不会被运行。还有这个现在仅支持CronTrigger。而且也不支持分组,不过你可以通过扩展

a4c26d1e5885305701be709a3d33442f.png

再一个要记得写一个加载类在系统初始化时从数据库的配置中加载所有启用的定时任务哦,不然所有定时任务都不能够运行

a4c26d1e5885305701be709a3d33442f.png



推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • 本文介绍了在Go语言中可见性与scope的规则,包括在函数内外声明的可见性、命名规范和命名风格,以及变量声明和短变量声明的语法。同时,还介绍了变量的生命周期,包括包级别变量和局部变量的生命周期,以及变量在堆和栈上分配的规则和逃逸分析的概念。 ... [详细]
  • 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之六 || API项目整体搭建 6.1 仓储模式
    代码已上传Github+Gitee,文末有地址  书接上文:前几回文章中,我们花了三天的时间简单了解了下接口文档Swagger框架,已经完全解放了我们的以前的Word说明文档,并且可以在线进行调 ... [详细]
  • PreparedStatement防止SQL注入
    添加数据:packagecom.hyc.study03;importcom.hyc.study02.utils.JDBCUtils;importjava.sql ... [详细]
  • 欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条回路。现给定一个图,问是否存在欧拉回路?Input测 ... [详细]
  • 最大连续登录天数
    1、创建数据CREATETABLEtb_shop(nameString,cdateDate,costFloat64)engineReplacingMergeTree(cdat ... [详细]
  • 本文整理了Java中org.apache.activemq.util.ByteArrayInputStream.<init>()方法的一些代码示例,展示了 ... [详细]
  • XTOOLS【运维平台】之本地&远程服务器磁盘容量校验(三)
    互联网爆炸时代,数据就是金钱,但过期数据如何有效定期压缩,为有效数据留下宝贵空间是很多业务遇到的问题。多数公司采用的方式如下:\x0a\x0ashell脚本以crontab的方式定 ... [详细]
  • 小编这次要给大家分享的是详解Python定时任务APScheduler,文章内容丰富,感兴趣的小伙伴可以来了解一下,希望大家阅读完这篇文章之后能够有所收获 ... [详细]
author-avatar
鬼王守护灵
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有