Quartz 框架搭建

本文参考了网络资料(均以超链接标注),对于其是否为原创,本文不付任何责任。

Quartz 是比较常用的任务调度框架,关于基础使用请参考Documentation。本文主要就实际运用中涉及到的动态加载和集群配置做介绍。

动态任务

动态任务的核心思路是将XML配置转换为管理类统一管理。

quartz-config.xml 如下

	<bean id="schedulerBeanFactory" class="xxx.QuartzSpringJobFactory"/>
	<!-- factory -->
	<bean id="schedulerFactory"
		class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="autoStartup" value="true" /> 
	</bean>
	
	<bean id="quartzJobService" class="xxx.QuartzJobServiceImpl"/>

QuartzSpringJobFactory.class

public class QuartzSpringJobFactory extends SpringBeanJobFactory{

	@Autowired
    private AutowireCapableBeanFactory beanFactory;


	/**
     * 这里我们覆盖了super的createJobInstance方法,对其创建出来的类再进行autowire。
     */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object jobInstance = super.createJobInstance(bundle);
        beanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}

QuartzJobService.class 实现类 QuartzJobServiceImpl

package xxx.impl;

import java.util.Calendar;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import xxx.ClusterQuartzJob;
import xxx.QuartzJob;
import xxx.QuartzJobService;

public class QuartzJobServiceImpl implements QuartzJobService{
	
	private Logger logger = LoggerFactory.getLogger(QuartzJobServiceImpl.class);
	
	@Autowired
	private SchedulerFactoryBean schedulerFactory;
	@Autowired
	private BeanFactory schedulerBeanFactory;

	@Override
	public JobDetail getJobDetail(String name, String group) {
		JobKey jobKey = new JobKey(name, group);
		try {
			JobDetail jobDetail = schedulerFactory.getScheduler().getJobDetail(jobKey);
			return jobDetail;
		} catch (SchedulerException e) {
			this.logger.error("{}",e);
		}
		return null;
	}

	@Override
	public void addJob(QuartzJob job) {
		try {
			JobDetail jobDetail = schedulerFactory.getScheduler().getJobDetail(new JobKey(job.getName(), job.getGroupCode()));
			if (null != jobDetail) {
				throw new JobExecutionException("Group:" + job.getGroupCode() + ", name:" + job.getName() + ". Job already exist.");
			}
			
			jobDetail = this.getJobDetail(job);
			if (null != jobDetail) {
				CronTrigger cronTrigger = this.getCronTrigger(job,jobDetail);
				
				schedulerFactory.getScheduler().addJob(jobDetail, true);
				schedulerFactory.getScheduler().scheduleJob(cronTrigger);
				if (job.getStartNow() && null == job.getStartTime() && Calendar.getInstance().getTime().after(job.getStartTime())) {
					cronTrigger.getTriggerBuilder().startNow();
				}
			}
		} catch (Exception e) {
			this.logger.error("{}",e);
		}
	}


	@Override
	public void removeJob(QuartzJob job) {
		try {
			JobKey jobKey = new JobKey(job.getName(), job.getGroupCode());
			JobDetail jobDetail = schedulerFactory.getScheduler().getJobDetail(jobKey);
			if (null == jobDetail) {
				throw new JobExecutionException("Group:" + job.getGroupCode() + ", name:" + job.getName() + ". Job not exist.");
			}
			schedulerFactory.getScheduler().pauseTrigger(new TriggerKey(job.getName(), job.getGroupCode()));
			schedulerFactory.getScheduler().deleteJob(jobKey);
		} catch (Exception e) {
			this.logger.error("{}",e);
		}
	}

	@Override
	public void stopJob(QuartzJob job) {
		try {
			JobKey jobKey = new JobKey(job.getName(), job.getGroupCode());
			JobDetail jobDetail = schedulerFactory.getScheduler().getJobDetail(jobKey);
			if (null == jobDetail) {
				throw new JobExecutionException("Group:" + job.getGroupCode() + ", name:" + job.getName() + ". Job not exist.");
			}
			schedulerFactory.getScheduler().pauseTrigger(new TriggerKey(job.getName(), job.getGroupCode()));
			schedulerFactory.getScheduler().pauseJob(jobKey);
		} catch (Exception e) {
			this.logger.error("{}",e);
		}
	}

	@Override
	public void resumeJob(QuartzJob job) {
		try {
			JobKey jobKey = new JobKey(job.getName(), job.getGroupCode());
			JobDetail jobDetail = schedulerFactory.getScheduler().getJobDetail(jobKey);
			if (null == jobDetail) {
				throw new JobExecutionException("Group:" + job.getGroupCode() + ", name:" + job.getName() + ". Job not exist.");
			}

			schedulerFactory.getScheduler().resumeJob(jobKey);
			schedulerFactory.getScheduler().resumeTrigger(new TriggerKey(job.getName(), job.getGroupCode()));
		} catch (Exception e) {
			this.logger.error("{}",e);
		}
	}

	@Override
	public void reschedule(QuartzJob job) {
		try {
			JobKey jobKey = new JobKey(job.getName(), job.getGroupCode());
			JobDetail jobDetail = schedulerFactory.getScheduler().getJobDetail(jobKey);
			if (null == jobDetail) {
				throw new JobExecutionException("Group:" + job.getGroupCode() + ", name:" + job.getName() + ". Job not exist.");
			}
			
			TriggerKey triggerKey = new TriggerKey(job.getName(), job.getGroupCode());
			CronTrigger newTrigger = this.getCronTrigger(job,jobDetail);
			schedulerFactory.getScheduler().rescheduleJob(triggerKey, newTrigger);
			if (job.getStartNow() && null != job.getStartTime() && Calendar.getInstance().getTime().after(job.getStartTime())) {
				newTrigger.getTriggerBuilder().startNow();
			}
		} catch (Exception e) {
			this.logger.error("{}",e);
		}
	}

	private JobDetail getJobDetail(QuartzJob job){
		try {
			MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean();
			jobDetailFactoryBean.setGroup(job.getGroupCode());
			jobDetailFactoryBean.setName(job.getName());
			jobDetailFactoryBean.setBeanFactory(this.schedulerBeanFactory);
			jobDetailFactoryBean.setTargetBeanName(job.getBeanName());
			jobDetailFactoryBean.setTargetMethod(job.getExecuteMethod());
			jobDetailFactoryBean.setConcurrent(job.getConcurrent());
			jobDetailFactoryBean.afterPropertiesSet();
			
			return jobDetailFactoryBean.getObject();
		} catch (Exception e) {
			this.logger.error("{}",e);
			return null;
		}
	}
	
	private CronTrigger getCronTrigger(QuartzJob job, JobDetail jobDetail){
		CronTriggerFactoryBean cronTrigger = new CronTriggerFactoryBean();
		cronTrigger.setGroup(job.getGroupCode());
		cronTrigger.setName(job.getName());
		cronTrigger.setJobDetail(jobDetail);
		cronTrigger.setCronExpression(job.getCronExpression());
		
		try {
			cronTrigger.afterPropertiesSet();
		} catch (Exception e) {
			this.logger.error("{}",e);
		}
		if (null != job.getStartTime()) {
			cronTrigger.getObject().getTriggerBuilder().startAt(job.getStartTime());
		}
		if (null != job.getEndTime()) {
			cronTrigger.getObject().getTriggerBuilder().endAt(job.getEndTime());
		}
		
		return cronTrigger.getObject();
	}
}

用到的几个基础类

public class QuartzJob implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -1007495492595068279L;
	
	private Long id;
	private String groupCode;
	private String name;
	private String beanName;
	private String executeMethod;
	private Object[] param;//执行任务方法的参数
	private Date startTime;
	private Date endTime;
	private String cronExpression;
	private Boolean concurrent = Boolean.FALSE;
	private Boolean startNow = Boolean.FALSE;
	private QuartzJobStatus status;

	public Object[] getParam() {
		return param;
	}

	public void setParam(Object[] param) {
		this.param = param;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getGroupCode() {
		return groupCode;
	}

	public String getName() {
		return name;
	}

	public String getBeanName() {
		return beanName;
	}

	public String getExecuteMethod() {
		return executeMethod;
	}

	public Date getStartTime() {
		return startTime;
	}

	public Date getEndTime() {
		return endTime;
	}

	public String getCronExpression() {
		return cronExpression;
	}

	public Boolean getConcurrent() {
		return concurrent;
	}

	public Boolean getStartNow() {
		return startNow;
	}

	public QuartzJobStatus getStatus() {
		return status;
	}

	public void setGroupCode(String groupCode) {
		this.groupCode = groupCode;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setBeanName(String beanName) {
		this.beanName = beanName;
	}

	public void setExecuteMethod(String executeMethod) {
		this.executeMethod = executeMethod;
	}

	public void setStartTime(Date startTime) {
		this.startTime = startTime;
	}

	public void setEndTime(Date endTime) {
		this.endTime = endTime;
	}

	public void setCronExpression(String cronExpression) {
		this.cronExpression = cronExpression;
	}

	public void setConcurrent(Boolean concurrent) {
		this.concurrent = concurrent;
	}

	public void setStartNow(Boolean startNow) {
		this.startNow = startNow;
	}

	public void setStatus(QuartzJobStatus status) {
		this.status = status;
	}

	
}

public enum QuartzJobStatus {

	DEFAULT(-1),RUNNING(0),STOPPED(1),DELETED(9);
	
	private final Integer value;
	
	private QuartzJobStatus(Integer value){
		this.value = value;
	}

	public Integer getValue() {
		return value;
	}
	
	public static QuartzJobStatus get(Integer value) {
		for (QuartzJobStatus status : QuartzJobStatus.values()) {
			if (status.getValue().intValue() == value.intValue()) {
				return status;
			}
		}
		return DEFAULT;
	}
}

业务系统独立封装quartz的db配置,通过QuartzJob类传递配置,动态控制quartz 任务。

集群

PART 1 已经介绍了动态控制,但是如果应用的微服务等分布式服务中,就会出现任务重复执行等一系列问题。

quartz本身支持分布式配置,Spring整合Quartz定时任务 在集群、分布式系统中的应用 详细介绍了配置。其中涉及到的sql脚本可以通过官网下载获得。(注:2.1x和2.2x有所不同,qrtz_fired_triggers 中多了SCHED_TIME BIGINT(13))。

修改配置后,启动项目会遇到 MethodInvokingJobDetailFactoryBean 无法序列化的bug,SPR-3797。网上很多转载均是已copy了此bug的解决方案。Spring 4.1 + 版本,MethodInvokingJobDetailFactoryBean已经有所调整,所以不再适用此解决方案。

	@Override
	@SuppressWarnings("unchecked")
	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 ? MethodInvokingJob.class : StatefulMethodInvokingJob.class);

		// Build JobDetail instance.
		JobDetailImpl jdi = new JobDetailImpl();
		jdi.setName(name);
		jdi.setGroup(this.group);
		jdi.setJobClass((Class) jobClass);
		jdi.setDurability(true);
		jdi.getJobDataMap().put("methodInvoker", this);
		this.jobDetail = jdi;

		postProcessJobDetail(this.jobDetail);
	}

如上图,Spring4.1+版本已经修改了afterPropertiesSet方法,且 new JobDetail 在quartz2.2x版本已不存在。所以此方案无效。

spring集成quartz中java.lang.ClassNotFoundException: org.quartz.impl.JobDetailImpl异常解决方法 中具体描述了这个问题。

二、集群环境下定时调度的解决方案之Quartz集群 提供了一种解决方案,测试可行。

(待补充。该方案重写 execute 时,尚未测试与spring的集成)

************ 2018/9/5 补充************

quartz2.2x之后,可以借助JobDataMap进行参数传递。

新增自定义quartz job类

package xxx.quartz;

import java.lang.reflect.Method;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ReflectionUtils;

public class ClusterQuartzJob implements Job {
	
	private Logger logger = LoggerFactory.getLogger(ClusterQuartzJob.class);

	public static final String BEAN_NAME = "beanName";
	public static final String METHOD_NAME = "methodName";

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		try {
			JobDataMap jobDataMap = context.getTrigger().getJobDataMap();	
			ApplicationContext ctx = this.getApplicationContext(context);
			Object bean = ctx.getBean(jobDataMap.getString(ClusterQuartzJob.BEAN_NAME));
			Method method = ReflectionUtils.findMethod(bean.getClass(), jobDataMap.getString(ClusterQuartzJob.METHOD_NAME));
			ReflectionUtils.invokeMethod(method, bean);
		} catch (Exception e) {
			this.logger.error("{}",e);
		}
	}

	private static final String APPLICATION_CONTEXT_KEY = "applicationContextKey";
	private ApplicationContext getApplicationContext(JobExecutionContext context) throws Exception {
		ApplicationContext appCtx = null;
		appCtx = (ApplicationContext) context.getScheduler().getContext().get(APPLICATION_CONTEXT_KEY);
		if (appCtx == null) {
			throw new JobExecutionException("No application context available in scheduler context for key \"" + APPLICATION_CONTEXT_KEY + "\"");
		}
		return appCtx;
}

}

任务动态增加时,通过map传递参数。

	@Override
	public void addClusterJob(QuartzJob job){
		try {
			JobDetail jobDetail = schedulerFactory.getScheduler().getJobDetail(new JobKey(job.getName(), job.getGroupCode()));
			if (null != jobDetail) {
				this.logger.info("Group:{}, name:{} already exist.", job.getGroupCode(), job.getName());
				this.removeJob(job);
			}
			Scheduler scheduler = schedulerFactory.getScheduler();
			jobDetail = JobBuilder.newJob(ClusterQuartzJob.class)
					.withIdentity(job.getName(), job.getGroupCode()).build();
			Trigger trigger = TriggerBuilder
					.newTrigger()
					.withIdentity(job.getExecuteMethod(), job.getGroupCode())
					.startNow()
					.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression())).build();

			JobDataMap jobDataMap = trigger.getJobDataMap();
			jobDataMap.put(ClusterQuartzJob.BEAN_NAME, job.getBeanName());
			jobDataMap.put(ClusterQuartzJob.METHOD_NAME, job.getExecuteMethod());
			scheduler.scheduleJob(jobDetail, trigger);
			if (scheduler.isStarted()) {
				scheduler.start();
			}
		} catch (Exception e) {
			this.logger.error("{}",e);
		}
	}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值