热门标签 | 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



推荐阅读
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • 本文节选自《NLTK基础教程——用NLTK和Python库构建机器学习应用》一书的第1章第1.2节,作者Nitin Hardeniya。本文将带领读者快速了解Python的基础知识,为后续的机器学习应用打下坚实的基础。 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • 本文深入解析了 jQuery 中用于扩展功能的三个关键方法:`$.extend()`、`$.fn` 和 `$.fn.extend()`。其中,`$.extend()` 用于扩展 jQuery 对象本身,而 `$.fn.extend()` 则用于扩展 jQuery 的原型对象,使自定义方法能够作为 jQuery 实例的方法使用。通过这些方法,开发者可以轻松地创建和集成自定义插件,增强 jQuery 的功能。文章详细介绍了每个方法的用法、参数及实际应用场景,帮助读者更好地理解和运用这些强大的工具。 ... [详细]
  • 更新vuex的数据为什么用mutation?
    更新vuex的数据为什么用mutation?,Go语言社区,Golang程序员人脉社 ... [详细]
  • Android 自定义 RecycleView 左滑上下分层示例代码
    为了满足项目需求,需要在多个场景中实现左滑删除功能,并且后续可能在列表项中增加其他功能。虽然网络上有很多左滑删除的示例,但大多数封装不够完善。因此,我们尝试自己封装一个更加灵活和通用的解决方案。 ... [详细]
  • Hadoop的文件操作位于包org.apache.hadoop.fs里面,能够进行新建、删除、修改等操作。比较重要的几个类:(1)Configurati ... [详细]
  • 本文介绍了如何在 Vue 3 组合 API 中正确设置 setup() 函数的 TypeScript 类型,以避免隐式 any 类型的问题。 ... [详细]
  • Spring – Bean Life Cycle
    Spring – Bean Life Cycle ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • 原文网址:https:www.cnblogs.comysoceanp7476379.html目录1、AOP什么?2、需求3、解决办法1:使用静态代理4 ... [详细]
  • com.hazelcast.config.MapConfig.isStatisticsEnabled()方法的使用及代码示例 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 深入解析C#中app.config文件的配置与修改方法
    在C#开发过程中,经常需要对系统的配置文件进行读写操作,如系统初始化参数的修改或运行时参数的更新。本文将详细介绍如何在C#中正确配置和修改app.config文件,包括其结构、常见用法以及最佳实践。此外,还将探讨exe.config文件的生成机制及其在不同环境下的应用,帮助开发者更好地管理和维护应用程序的配置信息。 ... [详细]
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社区 版权所有