spring4.2.1+quartz2.2.1 实现定时任务数据库动态配置

spring4.2.1+quartz2.2.1 实现定时任务数据库动态配置
本示例的实现思路是将spring提供的计划管理转化为org.quartz中的scheduler管理。本示例实现了非集群环境下,通过数据库动态管理定时任务,可以通过前台新增、修改定时任务。在应用数据量不是特别大的情况下,也可以考虑将定时任务部署在集群的某一台机器上,以满足在集群环境中的应用。
一、环境信息
本示例的环境信息:
Spring 4.2.1 + quartz 2.2.1 + Mysql 5.7
二、相关代码及配置
1、Maven配置
引入jar包,本项目是maven管理的项目
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency>
2、数据库脚本

CREATE TABLE `t_quartz_model` (
  `id` varchar(40) NOT NULL,
  `jobid` varchar(40) DEFAULT NULL COMMENT '任务名称id',
  `jobname` varchar(100) DEFAULT NULL COMMENT '任务名称',
  `cronexpression` varchar(20) DEFAULT NULL COMMENT '触发器时钟表达式',
  `isbean` varchar(2) DEFAULT NULL COMMENT '加载对象是否为spring注册的bean',
  `targetobject` varchar(500) DEFAULT NULL COMMENT '要执行的任务对象:若为bean则给bean名称,否则给类的包路径加类名',
  `method` varchar(100) DEFAULT NULL COMMENT '要执行的任务对象中的方法名',
  `concurrent` varchar(20) DEFAULT NULL COMMENT '执行的任务是否为异步模式:1为是,0为否',
  `state` varchar(20) DEFAULT NULL COMMENT '任务是否启用:1为启用,0为停用',
  `operator` varchar(20) DEFAULT NULL COMMENT '操作人',
  `operatorname` varchar(20) DEFAULT NULL COMMENT '操作人名字',
  `operatingtime` date DEFAULT NULL COMMENT '操作时间',
  `des` varchar(20) DEFAULT NULL COMMENT '任务描述',
  PRIMARY KEY (`id`)
)

3、关键代码及配置
配置spring文件,新建一个spring-task.xml的文件

<!-- 自定义job -->
<bean id="quartzBean" class="com.quartz.framework.service.QuartzMain">
	 <property name="scheduler" ref="schedulerManager"></property>
</bean>
<!-- 定义jobdetail -->
<bean id="quartzJobDetail"
	class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
	<property name="targetObject" ref="quartzBean"></property>
	<property name="targetMethod" value="startQuartz"></property>
	<property name="concurrent" value="false" />
</bean>
<!-- 触发器 -->
<bean id="quartzTrigger"
	class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
	<property name="jobDetail" ref="quartzJobDetail"></property>
	<property name="startDelay" value="1000"></property>
	<property name="repeatInterval" value="1000"></property>
</bean>
<!-- 任务计划,总管理类,将lazy-init='false'那么容器启动就会执行调度程序 -->
<bean id="schedulerManager" lazy-init="false" autowire="no"
	class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
	<property name="triggers">
	   <list>
		 <ref bean="quartzTrigger" />
	   </list>
	</property>
</bean>

当spring-task.xml被spring加载时,SchedulerFactoryBean就会创建scheduler,默认由StdSchedulerFactory实例化QuartzScheduler。代码如下:

 private String getSchedulerName() {
        return this.cfg.getStringProperty("org.quartz.scheduler.instanceName", "QuartzScheduler");
    }
    public Scheduler getScheduler() throws SchedulerException {
        if (this.cfg == null) {
            this.initialize();
        }

        SchedulerRepository schedRep = SchedulerRepository.getInstance();
        Scheduler sched = schedRep.lookup(this.getSchedulerName());
        if (sched != null) {
            if (!sched.isShutdown()) {
                return sched;
            }

            schedRep.remove(this.getSchedulerName());
        }

        sched = this.instantiate();
        return sched;
    }

然后就是QuartzScheduler中的一系列方法,包括quartz自带的线程池等等。本示例中的初始化实际是将com.quartz.framework.service.QuartzMain(自定义的类)中的startQuartz作为一个job,并配置了一个每隔一秒执行一次的触发器。
com.quartz.framework.service.QuartzMain的实现如下:

public class QuartzMain implements BeanFactoryAware {
	private Logger logger=Logger.getLogger(QuartzMain.class);
	private static int repeatNum=0;
	/**
	 * bean工厂
	 */
	private BeanFactory beanFactory;
	private Scheduler scheduler;
	@Resource
	private QuartzModelMapper quartzModelMapper;
	/**
	 * 功能:启动定时任务入口
	 * 日期:2018-10-17上午10:17:18
	 * @throws Exception
	 */
	public void startQuartz() throws Exception{
		if (repeatNum==0) {
			//启动时从数据库中取出所有的任务
			List< QuartzModel> qmList=this.getJob();
			if (!CollectionUtils.isEmpty(qmList)) {
				for (QuartzModel quartzModel : qmList) {
					this.initJob(quartzModel);
				}
			}
			repeatNum++;
		}
	}
	
	/**
	 * 功能:初始化定时任务
	 * 日期:2018-10-17上午11:44:18
	 * @param qm
	 */
	public void initJob(QuartzModel qm){
		try {
			TriggerKey triggerKey=TriggerKey.triggerKey(qm.getJobid()+"Trigger");
			CronTrigger cronTrigger=(CronTrigger) scheduler.getTrigger(triggerKey);
			if (cronTrigger !=null) {
				update(qm,cronTrigger);
			}else{//不存在任务就添加
				if (qm.getState().equals("1")) {
					create(qm);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 功能:更新定时任务
	 * 日期:2018-10-17
	 * @param qm
	 * @param cronTrigger
	 * @throws Exception 
	 */
	private void update(QuartzModel qm,CronTrigger cronTrigger) throws Exception{
		delete(qm);
		if (qm.getState().equals("1")) {
			create(qm);
		}
	}
	
	/**
	 * 功能:删除定时任务
	 * 日期:2018-10-17下午3:04:18
	 * @param qm
	 * @throws Exception
	 */
	private void delete(QuartzModel qm) throws Exception{
		JobKey jobKey =JobKey.jobKey(qm.getJobid());
        TriggerKey triggerKey =TriggerKey.triggerKey(qm.getJobid()+"Trigger");
        //停止任务触发器
		scheduler.pauseTrigger(triggerKey);
		//删除任务触发器
		scheduler.unscheduleJob(triggerKey);
		//移除任务
		scheduler.deleteJob(jobKey);
	}
	/**
	 * 功能:将任务添加到总的执行任务中
	 * 日期:2018-10-17下午2:56:15
	 * @param qm
	 * @throws Exception
	 */
	private void create(QuartzModel qm) throws Exception{
        qm.setId(qm.getJobid());
        qm.setOperatingtime(new Date());
        //新建一个基于spring的job管理类
        MethodInvokingJobDetailFactoryBean mijdfb=new MethodInvokingJobDetailFactoryBean();
        mijdfb.setName(qm.getJobid());
        //判断任务是spring的bean还是还是class
        if (qm.getIsbean().equals("1")) {
			mijdfb.setTargetObject(beanFactory.getBean(qm.getTargetobject()));
		}else{
			mijdfb.setTargetObject(Class.forName(qm.getTargetobject()).newInstance());
		}
        mijdfb.setTargetMethod(qm.getMethod());
        mijdfb.setConcurrent(qm.getConcurrent().equals("1"));
        //实例化jobdetail
        mijdfb.afterPropertiesSet();
        //将job管理的detail转换为quartz管理的job
        JobDetail jd=new JobDetailImpl();
        jd=mijdfb.getObject();
        //获取jobkey
        //将job添加到管理类中
        scheduler.addJob(jd, true);
        //添加触发器
        String triggerName=qm.getJobid()+"Trigger";
        CronTriggerFactoryBean ctb=new CronTriggerFactoryBean();
        ctb.setName(triggerName);
        ctb.setCronExpression(qm.getCronexpression());
        ctb.setJobDetail(jd);
        ctb.afterPropertiesSet();
        CronTrigger cronTrigger= ctb.getObject();
        //为job添加触发器
        scheduler.scheduleJob(cronTrigger);
        //刷新管理类
        scheduler.rescheduleJob(cronTrigger.getKey(), cronTrigger);
	}
	
	/**
	 * 功能:从数据库获取要执行的任务信息
	 * 日期:2018-10-17上午11:10:18
	 * @return
	 */
	private List<QuartzModel> getJob(){
		List<QuartzModel> list=null;
		try {
			QuartzModel quartzModel=null;
			list=quartzModelMapper.getAll(quartzModel);
			for (QuartzModel qm : list) {
				// 任务名称
				String jobName = qm.getJobname();
				// 时间表达式
				String cronexpression = qm.getCronexpression();
				// 是否为bean
				String isbean = qm.getIsbean();
				// 目标类
				String targetobject = qm.getTargetobject();
				// 目标方法
				String method = qm.getMethod();
				// 是否同步
				String concurrent = qm.getConcurrent();
				// 是否启用
				String state = qm.getState();
				if (!StringUtils.hasText(jobName) || !StringUtils.hasText(cronexpression) || !StringUtils.hasText(isbean)
						|| !StringUtils.hasText(targetobject) || !StringUtils.hasText(method)
						|| !StringUtils.hasText(concurrent)) {
					StringBuffer sb = new StringBuffer();
					sb.append("jobName:" + jobName);
					sb.append("cronexpression:" + cronexpression);
					sb.append("isbean:" + isbean);
					sb.append("targetobject:" + targetobject);
					sb.append("method:" + method);
					sb.append("concurrent:" + concurrent);
					sb.append("state" + state);
					logger.info("错误的计划任务信息:" + sb.toString());
					list.remove(qm);
				}
			}	
		} catch (Exception e) {
			e.printStackTrace();
		}
		return list;
	}
	
	public Scheduler getScheduler() {
		return scheduler;
	}

	public void setScheduler(Scheduler scheduler) {
		this.scheduler = scheduler;
	}

	public BeanFactory getBeanFactory() {
		return beanFactory;
	}

	public void setBeanFactory(BeanFactory beanFactory) {
		this.beanFactory = beanFactory;
	}

}

启动定时任务入口,即startQuartz()方法,只在应用启动时执行一次,主要功能是从数据库中获取要执行的任务名、方法名及时间表达式,并添加到scheduler中。将任务由quartz的scheduler管理总的思路在spring-task.xml文件中就已经体现。
(1)实例化任务
首先实例化MethodInvokingJobDetailFactoryBean mijdfb=new MethodInvokingJobDetailFactoryBean();MethodInvokingJobDetailFactoryBean是spring自带的,并将数据库中配置的job相关信息添加到mijdfb,然后调用afterPropertiesSet()方法,这个方法会实例化一个JobDetailImpl,并将相应的信息添加到JobDetailImpl中,源码如下

  public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException {
        this.prepare();
        String name = this.name != null ? this.name : this.beanName;
        Class<?> jobClass = this.concurrent ? MethodInvokingJobDetailFactoryBean.MethodInvokingJob.class : MethodInvokingJobDetailFactoryBean.StatefulMethodInvokingJob.class;
        JobDetailImpl jdi = new JobDetailImpl();
        jdi.setName(name);
        jdi.setGroup(this.group);
        jdi.setJobClass(jobClass);
        jdi.setDurability(true);
        jdi.getJobDataMap().put("methodInvoker", this);
        this.jobDetail = jdi;
        this.postProcessJobDetail(this.jobDetail);
    }

通过mijdfb.getObject()来获取JobDetailImpl,这里有一个比较重要的就是jobid,后面会用jobid来查找任务。
(2)实例化触发器
首先实例化CronTriggerFactoryBean ctb=new CronTriggerFactoryBean(),然后添加对应job的时间表表达式等参数信息,然后调用afterPropertiesSet(),这个方法和MethodInvokingJobDetailFactoryBean 中的afterPropertiesSet()类似,最终实例化一个CronTriggerImpl,然后赋值,源码如下:

public void afterPropertiesSet() throws ParseException {
        if (this.name == null) {
            this.name = this.beanName;
        }

        if (this.group == null) {
            this.group = "DEFAULT";
        }

        if (this.jobDetail != null) {
            this.jobDataMap.put("jobDetail", this.jobDetail);
        }

        if (this.startDelay > 0L || this.startTime == null) {
            this.startTime = new Date(System.currentTimeMillis() + this.startDelay);
        }

        if (this.timeZone == null) {
            this.timeZone = TimeZone.getDefault();
        }

        CronTriggerImpl cti = new CronTriggerImpl();
        cti.setName(this.name);
        cti.setGroup(this.group);
        cti.setJobKey(this.jobDetail.getKey());
        cti.setJobDataMap(this.jobDataMap);
        cti.setStartTime(this.startTime);
        cti.setCronExpression(this.cronExpression);
        cti.setTimeZone(this.timeZone);
        cti.setCalendarName(this.calendarName);
        cti.setPriority(this.priority);
        cti.setMisfireInstruction(this.misfireInstruction);
        cti.setDescription(this.description);
        this.cronTrigger = cti;
    }

这里实例化的是CronTriggerFactoryBean对象,而在spring-task.xml文件中配置的是SimpleTriggerFactoryBean,这二者的区别是:SimpleTriggerFactoryBean实例化的SimpleTriggerImpl是在规定的时间执行一次或在规定的时间段以一定的时间间隔重复触发执行Job,而CronTriggerFactoryBean实例化的CronTriggerImpl支持时间表达式,CronTrigger支持类似日历的重复间隔,而不是单一的时间间隔。
最后,为job添加触发器,并刷新管理类scheduler。添加的任务就会根据时间表达式来执行。

以上过程是数据库中已配置好要执行的任务计划,应用在启动时自动加载,下面介绍从前台界面新增、修改、查询正在运行的定时任务
本示例前台界面是用bootstrap,Controller用的是springmvc,这两部分就不介绍了。新增的逻辑和上面创建定时任务的方法(create()方法)是类似的,只是要新生成jobid,并将任务保存到数据库,这里就不介绍;修改定时任务时首先要判断任务是否已经存在,代码如下

//先删除定时任务
JobKey jobKey =JobKey.jobKey(quartzModel.getJobid());
TriggerKey triggerKey =TriggerKey.triggerKey(quartzModel.getJobid()+"Trigger");
//任务已存在
if(scheduler.checkExists(jobKey) ){
	//停止任务触发器
	scheduler.pauseTrigger(triggerKey);
	//删除任务触发器
	scheduler.unscheduleJob(triggerKey);
	//移除任务
	scheduler.deleteJob(jobKey);
	}

如果修改的定时任务不启用,就直接到保存数据库;如果要启用,就和创建定时任务一样。
查询正在运行中的定时任务代码如下:

	public  static Object lookRunJob(){
		List<QuartzModel> list=new ArrayList<>();
		Long count=new Long(0);
		try {
			List<String> jobid=new ArrayList<>();
			for (String groupName:scheduler.getJobGroupNames()){
				for(JobKey jobKey:scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))){
					jobid.add(jobKey.getName());
				}
			}
           list=quartzModelMapper.lookRunJob(jobid);
           count=quartzModelMapper.lookRunJobCount(jobid);
		}catch (Exception e){
			e.printStackTrace();
		}
      return  JSONUtils.resultMap(count,list);
	}

jobKey.getName()获取到的就是jobid。这里和上面创建job时是关联的,在JobDetailImpl中生成jobkey的方法如下:

  public String getName() {
        return this.name;
    }
    
 public JobKey getKey() {
        if (this.key == null) {
            if (this.getName() == null) {
                return null;
            }

            this.key = new JobKey(this.getName(), this.getGroup());
        }

        return this.key;
    }

也就是说jobid当做任务的name在删除、修改以及查询正在执行的定时任务时都有作用。
总结
这是本人的第一篇博客,还有很多不足之处,还请多多包涵。在写这篇博客期间我也认真阅读了quartz的部分源码,感觉涉及到的东西还是很多的。在写作的过程中对很多东西也进行了回顾和思考,也是对知识进行梳理的一个过程。

猜你喜欢

转载自blog.csdn.net/weixin_37745972/article/details/84346738