因为项目的需求,需要有动态配置计划任务的功能。
本文在 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. PCA算法理解及代码实现

    github:PCA代码实现.PCA应用 本文算法均使用python3实现 1. 数据降维   在实际生产生活中,我们所获得的数据集在特征上往往具有很高的维度,对高维度的数据进行处理时消耗的时间很大, ...

  2. 原生js操作Dom节点:CRUD

    知识点,依然会遗忘.我在思考到底是什么原因.想到研究生考试准备的那段岁月,想到知识体系的建立,知识体系分为正向知识体系和逆向知识体系:正向知识体系可以理解为教科书目录,逆向知识体系可以理解考试真题. ...

  3. SpringData——HelloWorld

    1.背景 最开始了解SpringData的时候,以为他不就是ORM的一种实现方式嘛,还能有什么新的东西.从hibernate到ibatis.mybatis,也许他只不过是spring想整合一个更方便的 ...

  4. Perfmon - 脚本自动监控

    PerfMon-Windows性能监视器是个好东西,可以辅助我们分析发生问题时间段服务器资源占用情况,但是部署性能计数器确实一个相当麻烦的事情,往往这种枯燥的事别人还做不了,只能由我们这些希望获取到P ...

  5. django为model设置表名

    class redis_data(models.Model):     class Meta:         db_table='redis_data'     key=models.CharFie ...

  6. SQL SERVER技术内幕之5 表表达式

    表表达式是一种命名的查询表达式,代表一个有效的关系表.可以像其他表一样,在数据处理语句中使用表表达式.SQL Server支持4种类型的表表达式:派生表(derived table).公用表表达式(C ...

  7. volatile并不能保证数据同步、只能保证读取到最新主内存数据

    在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配.其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈, 线程栈保存了线程运行时候变量值信息.当线程访问某一个对象时候值的 ...

  8. asp.net中缓存的使用

    刚学到asp.net怎么缓存,这里推荐学习一下 www.cnblogs.com/wang726zq/archive/2012/09/06/cache.html http://blog.csdn.net ...

  9. BZOJ 1930 吃豆豆(费用流)

    首先这题的两条线不相交的限制可以去掉,因为如果相交的话把点换一换是不影响最终结果的. 剩下的费用流建图是显然的,把点拆为两个,建立超级源点s和源点ss汇点t,连边(s,ss,2,0). 对于每个点,连 ...

  10. PowerDesigner 建表语句没出现字段描述

    填写了Name 还是没有 修改步骤如下: