定时任务:springboot集成Quartz实现多任务多触发的动态管理

本文主要讲解以下几个方面:

1.定时任务的定义及其常见的模式

2.springboot集成quart实例

3.中途会遇到的一些问题

 

一、定时任务的定义及其常见的模式

   1)定时任务的定义

      首先要明白的是,定时任务在系统中的表现形式和在我们口中常说的定时是不一样的:

口中的定时:在明天的八点把设备打开,晚上八点把设备关掉,这个月每天都这样。

系统的定时:上述的定时在系统中就会表现为两个任务:

   2019-05-15到2019-05-31,在8点把设备打开,间隔24小时(隔天)再操作一次;

   2019-05-15到2019-05-31,在20点把设备关闭,间隔24小时(隔天)再操作一次。

也就是说系统的实现需要肢解到单个操作,然后看单个操作的规律来设定任务。(讲这个是因为涉及到后面的Cron表达式,一个时间段的可能需要肢解为多个任务)

   2)常见的几种定时任务方式

一种是while无限循环,隔一定时间执行一次;

一种是Timer,支持一次性调度和循环调度,循环调度有分为固定速率调度(fixRate)和固定时延调度(fixDelay),能支持很多的应用了;它是单线程的,原理如下:

   Timer 类里包含一个任务队列和一个异步轮训线程:

      任务队列里容纳了所有待执行的任务,所有的任务将会在这一个异步线程里执行;

      轮训线程会每次轮训出时间点最近的并且到点的任务来执行

      可参考:https://blog.51cto.com/13732225/2331530

一种是ScheduledExecutorService,这个是用线程池了,可以很灵活的去设定第一次执行任务delay时间等,

    可参考:https://www.cnblogs.com/maoniu602/p/3900587.html

一种就是quartz,基本上具备了上述的各种优点,还实现了任务和触发器的多对多的关系,可以动态的添加删除定时任务,另外很好的支撑集群调度;它的核心如下:

 1个job可对应多个Trigger

 

二、springboot集成quart实例

主要的结构如下:

netak可放到model目录中。

这是我的线程启动类,用来替代SchedulerListener.java,大家没有其他线程也可以用SchedulerListener,自己看着放。

1.QuartzScheduler.java:涉及的是基本的配置,以后的动态修改方法。这个是核心,大家好好看看注解。


import java.util.Date;
import java.util.List;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;


@Configuration
public class QuartzScheduler {

	@Autowired
	private Scheduler scheduler;
	@Autowired
	private NeTaskService neTaskService;

	/**
	 * 开始执行所有任务
	 */
	public void startJob() throws SchedulerException {
		//启动时进行查库将任务添加至job
		
		List<NeTask> netaskList = neTaskService.findAllByNeIdAndInUse();
		List<NeTask> zctaskList = neTaskService.findAllByIpAndInUse();
		
		//添加任务至调度器scheduler
		startJob1(scheduler,netaskList);
		//调度任务开始执行
		//		scheduler.start();
		startJob2(scheduler,zctaskList);
		scheduler.start();
	}

	/*
	 * 重启所有任务
	 */
	public void restartJob() throws SchedulerException, InterruptedException {
		//不可以用shutdown,也不需要停止,直接清除,然后启动
		//		scheduler.shutdown();
		//		scheduler.pauseAll();
		scheduler.clear();
		this.startJob();
	}

	/**
	 * title:
	 * mentality:
	 * @throws 
	 * @param scheduler2
	 * @param zctaskList
	 */
	private void startJob2(Scheduler scheduler2, List<NeTask> zctaskList) throws SchedulerException{
		// TODO Auto-generated method stub
		

	}

	/**
	 * title:计划任务1
	 * mentality:
	 * @throws 
	 * @param scheduler2
	 * @param netaskList
	 */
	private void startJob1(Scheduler scheduler2, List<NeTask> netaskList) throws SchedulerException{
		// TODO Auto-generated method stub
		// 通过JobBuilder构建JobDetail实例,JobDetail规定只能是实现Job接口的实例
		// JobDetail 是具体Job实例
		for(NeTask netask : netaskList){

			JobDetail jobDetail = JobBuilder.newJob(NeTaskJob.class)//不同的业务,增加不同的.class
					.withIdentity(netask.getId().toString(), netask.getStationId()+netask.getNeId())
					.build();
			jobDetail.getJobDataMap().put("id",netask);
			List<Cron> cronList = CronUtil.getCronByTask(netask);
//			for(Cron cron : cronList ){
			for(int i = 0;i< cronList.size();i++){
				// 基于表达式构建触发器
				CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(
						//下面的cron你可以直接写个cron表达式来做验证,入:每隔5秒执行一次:*/5 * * * * ?
						cronList.get(i).getCron()
						);

				// CronTrigger表达式触发器 继承于Trigger
				// TriggerBuilder 用于构建触发器实例
				CronTrigger cronTrigger = TriggerBuilder.newTrigger()
						//若一个jobdetail有多个trigger,则需要注意命名规则,便于后面修改任务
//						.withIdentity(netask.getNeId().toString(), netask.getStationId())
						.forJob(jobDetail)
						.withIdentity(netask.getId() + CronUtil.cronEndId[i], netask.getStationId()+netask.getNeId())
						.withSchedule(cronScheduleBuilder).build();

				// scheduleJob该接口的作用是在将任务加入Quartz的同时绑定一个Trigger,Quartz会在加入该任务后自动设置Trigger的JobName与JobGroup为该JobDetail的name与group
				if(i==0){
					scheduler2.scheduleJob(jobDetail, cronTrigger);//第一次必须有jobdetail
				}else{
					scheduler2.scheduleJob(cronTrigger);
				}
				
				
				//rescheduleJob(String, String, Trigger)  替换一个指定的Trigger, 即解除指定Trigger与任务的绑定,并将新的Trigger与任务绑定,Quartz会自动调整新Trigger的JobName与JobGroup,而旧的Trigger将被移除
				//Scheduler#triggerJob(String, String)   创建一个立即触发的Trigger,并将其与name与group指定的任务绑定
			}

		}

	}


	/**
	 * 获取Job信息
	 *
	 * @param name
	 * @param group
	 * @return
	 * @throws SchedulerException
	 */
	public String getJobInfo(String name, String group) throws SchedulerException {
		TriggerKey triggerKey = new TriggerKey(name, group);
		CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
		return String.format("time:%s,state:%s", cronTrigger.getCronExpression(),
				scheduler.getTriggerState(triggerKey).name());
	}

	/**
	 * 修改某个任务的执行时间
	 * (修改的是具体的trigger,不是jobdetail)
	 * @param name
	 * @param group
	 * @param time
	 * @return
	 * @throws SchedulerException
	 */
	public boolean modifyJob(String name, String group, String time) throws SchedulerException {
		Date date = null;
		TriggerKey triggerKey = new TriggerKey(name, group);
		CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
		String oldTime = cronTrigger.getCronExpression();
		if (!oldTime.equalsIgnoreCase(time)) {
			CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(time);
			CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group)
					.withSchedule(cronScheduleBuilder).build();
			date = scheduler.rescheduleJob(triggerKey, trigger);
		}
		return date != null;
	}

	/**
	 * 暂停所有任务
	 *
	 * @throws SchedulerException
	 */
	public void pauseAllJob() throws SchedulerException {
		scheduler.pauseAll();
	}

	/**
	 * 暂停某个任务
	 *
	 * @param name
	 * @param group
	 * @throws SchedulerException
	 */
	public void pauseJob(String name, String group) throws SchedulerException {
		JobKey jobKey = new JobKey(name, group);
		JobDetail jobDetail = scheduler.getJobDetail(jobKey);
		if (jobDetail == null){
			return;
		}

		scheduler.pauseJob(jobKey);
	}

	/**
	 * 恢复所有任务
	 *
	 * @throws SchedulerException
	 */
	public void resumeAllJob() throws SchedulerException {
		scheduler.resumeAll();
	}

	/**
	 * 恢复某个任务
	 *
	 * @param name
	 * @param group
	 * @throws SchedulerException
	 */
	public void resumeJob(String name, String group) throws SchedulerException {
		JobKey jobKey = new JobKey(name, group);
		JobDetail jobDetail = scheduler.getJobDetail(jobKey);
		if (jobDetail == null){
			return;
		}
		scheduler.resumeJob(jobKey);
	}

	/**
	 * 删除某个任务
	 *
	 * @param name
	 * @param group
	 * @throws SchedulerException
	 */
	public void deleteJob(String name, String group) throws SchedulerException {
		JobKey jobKey = new JobKey(name, group);
		JobDetail jobDetail = scheduler.getJobDetail(jobKey);
		if (jobDetail == null){
			return;
		}
		scheduler.deleteJob(jobKey);
	}
}

2.NeTaskJob.java:任务类,这里的关键是@PostConstruct,这是我们在实际项目中要调用其他接口的时候要用到的。

ZcTaskJob.java就是另一个任务,格式都一样,就里调用的接口不一样,这里就不写了。


import javax.annotation.PostConstruct;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
public class NeTaskJob implements Job{
	
	@Autowired
    private NeThreadService neThreadService;
	
	public static NeTaskJob neTaskJob;
	public NeTaskJob(){}
//注入要调用的方法
	@PostConstruct
	public void init(){
		neTaskJob = this;
		neTaskJob.neThreadService = this.neThreadService;
	}

//	private void before(){
//        System.out.println("任务开始执行");
//    }
	/* (non-Javadoc)
	 * @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
	 */
	@Override
	public void execute(JobExecutionContext JobExecutionContext) throws JobExecutionException {
		// TODO Auto-generated method stub
//		before();
        System.out.println("任务开始:"+System.currentTimeMillis());
        // TODO 业务:此处是关键,如何从map中取得对应的job名称(netask的唯一标识id)
        NeTask netask = (NeTask)JobExecutionContext.getMergedJobDataMap().get("id");
        //实际项目中经常要调用到其他的接口方法,那么一定要在上方注入PostConstruct
        neTaskJob.neThreadService.sendCmd(netask.getPeriodValue());
        
        System.out.println("任务结束:"+ netask.getTitle() +System.currentTimeMillis());
//        after();
	}

//	private void after(){
//        System.out.println("任务开始执行");
//    }
	
}

3.service就是大家自己从数据库取任务,更新任务的方法,就不写了。

4.listener

@Component
public class AppListener implements ApplicationListener<ApplicationEvent>{

	private static boolean loaded = false;

	@Autowired
    private QuartzScheduler quartzScheduler;
	
	@Override
	public void onApplicationEvent(ApplicationEvent e){
		if(e instanceof ContextRefreshedEvent){
			if(!loaded){//避免多次执行
				loaded = true;
				//定时任务启动
				try {
		            quartzScheduler.startJob();
		            System.out.println("任务已经启动...");
		        } catch (SchedulerException se) {
		            se.printStackTrace();
		        }
			}
			
			if(e instanceof ContextStartedEvent){
				
			}
			
		}
	}
	
	/**
     * 初始注入scheduler
     * @return
     * @throws SchedulerException
     */
    @Bean
    public Scheduler scheduler() throws SchedulerException{
        SchedulerFactory schedulerFactoryBean = new StdSchedulerFactory();
        return schedulerFactoryBean.getScheduler();
    }

}

三、中途会遇到的一些问题

1.The job (0000000035.2) referenced by the trigger does not exist.

2.Jobs added with no trigger must be durable.

    这些问题都是job配置多个触发器时需要注意的问题:

第一次scheduler2.scheduleJob(jobDetail, cronTrigger);必须有jobDetail第二个trigger不能有jobDetail,scheduler2.scheduleJob(cronTrigger);,同时注意在newTrigger的时候必须.forJob(jobDetail)指明触发器是给哪个job用的,范例见QuartzScheduler.java;

 

此致,关于配置的问题就说这么多。关于实际应用中集成cron的问题,我们下篇再说。

 

在做的过程中筛选出来的几个对我参考价值较高的链接如下:

quartz:

https://www.cnblogs.com/qlqwjy/p/8721982.html

https://blog.csdn.net/weixin_40692498/article/details/88025275

https://blog.csdn.net/upxiaofeng/article/details/79415108#commentBox

https://blog.csdn.net/qq_28483283/article/details/80623417#commentBox

https://blog.csdn.net/u013042707/article/details/82934725#commentBox

https://blog.csdn.net/liuchuanhong1/article/details/60873295

https://blog.csdn.net/qq_29145405/article/details/81843123

cron:

https://www.cnblogs.com/a8457013/p/8515939.html

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值