Spring + Quartz 定时任务

基本描述

  1. Quartz 是一个用于在Java环境下,执行定时任务/轮询任务的框架
  2. Quartz 可以将通过Spring进行集成,进而实现定时任务参数的配置化
  3. Quartz 也可以不通过Spring集成,按照一般的jar API 方式进行使用

Quartz的概念

业务逻辑类

  1. 待定时执行的具体业务逻辑类,一般有【继承式】和【配置式】两种语法形式
  2. 继承式
    1. 必须继承QuartzJobBean,并在executeInternal方法中实现具体的业务逻辑
    2. 自身就是一个定时Job,因此,后续不需要再Spring中配置Job参数
    3. 类似于Java原生Runanble接口实现多线程的语法
    4. 因有类型限制,一般使用的较少,(本文不累述)
  3. 配置式
    1. 自身无任何语法限制,可以是任意一个具有定时业务逻辑方法的类
    2. 使用前,需要通过Spring将其套接成一个定时Job,以被Quartz框架调用
    3. 配置式业务逻辑类中若想对Quarzt框架本身进行操作(java代码启停任务,修改定时间隔)需要通过Spring将Quartz的相关对象注入进来

Job

  1. Quartz可调度的具体定时任务的最小单位
  2. 只有业务逻辑类被套接或封装成Job时,才能被Quartz框架所调用
  3. 继承式的业务逻辑类,自身就是job类,因此不用额外配置
  4. 配置式的业务逻辑类,需要下Spring的配置文件中,指定具体的业务逻辑方法

Trigger

  1. 定时任务的触发规则类
  2. 用于指定job何时启动,何时重复(但不包括控制job的启停和间隔修改)
  3. 一般有间隔触发规则和Cron表达式触发规则两类
    1. 间隔类规则:只能指定定时任务的重复间隔,功能较为有限(使用较少)
    2. Cron表达式类规则:指定重复间隔,启停时间,语法上可完全取代间隔类规则(使用较多)

cron表达式

  1. 用于指定定时任务触发规则的特定表达式,有固有且通用的语法规则,具体参考Cron表达式
  2. 一般情况下可通过 Cron表达式生成器 ,定制需要的触发条件

schedule

  1. Quarzt 定时任务队列容器本身
  2. 内部包含了需要执行的trigger对象集合
  3. 动态控制定时任务启停或修改定时任务触发规则的统一操作类

spring 集成的优势

  1. 定时任务的业务逻辑以Ioc的方式进行组合,方便控制
  2. 定时任务的执行参数以Ioc的方式进行配置,方便修改
  3. 定时任务的初始化和关闭能够和程序生命周期同步(或Tomcat运行周期)

所需的包

基本步骤

本步骤基于【配置式业务逻辑类】描述

  1. 编写业务逻辑类,实现待定制执行的业务逻辑
  2. Spring中配置业务逻辑类
  3. Spring中将业务逻辑类套接成Job类
  4. Spring中将为Job类配套调度Trigger
  5. Spring中将Trigger放入schedule中
  6. 启动程序(运行tomcat)等待定时任务按需执行

实例代码

基本功能一:按指定时间间隔重复执行某个业务逻辑

业务逻辑类

public class DataSyncPollingTask
{
	public void executePolling(){
		System.out.println("running ------ " + new Date().toString());
		//只是为了查看定时任务是否启动
	}
}

代码说明

  1. 语法上,该类没有继承任何基类或实现任何接口
  2. 语法上,该类业务逻辑方法名没有任何限制

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>
代码说明
  1. gpStatusPollingTask:由Spring创建的DataSyncPollingTask实例
  2. gpStatusPollingTaskJob
    1. 一定是org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean类的实例
    2. targetObject指定业务逻辑类对象
    3. targetMethod指定业务逻辑类中的需要执行的业务方法
    4. concurrent是否要在程序启动后立刻执行
      1. true : 程序启动后立刻调用job
      2. false: 程序启动后,一直等到满足第一个触发条件时才启动
  3. gpStatusPollingTaskCronTrigger
    1. 一定是org.springframework.scheduling.quartz.CronTriggerFactoryBean类的实例
    2. jobDetail:指定当前触发器所需要调度的job对象
    3. cronExpression:当前的触发器所采用的默认调度表达式
  4. gpStatusPollingTaskScheduler
    1. 一定是org.springframework.scheduling.quartz.SchedulerFactoryBean类的实例
    2. 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的关闭泄露

  1. 网上流传一种Quartz的调度问题,即当Quartz在Tomcat中运行时,有时候当Tomcat结束了,Quartz的调度还在继续,因此会引起内存泄漏,非法资源访问等问题
  2. 实际使用中暂未发现这样的情况,网上推荐按照以下方式进行补救
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="simpleTrigger" />
        </list>
    </property>
    <!-- 在web关闭的时候关闭线程 -->
    <property name="waitForJobsToCompleteOnShutdown" value="true"/>  
</bean>

Quartz延迟启动的问题

以下问题均在个人项目中出现,不具备代表性和通用性,仅作为问题记录

  1. 启动tomcat后,Quartz调度任务没有正常启动
  2. 只用在执行queryPollingState【查询任务状态】方法后,相关Spring配置信息才被加载,调入任务启动
  3. 若想自动启动需要在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>
  1. 但该种配置会在首次执行queryPollingState时,启动两个调度计划表,从而出现调度两次的问题
  2. 可能时原始Spring在web.xml中配置不太正确,但因项目原因未进行修改或尝试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值