1. 程式人生 > >Spring+Quartz實現動態新增定時任務(一)

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

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

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

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" />


以上第一個目標就完成,定時任務從資料庫中讀取並按照指定表示式執行,視覺化介面我們將在下一篇中講解