Spring+Quartz实现动态添加定时任务(一)
在最近工作中,由于涉及到定时任务特别多,而这些工作又是由下属去完成的,在生成环境中经常会出现业务逻辑错误,分析下来多数是定时任务运行问题,所以就希望把定时任务优化一下,主要实现2个方面
1.定时任务动态配置及持久化
2.可视化的管理界面,可以非常清晰的管理自己的所有定时任务
首先,我们先来看第一个目标
一、版本说明
spring3.1以下的版本必须使用quartz1.x系列,3.1以上的版本才支持quartz 2.x,不然会出错。
原因:spring对于quartz的支持实现,org.springframework.scheduling.quartz.CronTriggerBean继承了org.quartz.CronTrigger,在quartz1.x系列中org.quartz.CronTrigger是个类,而在quartz2.x系列中org.quartz.CronTrigger变成了接口,从而造成无法用spring的方式配置quartz的触发器(trigger)
此示例所选版本:4.0.2.RELEASE,quartz版本2.2.1
二、pom中引用相关包
三、数据结构
t_timetask 任务表
t_timetask_log 任务运行日志
接下来把代码抛出来
1.项目启动时,初始化数据库中的定时任务
- /
- 根据上下文获取spring类
- @author
- /
- public class InitQuartzJob implements ApplicationContextAware {
- private static final Logger logger = LoggerFactory.getLogger(InitQuartzJob.class);
- private static ApplicationContext appCtx;
- public static SchedulerFactoryBean schedulerFactoryBean = null;
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- if (this.appCtx == null) {
- this.appCtx = applicationContext;
- }
- }
- public static void init() {
- schedulerFactoryBean = (SchedulerFactoryBean) appCtx.getBean(SchedulerFactoryBean.class);
- Scheduler scheduler = schedulerFactoryBean.getScheduler();
- try {
- logger.info(scheduler.getSchedulerName());
- } catch (SchedulerException e1) {
- // TODO Auto-generated catch block
- e1.printStackTrace();
- }
- // 这里从数据库中获取任务信息数据
- STimetaskService sTimetaskService = (STimetaskService) ApplicationContextUtils.getBean(STimetaskService.class);
- STimetaskExample example = new STimetaskExample();
- Criteria c = example.createCriteria();
- c.andJobStatusEqualTo("1"); // 已发布的定时任务
- List<STimetask> list = sTimetaskService.selectByExample(example);
- List<ScheduleJob> jobList = new ArrayList<ScheduleJob>();
- for (STimetask sTimetask : list) {
- ScheduleJob job1 = new ScheduleJob();
- job1.setJobId(sTimetask.getId());
- job1.setJobGroup(sTimetask.getGroupName()); // 任务组
- job1.setJobName(sTimetask.getName());// 任务名称
- job1.setJobStatus(sTimetask.getJobStatus()); // 任务发布状态
- job1.setIsConcurrent(sTimetask.getConcurrent() ? "1" : "0"); // 运行状态
- job1.setCronExpression(sTimetask.getCron());
- job1.setBeanClass(sTimetask.getBeanName());// 一个以所给名字注册的bean的实例
- job1.setMethodName(sTimetask.getMethodName());
- job1.setJobData(sTimetask.getJobData()); // 参数
- jobList.add(job1);
- }
- for (ScheduleJob job : jobList) {
- try {
- addJob(job);
- } catch (SchedulerException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- /
- 添加任务
- @param scheduleJob
- @throws SchedulerException
- /
- public static void addJob(ScheduleJob job) throws SchedulerException {
- if (job == null || !ScheduleJob.STATUS_RUNNING.equals(job.getJobStatus())) {
- return;
- }
- Scheduler scheduler = schedulerFactoryBean.getScheduler();
- logger.debug(scheduler + "...........................................add");
- TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
- CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
- // 不存在,创建一个
- if (null == trigger) {
- Class clazz = ScheduleJob.CONCURRENT_IS.equals(job.getIsConcurrent()) ? QuartzJobFactory.class
- : QuartzJobFactoryDisallowConcurrentExecution.class;
- JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(job.getJobName(), job.getJobGroup()).usingJobData("data", job.getJobData()).build();
- jobDetail.getJobDataMap().put("scheduleJob", job);
- CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
- trigger = TriggerBuilder.newTrigger().withDescription(job.getJobId().toString()).withIdentity(job.getJobName(), job.getJobGroup())
- .withSchedule(scheduleBuilder).build();
- scheduler.scheduleJob(jobDetail, trigger);
- } else {
- // Trigger已存在,那么更新相应的定时设置
- CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
- // 按新的cronExpression表达式重新构建trigger
- trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).usingJobData("data", job.getJobData()).withSchedule(scheduleBuilder).build();
- // 按新的trigger重新设置job执行
- scheduler.rescheduleJob(triggerKey, trigger);
- }
- }
- }
- 2.1工具类
- package com.ffxl.cloud.quartz;
- import org.apache.log4j.Logger;
- import org.quartz.Job;
- import org.quartz.JobExecutionContext;
- import org.quartz.JobExecutionException;
- /**
- @Description: 计划任务执行处 无状态
- @author wison
- @date 2017年11月11日 下午5:05:47
- /
- public class QuartzJobFactory implements Job {
- public final Logger log = Logger.getLogger(this.getClass());
- public void execute(JobExecutionContext context) throws JobExecutionException {
- ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
- TaskUtils.invokMethod(scheduleJob);
- }
- }
2.2
- /
- @Description: 若一个方法一次执行不完下次轮转时则等待改方法执行完后才执行下一次操作
- @author wison
- @date 2017年11月11日 下午5:05:47
- */
- @DisallowConcurrentExecution
- public class QuartzJobFactoryDisallowConcurrentExecution implements Job {
- public final Logger log = Logger.getLogger(this.getClass());
- public void execute(JobExecutionContext context) throws JobExecutionException {
- ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
- TaskUtils.invokMethod(scheduleJob);
- }
- }
2.3
copy
public class ScheduleJob { public static final String STATUS_RUNNING = "1"; //正在运行 public static final String STATUS_NOT_RUNNING = "0"; // 已停止 public static final String CONCURRENT_IS = "1"; public static final String CONCURRENT_NOT = "0"; private String jobId; private Date createTime; private Date updateTime; /** 任务名称 / private String jobName; /** 任务分组 / private String jobGroup; /** 任务状态 是否启动任务 / private String jobStatus; /** cron表达式 / private String cronExpression; /** 描述 / private String description; /** 任务执行时调用哪个类的方法 包名+类名 / private String beanClass; /** 任务是否有状态 / private String isConcurrent; /** spring bean / private String springId; /** 任务调用的方法名 / private String methodName; private String jobData; public String getJobData() { return jobData; } public void setJobData(String jobData) { this.jobData = jobData; } public String getJobId() { return jobId; } public void setJobId(String jobId) { this.jobId = jobId; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public Date getUpdateTime() { return updateTime; } public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } public String getJobName() { return jobName; } public void setJobName(String jobName) { this.jobName = jobName; } public String getJobGroup() { return jobGroup; } public void setJobGroup(String jobGroup) { this.jobGroup = jobGroup; } public String getJobStatus() { return jobStatus; } public void setJobStatus(String jobStatus) { this.jobStatus = jobStatus; } public String getCronExpression() { return cronExpression; } public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getBeanClass() { return beanClass; } public void setBeanClass(String beanClass) { this.beanClass = beanClass; } public String getIsConcurrent() { return isConcurrent; } public void setIsConcurrent(String isConcurrent) { this.isConcurrent = isConcurrent; } public String getSpringId() { return springId; } public void setSpringId(String springId) { this.springId = springId; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } }
2.4
copy
package com.ffxl.cloud.quartz; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; public final class SpringUtils implements BeanFactoryPostProcessor { private static ConfigurableListableBeanFactory beanFactory; // Spring应用上下文环境 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } /** 获取对象 @param name @return Object 一个以所给名字注册的bean的实例 @throws org.springframework.beans.BeansException / @SuppressWarnings("unchecked") public static <T> T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } / 获取类型为requiredType的对象 @param clz @return @throws org.springframework.beans.BeansException */ public static <T> T getBean(Class<T> clz) throws BeansException { @SuppressWarnings("unchecked") T result = (T) beanFactory.getBean(clz); return result; } / 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true @param name @return boolean / public static boolean containsBean(String name) { return beanFactory.containsBean(name); } /** 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) @param name @return boolean @throws org.springframework.beans.factory.NoSuchBeanDefinitionException / public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return beanFactory.isSingleton(name); } /** @param name @return Class 注册对象的类型 @throws org.springframework.beans.factory.NoSuchBeanDefinitionException / public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); } / 如果给定的bean名字在bean定义中有别名,则返回这些别名 @param name @return @throws org.springframework.beans.factory.NoSuchBeanDefinitionException */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return beanFactory.getAliases(name); } }
2.5
copy
package com.ffxl.cloud.quartz; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Date; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import com.ffxl.cloud.model.STimetaskLog; import com.ffxl.cloud.service.STimetaskLogService; import com.ffxl.cloud.util.wxmsg.ApplicationContextUtils; import com.ffxl.platform.util.UUIDUtil; public class TaskUtils { public final static Logger log = Logger.getLogger(TaskUtils.class); /** 通过反射调用scheduleJob中定义的方法 @param scheduleJob / @SuppressWarnings("unchecked") public static void invokMethod(ScheduleJob scheduleJob) { Object object = null; Class clazz = null; boolean flag = true; if (StringUtils.isNotBlank(scheduleJob.getSpringId())) { object = SpringUtils.getBean(scheduleJob.getSpringId()); } else if (StringUtils.isNotBlank(scheduleJob.getBeanClass())) { try { clazz = Class.forName(scheduleJob.getBeanClass()); object = clazz.newInstance(); } catch (Exception e) { flag = false; STimetaskLogService sTimetaskLogService = (STimetaskLogService) ApplicationContextUtils.getBean(STimetaskLogService.class); STimetaskLog tlog = new STimetaskLog(); tlog.setId(UUIDUtil.getUUID()); tlog.setCreateDate(new Date()); tlog.setJobId(scheduleJob.getJobId().toString()); tlog.setReason("未找到"+scheduleJob.getBeanClass()+"对应的class"); tlog.setState("fail"); sTimetaskLogService.insertSelective(tlog); e.printStackTrace(); } } if (object == null) { flag = false; log.error("任务名称 = [" + scheduleJob.getJobName() + "]---------------未启动成功,请检查是否配置正确!!!"); STimetaskLogService sTimetaskLogService = (STimetaskLogService) ApplicationContextUtils.getBean(STimetaskLogService.class); STimetaskLog tlog = new STimetaskLog(); tlog.setId(UUIDUtil.getUUID()); tlog.setCreateDate(new Date()); tlog.setJobId(scheduleJob.getJobId().toString()); tlog.setReason("未找到"+scheduleJob.getBeanClass()+"对应的class"); tlog.setState("fail"); sTimetaskLogService.insertSelective(tlog); return; } clazz = object.getClass(); Method method = null; try { method = clazz.getDeclaredMethod(scheduleJob.getMethodName(), new Class[] { String.class }); } catch (NoSuchMethodException e) { flag = false; log.error("任务名称 = [" + scheduleJob.getJobName() + "]---------------未启动成功,方法名设置错误!!!"); STimetaskLogService sTimetaskLogService = (STimetaskLogService) ApplicationContextUtils.getBean(STimetaskLogService.class); STimetaskLog tlog = new STimetaskLog(); tlog.setId(UUIDUtil.getUUID()); tlog.setCreateDate(new Date()); tlog.setJobId(scheduleJob.getJobId().toString()); tlog.setReason("未找到"+scheduleJob.getBeanClass()+"类下"+scheduleJob.getMethodName()+"对应的方法"); tlog.setState("fail"); sTimetaskLogService.insertSelective(tlog); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (method != null) { try { method.invoke(object, scheduleJob.getJobData()); } catch (IllegalAccessException e) { flag = false; STimetaskLogService sTimetaskLogService = (STimetaskLogService) ApplicationContextUtils.getBean(STimetaskLogService.class); STimetaskLog tlog = new STimetaskLog(); tlog.setId(UUIDUtil.getUUID()); tlog.setCreateDate(new Date()); tlog.setJobId(scheduleJob.getJobId().toString()); tlog.setReason("未找到"+scheduleJob.getBeanClass()+"类下"+scheduleJob.getMethodName()+"对应的方法参数设置错误"); tlog.setState("fail"); sTimetaskLogService.insertSelective(tlog); e.printStackTrace(); } catch (IllegalArgumentException e) { flag = false; STimetaskLogService sTimetaskLogService = (STimetaskLogService) ApplicationContextUtils.getBean(STimetaskLogService.class); STimetaskLog tlog = new STimetaskLog(); tlog.setId(UUIDUtil.getUUID()); tlog.setCreateDate(new Date()); tlog.setJobId(scheduleJob.getJobId().toString()); tlog.setReason("未找到"+scheduleJob.getBeanClass()+"类下"+scheduleJob.getMethodName()+"对应的方法参数设置错误"); tlog.setState("fail"); sTimetaskLogService.insertSelective(tlog); e.printStackTrace(); } catch (InvocationTargetException e) { flag = false; STimetaskLogService sTimetaskLogService = (STimetaskLogService) ApplicationContextUtils.getBean(STimetaskLogService.class); STimetaskLog tlog = new STimetaskLog(); tlog.setId(UUIDUtil.getUUID()); tlog.setCreateDate(new Date()); tlog.setJobId(scheduleJob.getJobId().toString()); tlog.setReason("未找到"+scheduleJob.getBeanClass()+"类下"+scheduleJob.getMethodName()+"对应的方法参数设置错误"); tlog.setState("fail"); sTimetaskLogService.insertSelective(tlog); e.printStackTrace(); } } if(flag){ System.out.println("任务名称 = [" + scheduleJob.getJobName() + "]----------启动成功"); STimetaskLogService sTimetaskLogService = (STimetaskLogService) ApplicationContextUtils.getBean(STimetaskLogService.class); STimetaskLog tlog = new STimetaskLog(); tlog.setId(UUIDUtil.getUUID()); tlog.setCreateDate(new Date()); tlog.setJobId(scheduleJob.getJobId().toString()); tlog.setState("success"); sTimetaskLogService.insertSelective(tlog); } } }
四、配置
dispatcher-servlet.xml中添加如下配置
- <!-- 初始化springUtils -->
- <bean id="springUtils" class="com.ffxl.cloud.quartz.SpringUtils" />
- <!-- 初始化Scheduler -->
- <bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" />
- <!-- 初始化job -->
- <bean id="initQuartzJob" class="com.ffxl.quartz.init.InitQuartzJob" init-method="init" lazy-init="false" />
以上第一个目标就完成,定时任务从数据库中读取并按照指定表达式运行,可视化界面我们将在下一篇中讲解