文章目录
基本描述
- Quartz 是一个用于在Java环境下,执行定时任务/轮询任务的框架
- Quartz 可以将通过Spring进行集成,进而实现定时任务参数的配置化
- Quartz 也可以不通过Spring集成,按照一般的jar API 方式进行使用
Quartz的概念
业务逻辑类
- 待定时执行的具体业务逻辑类,一般有【继承式】和【配置式】两种语法形式
- 继承式
- 必须继承QuartzJobBean,并在executeInternal方法中实现具体的业务逻辑
- 自身就是一个定时Job,因此,后续不需要再Spring中配置Job参数
- 类似于Java原生Runanble接口实现多线程的语法
- 因有类型限制,一般使用的较少,(本文不累述)
- 配置式
- 自身无任何语法限制,可以是任意一个具有定时业务逻辑方法的类
- 使用前,需要通过Spring将其套接成一个定时Job,以被Quartz框架调用
- 配置式业务逻辑类中若想对Quarzt框架本身进行操作(java代码启停任务,修改定时间隔)需要通过Spring将Quartz的相关对象注入进来
Job
- Quartz可调度的具体定时任务的最小单位
- 只有业务逻辑类被套接或封装成Job时,才能被Quartz框架所调用
- 继承式的业务逻辑类,自身就是job类,因此不用额外配置
- 配置式的业务逻辑类,需要下Spring的配置文件中,指定具体的业务逻辑方法
Trigger
- 定时任务的触发规则类
- 用于指定job何时启动,何时重复(但不包括控制job的启停和间隔修改)
- 一般有间隔触发规则和Cron表达式触发规则两类
- 间隔类规则:只能指定定时任务的重复间隔,功能较为有限(使用较少)
- Cron表达式类规则:指定重复间隔,启停时间,语法上可完全取代间隔类规则(使用较多)
cron表达式
- 用于指定定时任务触发规则的特定表达式,有固有且通用的语法规则,具体参考Cron表达式
- 一般情况下可通过 Cron表达式生成器 ,定制需要的触发条件
schedule
- Quarzt 定时任务队列容器本身
- 内部包含了需要执行的trigger对象集合
- 动态控制定时任务启停或修改定时任务触发规则的统一操作类
spring 集成的优势
- 定时任务的业务逻辑以Ioc的方式进行组合,方便控制
- 定时任务的执行参数以Ioc的方式进行配置,方便修改
- 定时任务的初始化和关闭能够和程序生命周期同步(或Tomcat运行周期)
所需的包
基本步骤
本步骤基于【配置式业务逻辑类】描述
- 编写业务逻辑类,实现待定制执行的业务逻辑
- Spring中配置业务逻辑类
- Spring中将业务逻辑类套接成Job类
- Spring中将为Job类配套调度Trigger
- Spring中将Trigger放入schedule中
- 启动程序(运行tomcat)等待定时任务按需执行
实例代码
基本功能一:按指定时间间隔重复执行某个业务逻辑
业务逻辑类
public class DataSyncPollingTask
{
public void executePolling(){
System.out.println("running ------ " + new Date().toString());
//只是为了查看定时任务是否启动
}
}
代码说明
- 语法上,该类没有继承任何基类或实现任何接口
- 语法上,该类业务逻辑方法名没有任何限制
Spring配置
<!-- 数据同步轮询任务 -->
<bean id="gpStatusPollingTask" class="com.gisuni.geoportal.datasynctask.utils.DataSyncPollingTask"></bean>
<!-- 数据同步轮询Job -->
<bean id="gpStatusPollingTaskJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject">
<ref bean="gpStatusPollingTask"/>
</property>
<property name="targetMethod">
<value>executePolling</value>
</property>
<property name="concurrent" value="false"/>
</bean>
<!-- 数据同步轮询触发规则-->
<bean id="gpStatusPollingTaskCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<ref bean="gpStatusPollingTaskJob" />
</property>
<property name="cronExpression" value="0 0/1 * * * ? *" /><!-- 一分钟 循环执行一次 -->
</bean>
<!-- 数据同步轮询计划表-->
<bean id="gpStatusPollingTaskScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="gpStatusPollingTaskCronTrigger"/>
</list>
</property>
</bean>
代码说明
- gpStatusPollingTask:由Spring创建的DataSyncPollingTask实例
- gpStatusPollingTaskJob
- 一定是org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean类的实例
- targetObject指定业务逻辑类对象
- targetMethod指定业务逻辑类中的需要执行的业务方法
- concurrent是否要在程序启动后立刻执行
- true : 程序启动后立刻调用job
- false: 程序启动后,一直等到满足第一个触发条件时才启动
- gpStatusPollingTaskCronTrigger
- 一定是org.springframework.scheduling.quartz.CronTriggerFactoryBean类的实例
- jobDetail:指定当前触发器所需要调度的job对象
- cronExpression:当前的触发器所采用的默认调度表达式
- gpStatusPollingTaskScheduler
- 一定是org.springframework.scheduling.quartz.SchedulerFactoryBean类的实例
- triggers:以列表形式指定当前计划表中要维护的trigger实例
基本功能二:从代码中修改定时任务的启停
任务控制类
仅为了保证业务逻辑类的纯粹性,此处添加了Helper类对Quartz中的定时任务进行操作
@Component
/* 此处Component注解,只是为了方便在外部使用DataSyncPollingTaskHelper对象*/
public class DataSyncPollingTaskHelper
{
@Autowired
@Qualifier("gpStatusPollingTaskScheduler")
/*通过Spring注入计划表对象实例*/
private Scheduler scheduler;
@Autowired
@Qualifier("gpStatusPollingTaskCronTrigger")
/*通过Spring注入触发器对象实例*/
private CronTrigger trigger;
private String combineCronExpressint(int interval)
{
return String.format("0 0/%d * * * ? *", interval);
}
private int parserCronExpression(String cornExpression)
{
return Integer.parseInt(cornExpression.split(" ")[1].split("/")[1]);
}
/**
* 判断当前定时任务是否执行
* @return
*/
public boolean isRunning()
{
boolean result = true;
try
{
//实际是判断触发器在计划表中的状态
result = scheduler.getTriggerState(trigger.getKey()) == TriggerState.NORMAL;
}
catch (SchedulerException e)
{
e.printStackTrace();
}
return result;
}
/**
* 查询当前触发规则的详情
* @param result 触发规则查询结果[out]
*/
public void queryPollingState(DataSyncTaskPollingBean result)
{
try
{
//判断是否在运行
result.setRunning(scheduler.getTriggerState(trigger.getKey()) == TriggerState.NORMAL);
//获取原有的Conrn表达式
result.setCornExpression(trigger.getCronExpression());
//获取原有的Conrn表达式的描述字符串
result.setMsg(trigger.getExpressionSummary());
result.setInterval(parserCronExpression(result.getCornExpression()));
}
catch (Exception e)
{
result.setMsg(e.getMessage());
e.printStackTrace();
}
}
/**
* 修改触发规则
* @param param 修改参数
* @param result 触发规则修改结果[out]
*/
public void upadatePollingInterval(DataSyncTaskPollingParam param, DataSyncTaskPollingBean result)
{
try
{
//创建一个Cron触发器的builer
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(combineCronExpressint(param.getInterval()));
//利用builder构建一个新的trigger(保证Key一致)
trigger = trigger.getTriggerBuilder().withIdentity(trigger.getKey()).withSchedule(scheduleBuilder).build();
//用新的trigger替换原来的trigger
scheduler.rescheduleJob(trigger.getKey(),trigger);
//重新查询当前trigger的详情
queryPollingState(result);
}
catch (Exception e)
{
result.setMsg(e.getMessage());
e.printStackTrace();
}
}
/***
* 暂停调度任务
* @param result 暂停后的调度任务结果[out]
*/
public void pausePolling(DataSyncTaskPollingBean result)
{
try
{
scheduler.pauseTrigger(trigger.getKey());
queryPollingState(result);
}
catch (SchedulerException e)
{
result.setMsg(e.getMessage());
e.printStackTrace();
}
}
/**
* 恢复调度任务
* @param result 恢复后的调度任务结果[out]
*/
public void resumePolling(DataSyncTaskPollingBean result)
{
try
{
scheduler.resumeTrigger(trigger.getKey());
queryPollingState(result);
}
catch (SchedulerException e)
{
result.setMsg(e.getMessage());
e.printStackTrace();
}
}
}
补充说明
Quartz的关闭泄露
- 网上流传一种Quartz的调度问题,即当Quartz在Tomcat中运行时,有时候当Tomcat结束了,Quartz的调度还在继续,因此会引起内存泄漏,非法资源访问等问题
- 实际使用中暂未发现这样的情况,网上推荐按照以下方式进行补救
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="simpleTrigger" />
</list>
</property>
<!-- 在web关闭的时候关闭线程 -->
<property name="waitForJobsToCompleteOnShutdown" value="true"/>
</bean>
Quartz延迟启动的问题
以下问题均在个人项目中出现,不具备代表性和通用性,仅作为问题记录
- 启动tomcat后,Quartz调度任务没有正常启动
- 只用在执行queryPollingState【查询任务状态】方法后,相关Spring配置信息才被加载,调入任务启动
- 若想自动启动需要在web.xml中重复配置spring参数信息,即如下形式
<!-- 支持Spring扩展-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 保证SpringMVC能够正确找到Spring配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- 保证SpringMVC能够正确.do后缀类型的服务请求 -->
<url-pattern>*.do</url-pattern>
</servlet-mapping >
<!-- 重复载入spring配置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
- 但该种配置会在首次执行queryPollingState时,启动两个调度计划表,从而出现调度两次的问题
- 可能时原始Spring在web.xml中配置不太正确,但因项目原因未进行修改或尝试