因为项目的需求,需要有动态配置计划任务的功能。
本文在 Quartz JobBean 中获取配置的 Quartz cronExpression 时间表达式及 Spring Bean 的对象名、方法名并运行。

准备

环境

  • quartz : 2.2.2
  • spring : 4.2.3.RELEASE

配置

假设已经配置好数据源,且在数据库中已经建好相关的 Quartz 表。

Spring 配置文件配置好单机器的 Quartz 任务。

<bean id="localQuartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"></bean>

去除原有的 quartz 的 jobDetail 等其他设置,下面我们将把这些改为动态设置。

集群

Spring 增加 cluster quartz 配置。

<!-- Quartz集群Scheduler -->
<bean id="clusterQuartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<!-- quartz配置文件路径-->
<property name="configLocation" value="classpath:quartz.properties"/>
<!-- 启动时延期3秒开始任务 -->
<property name="startupDelay" value="3"/>
<!-- 保存Job数据到数据库所需的数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- Job接受applicationContext的成员变量名 -->
<property name="applicationContextSchedulerContextKey" value="applicationContext"/>
<property name="overwriteExistingJobs" value="true"/>
</bean>

配置中使用的 dataSource 数据源,需要提前配置;quartz.properties 属性文件自行配置。集群定时任务的任务会序列化后储存至数据库,在某机器 crash 后,可以快速的切换到新的机器去运行,并且保证有仅只有一台机器运行计划任务。

先写 QuartzRunnable 文件,这个文件我用来启动 Quartz 定时任务。

import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext; /**
* Created by ixiaozhi on 16/6/27.
*/
public class QuartzRunnable {
private static final Logger logger = LoggerFactory.getLogger(QuartzRunnable.class); private ApplicationContext context; /**
* 构造函数, 传入 applicationContext
*
* @param context
*/
public QuartzRunnable(ApplicationContext context) {
this.context = context;
} public void work() throws SchedulerException {
logger.info("quartz is running ...");
// scheduler 对象
Scheduler schedulerCluster = (Scheduler) context.getBean("clusterQuartzScheduler");
Scheduler schedulerLocal = (Scheduler) context.getBean("localQuartzScheduler"); List<ScheduleJob> allQuartzJobs = ......; // 从数据库或者配置文件或者其他任何地方取得 Quartz 任务的配置文件, ScheduleJob 对象为自定义的 Quartz 任务设置,对象的属性见下文 // 启动定时任务
for (ScheduleJob job : allQuartzJobs) {
// 区分本机运行或集群运行
Scheduler scheduler;
if (job.getIsCluster() == 1) {
scheduler = schedulerCluster;
} else {
scheduler = schedulerLocal;
} TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
//不存在,创建一个
if (null == trigger) {
JobDetail jobDetail = JobBuilder.newJob(MyDetailQuartzJobBean.class).withIdentity(job.getJobName(), job.getJobGroup()).build();
JobDataMap dataMap = jobDetail.getJobDataMap();
dataMap.put("scheduleJob", job); // 传递 job 对象至执行的方法体 //表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
//按新的cronExpression表达式构建一个新的trigger
trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup()).withSchedule(scheduleBuilder).withDescription(job.getDescription()).build();
scheduler.scheduleJob(jobDetail, trigger);
} else {
// Trigger已存在,那么更新相应的定时设置
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
//按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
}
}
}
}

从 Spring ApplicationContext 上下文对象中取得本文上面配置的两个 Quartz Scheduler,分别用于启动本机的定时任务与集群定时任务。再根据自己的配置,构造对应的 Trigger 与 Job,加入不同的计划任务中执行。

对于计划任务,都使用同一个类 MyDetailQuartzJobBean 进行启动。在配置中可以根据配置反射启动相应的方法。

我的 ScheduleJob 计划任务配置的属性有以下。 targetObject 为 Spring 中注入的 bean 名称, targetMethod用于定时任务启动的方法入口。

public class ScheduleJob implements Serializable {
private static final long serialVersionUID = -4166311089940333025L;
private String jobId; // 任务 ID
private String jobName; // 任务名称
private String jobGroup; // 任务分组
private String cronExpression; // 时间表达式
private String description; // 任务描述 private String targetObject; // Spring 注入的类名
private String targetMethod; // 方法 private int isCluster;// 是否集群运行 // getter and setter
... ... }

比如测试的 ScheduleJob 对象:
jobId=1;
jobName=“Test”;
jobGroup=“DEFAULT”;
cronExpression=“1/30 * * * * ?”; // 从1秒开始,每30秒执行一次
description=“测试任务”;
targetObject=“testService”;
targetMethod=“quartzTest”;
isCluster=true;
含义为,将从 1 秒开始,每 30 秒在集群中的某一台机器运行一次,从 targetObject 的注入对象中的 targetMethod 方法。

现在来实现 JobBean 计划任务执行类,我命名为 MyDetailQuartzJobBean.java

import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean; import java.lang.reflect.Method; /**
* 动态运行方法
*/
//@PersistJobDataAfterExecution
//@DisallowConcurrentExecution //确保多个任务不会同时运行
public class MyDetailQuartzJobBean extends QuartzJobBean {
private static final Logger logger = LoggerFactory.getLogger(MyDetailQuartzJobBean.class); private ScheduleJob scheduleJob; protected void executeInternal(JobExecutionContext context)
throws JobExecutionException {
try {
Object targetObject = ApplicationContextUtil.getBean(scheduleJob.getTargetObject());
Method m;
try {
m = targetObject.getClass().getMethod(scheduleJob.getTargetMethod(), new Class[]{});
m.invoke(targetObject, new Object[]{});
} catch (SecurityException e) {
logger.error(e.getMessage(), e);
} catch (NoSuchMethodException e) {
logger.error(e.getMessage(), e);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new JobExecutionException(e);
} } public void setScheduleJob(ScheduleJob scheduleJob) {
this.scheduleJob = scheduleJob;
}
}

JobBean 需要继承至 QuartzJobBean,并重写 executeInternal 方法。而且,Quartz 集群中运行的 QuartzJobBean 必须实现序列化。但是,applicationContext 并不支持序列化,在这里面直接注入对象会报 exception 且无法使用。

因此我使用静态化来保存 applicationContext 对象,实现类 ApplicationContextUtil 。

@Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext; // Spring应用上下文环境 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextUtil.applicationContext = applicationContext;
} public static ApplicationContext getApplicationContext() {
return applicationContext;
} public static Object getBean(String beanName) {
return applicationContext.getBean(beanName);
} @SuppressWarnings("unchecked")
public static <T> T getBeanDetail(String beanName) throws BeansException {
return (T) applicationContext.getBean(beanName);
}
}

Spring 配置文件中注册该 Bean。

<bean id="applicationContextUtil" class="com.ixiaozhi.util.ApplicationContextUtil"/>

利用 getBean 可直接从 Spring 上下文中取得注入的对象,如上述的 MyDetailQuartzJobBean 使用该方法绕过 Spring ApplicationContext 无法序列化的问题,且取得 Spring Bean 并反射调用其中的方法。

测试

    /**
* 程序入口
*
* @param args
*/
public void test() {
// 初始化 Spring
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml"); // 启动定时任务
QuartzRunnable quartz = new QuartzRunnable(applicationContext);
quartz.work();
}

其他更好的实现方案,欢迎留言一起探讨。

参考

Spring 与 Quartz 动态配置(数漫江湖)的更多相关文章

  1. Spring Boot Quartz 动态配置,持久化

    Quartz 是一个很强大的任务调度框架在SpringBoot中也很容易集成 添加依赖: <dependency> <groupId>org.springframework&l ...

  2. Quartz 在 Spring 中如何动态配置时间--转

    原文地址:http://www.iteye.com/topic/399980 在项目中有一个需求,需要灵活配置调度任务时间,并能自由启动或停止调度. 有关调度的实现我就第一就想到了Quartz这个开源 ...

  3. Quartz动态配置表达的方法

    在项目中有一个需求,需要灵活配置调度任务时间,并能自由启动或停止调度.有关调度的实现我就第一就想到了Quartz这个开源调度组件,因为很多项目使用过,Spring结合Quartz静态配置调度任务时间, ...

  4. 初识quartz 并分析 项目中spring整合quartz的配置【原创+转载】

    初识quartz 并分析 项目中spring整合quartz的配置[原创+转载]2018年01月29日 12:08:07 守望dfdfdf 阅读数:114 标签: quartz 更多个人分类: 工具 ...

  5. spring boot1.0 集成quartz 动态配置定时任务

    转载自 https://www.imooc.com/article/36278 一.Quartz简介了解 Quartz Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应 ...

  6. Spring中Quartz的配置及corn表达式

    Quartz可以用来执行任务调度功能,如间隔一定时间调用执行任务.用起来还是蛮方便的.只要将你要调用的类配置到Spring配置文件即可. 在Spring的配置文件中配置Quartz. <!-- ...

  7. Spring Security之动态配置资源权限

    在Spring Security中实现通过数据库动态配置url资源权限,需要通过配置验证过滤器来实现资源权限的加载.验证.系统启动时,到数据库加载系统资源权限列表,当有请求访问时,通过对比系统资源权限 ...

  8. spring security实现动态配置url权限的两种方法

    缘起 标准的RABC, 权限需要支持动态配置,spring security默认是在代码里约定好权限,真实的业务场景通常需要可以支持动态配置角色访问权限,即在运行时去配置url对应的访问角色. 基于s ...

  9. SPring中quartz的配置(可以用实现邮件定时发送,任务定时执行,网站定时更新等)

    http://www.cnblogs.com/kay/archive/2007/11/02/947372.html 邮件或任务多次发送或执行的问题: 1.<property name=" ...

随机推荐

  1. Java内存区域划分和GC机制

    Java 内存区域和GC机制   目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Ga ...

  2. LintCode-70.二叉树的层次遍历 II

    二叉树的层次遍历 II 给出一棵二叉树,返回其节点值从底向上的层次序遍历(按从叶节点所在层到根节点所在的层遍历,然后逐层从左往右遍历) 样例 给出一棵二叉树 {3,9,20,#,#,15,7}, 按照 ...

  3. TCP系列06—连接管理—5、TCP fastopen(TFO)

    一.TFO背景 当前web和web-like应用中一般都是在三次握手后开始数据传输,相比于UDP,多了一个RTT的时延,即使当前很多应用使用长连接来处理这种情况,但是仍然由一定比例的短连接,这额外多出 ...

  4. Dubbo分享

    1. Dubbo是什么? Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案.简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需 ...

  5. ConcurrentHashMap弱一致性

    [原文链接] 本文将用到Java内存模型的happens-before偏序关系(下文将简称为hb)以及ConcurrentHashMap的底层模型相关的知识.happens-before相关内容参见: ...

  6. 第72天:jQuery实现下拉菜单

    jQuery实现下拉菜单 一.居中 1.块元素居中:给块元素本身设置:margin:0 auto;,块元素必须设置宽度 2.行内块元素居中:给元素父级设置text-algin:center; < ...

  7. 【EF】EF扩展库(批量操作)

    EF删除和修改数据只能先从数据库取出,然后再进行删除 delete from Table1 where Id>5; update Table1 set Age=10; 我们需要这样操作 //删除 ...

  8. 转:浅谈Spectral Clustering 谱聚类

    浅谈Spectral Clustering Spectral Clustering,中文通常称为“谱聚类”.由于使用的矩阵的细微差别,谱聚类实际上可以说是一“类”算法. Spectral Cluste ...

  9. 【题解】Atcoder AGC#16 E-Poor Turkeys

    %拜!颜神怒A此题,像我这样的渣渣只能看看题解度日╭(╯^╰)╮在这里把两种做法都记录一下吧~ 题解做法:可以考虑单独的一只鸡 u 能否存活.首先我们将 u 加入到集合S.然后我们按照时间倒序往回推, ...

  10. 用Matlab对数据进行线性拟合算法

    http://www.cnblogs.com/softlin/p/5965939.html 挖坑