SpringBoot多任務Quartz動態管理Scheduler(只要一張表,動態啓停任務)

前言:由於公司最近在開發新架構,需要對任務調度進行統一管理,舊的架構中採用的 是spring內置的定時,存在一定的問題,而且每次修改定時之後需要重新啓動應用,不是很好使,也不夠強大,於是引入了quartz進行管理。具體處理如下

 

一、quartz簡單介紹

1. 介紹 
Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,是完全由java開發的一個開源的任務日程管理系統,“任務進度管理器”就是一個在預先確定(被納入日程)的時間到達時,負責執行(或者通知)其他軟件組件的系統。 
Quartz用一個小Java庫發佈文件(.jar文件),這個庫文件包含了所有Quartz核心功能。這些功能的主要接口(API)是Scheduler接口。它提供了簡單的操作,例如:將任務納入日程或者從日程中取消,開始/停止/暫停日程進度。 
2. 定時器種類 
Quartz 中五種類型的 Trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,NthIncludedDayTrigger和Calendar 類( org.quartz.Calendar)。 
最常用的: 
SimpleTrigger:用來觸發只需執行一次或者在給定時間觸發並且重複N次且每次執行延遲一定時間的任務。 
CronTrigger:按照日曆觸發,例如“每個週五”,每個月10日中午或者10:15分。 
3. 存儲方式 
RAMJobStore和JDBCJobStore 
對比:

類型    優點    缺點
RAMJobStore    不要外部數據庫,配置容易,運行速度快    因爲調度程序信息是存儲在被分配給JVM的內存裏面,所以,當應用程序停止運行時,所有調度信息將被丟失。另外因爲存儲到JVM內存裏面,所以可以存儲多少個Job和Trigger將會受到限制
JDBCJobStore    支持集羣,因爲所有的任務信息都會保存到數據庫中,可以控制事物,還有就是如果應用服務器關閉或者重啓,任務信息都不會丟失,並且可以恢復因服務器關閉或者重啓而導致執行失敗的任務    運行速度的快慢取決與連接數據庫的快慢
4. 表關係和解釋

表關係

解釋

表名稱    說明
qrtz_blob_triggers    Trigger作爲Blob類型存儲(用於Quartz用戶用JDBC創建他們自己定製的Trigger類型,JobStore 並不知道如何存儲實例的時候)
qrtz_calendars    以Blob類型存儲Quartz的Calendar日曆信息, quartz可配置一個日曆來指定一個時間範圍
qrtz_cron_triggers    存儲Cron Trigger,包括Cron表達式和時區信息。
qrtz_fired_triggers    存儲與已觸發的Trigger相關的狀態信息,以及相聯Job的執行信息
qrtz_job_details    存儲每一個已配置的Job的詳細信息
qrtz_locks    存儲程序的非觀鎖的信息(假如使用了悲觀鎖)
qrtz_paused_trigger_graps    存儲已暫停的Trigger組的信息
qrtz_scheduler_state    存儲少量的有關 Scheduler的狀態信息,和別的 Scheduler 實例(假如是用於一個集羣中)
qrtz_simple_triggers    存儲簡單的 Trigger,包括重複次數,間隔,以及已觸的次數
qrtz_triggers    存儲已配置的 Trigger的信息
qrzt_simprop_triggers    
5. 核心類和關係

核心類 
(1)核心類 
QuartzSchedulerThread :負責執行向QuartzScheduler註冊的觸發Trigger的工作的線程。 
ThreadPool:Scheduler使用一個線程池作爲任務運行的基礎設施,任務通過共享線程池中的線程提供運行效率。 
QuartzSchedulerResources:包含創建QuartzScheduler實例所需的所有資源(JobStore,ThreadPool等)。 
SchedulerFactory :提供用於獲取調度程序實例的客戶端可用句柄的機制。 
JobStore: 通過類實現的接口,這些類要爲org.quartz.core.QuartzScheduler的使用提供一個org.quartz.Job和org.quartz.Trigger存儲機制。作業和觸發器的存儲應該以其名稱和組的組合爲唯一性。 
QuartzScheduler :這是Quartz的核心,它是org.quartz.Scheduler接口的間接實現,包含調度org.quartz.Jobs,註冊org.quartz.JobListener實例等的方法。 
Scheduler :這是Quartz Scheduler的主要接口,代表一個獨立運行容器。調度程序維護JobDetails和觸發器的註冊表。 一旦註冊,調度程序負責執行作業,當他們的相關聯的觸發器觸發(當他們的預定時間到達時)。 
Trigger :具有所有觸發器通用屬性的基本接口,描述了job執行的時間出發規則。 - 使用TriggerBuilder實例化實際觸發器。 
JobDetail :傳遞給定作業實例的詳細信息屬性。 JobDetails將使用JobBuilder創建/定義。 
Job:要由表示要執行的“作業”的類實現的接口。只有一個方法 void execute(jobExecutionContext context) 
(jobExecutionContext 提供調度上下文各種信息,運行時數據保存在jobDataMap中) 
Job有個子接口StatefulJob ,代表有狀態任務。 
有狀態任務不可併發,前次任務沒有執行完,後面任務處於阻塞等到。
注:在網上查看了各種貼子、文章,結合本身的業務,發現如此多的表對我們確實用處不大,然後產生一個想法,是否不需要這麼多表的情況下也能滿足我現有的功能需求呢?於是,做了如下的配置開發.......

二、引入quartz開發

1、建表

CREATE TABLE `base_quartz_config` (
  `configid` varchar(64) COLLATE utf8_bin NOT NULL,
  `quarta_name` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '任務名稱',
  `quarta_group` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '任務組',
  `status` int(11) DEFAULT NULL COMMENT '狀態',
  `quartz_class` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '類全名',
  `remark` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '備註',
  `createuserid` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '創建人',
  `expression` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '表達式',
  `createtime` datetime DEFAULT NULL,
  PRIMARY KEY (`configid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

2、引入jar包

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-quartz</artifactId>
		</dependency>

整體目錄結構如下

3、具體代碼

創建調度工廠

package com.app.base.modules.common.quartzutil;

import java.util.List;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import com.app.base.modules.common.entity.BaseQuartzConfigEntity;
import com.app.base.modules.common.service.BaseQuartzConfigService;
import com.app.tool.core.util.StringUtil;

/**
 * 調度工廠類
 */
@Service("MySchedulerFactory")
@Component
public class MySchedulerFactory {
	private static Logger logger = LoggerFactory.getLogger(MySchedulerFactory.class);
	@Autowired
	SchedulerFactoryBean schedulerFactoryBean;

	/**
	 * 任務配置讀取服務
	 */
	@Autowired
	private BaseQuartzConfigService baseQuartzConfigService;

	public void scheduleJobs() throws SchedulerException {
		Scheduler scheduler = getScheduler();
		// 爲了避免org.quartz.ObjectAlreadyExistsException,在執行前將scheduler進行清理
		scheduler.clear();
		startJob(scheduler);
	}

	/**
	 * 獲取scheduler
	 * @return
	 * @author pangxianhe
	 * @date 2018年12月27日
	 */
	private Scheduler getScheduler() {
		return schedulerFactoryBean.getScheduler();
	}

	/**
	 * 項目啓動 開啓任務
	 * @param scheduler
	 * @author pangxianhe
	 * @date 2018年12月27日
	 */
	private void startJob(Scheduler scheduler) {
		try {
			List<BaseQuartzConfigEntity> jobList = baseQuartzConfigService.findAll();
			for (BaseQuartzConfigEntity config : jobList) {
				try {
					// 1-暫停的任務 0-正常運行任務
					if (1l == config.getStatus()) {
						continue;
					}
					String classz = config.getQuartzClass();
					if (StringUtil.isNotBlank(classz)) {
						@SuppressWarnings("unchecked")
						Class<? extends Job> clazz = (Class<? extends Job>) Class.forName(classz);
						JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(config.getConfigid(), config.getQuartaGroup())
								.build();
						CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(config.getExpression());
						CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(config.getConfigid(), config.getQuartaGroup())
								.withSchedule(scheduleBuilder).build();
						scheduler.scheduleJob(jobDetail, cronTrigger);
					}
				} catch (Exception e) {
					logger.info("定時啓動異常:" + config.getQuartaName() + " 異常信息:" + e.getMessage());
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 任務暫停
	 * @param id
	 * @throws Exception
	 * @author pangxianhe
	 * @date 2018年12月27日
	 */
	public void pauseJob(String id) throws Exception {
		Scheduler scheduler = getScheduler();
		BaseQuartzConfigEntity QuartzConfig = baseQuartzConfigService.get(String.valueOf(id));
		JobKey jobKey = JobKey.jobKey(QuartzConfig.getConfigid(), QuartzConfig.getQuartaGroup());
		scheduler.deleteJob(jobKey);
	}

	/**
	 * 任務恢復
	 * @param id
	 * @throws Exception
	 * @author pangxianhe
	 * @date 2018年12月27日
	 */
	public void resumeJob(String id) throws Exception {
		Scheduler scheduler = getScheduler();
		BaseQuartzConfigEntity QuartzConfig = baseQuartzConfigService.get(String.valueOf(id));
		JobKey jobKey = JobKey.jobKey(QuartzConfig.getConfigid(), QuartzConfig.getQuartaGroup());
		Class<? extends Job> clazz = (Class<? extends Job>) Class.forName(QuartzConfig.getQuartzClass());
		JobDetail jobDetail1 = scheduler.getJobDetail(jobKey);
		if (jobDetail1 == null) {
			JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(QuartzConfig.getConfigid(), QuartzConfig.getQuartaGroup())
					.build();
			CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(QuartzConfig.getExpression());
			CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(QuartzConfig.getConfigid(), QuartzConfig.getQuartaGroup())
					.withSchedule(scheduleBuilder).build();
			scheduler.scheduleJob(jobDetail, cronTrigger);
		} else {
			scheduler.resumeJob(jobKey);
		}
	}
}

創建任務工廠

package com.app.base.modules.common.quartzutil;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component
public class MyJobFactory extends AdaptableJobFactory {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 調用父類的方法
        Object jobInstance = super.createJobInstance(bundle);
        // 進行注入
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}

向spring容器注入調度任務配置信息

package com.app.base.modules.common.quartzutil;

import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

/**
 * 定時任務運行工廠類
 */
@Configuration
public class StartSchedulerListener implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    public MySchedulerFactory mySchedulerFactory;
    @Autowired
    private MyJobFactory myJobFactory;
    // springboot 啓動監聽
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        try {
            mySchedulerFactory.scheduleJobs();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    //注入SchedulerFactoryBean
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setJobFactory(myJobFactory);
        return schedulerFactoryBean;
    }

}

監聽

package com.app.base.modules.common.quartzutil;

import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

/**
 * 定時任務運行工廠類
 */
@Configuration
public class StartSchedulerListener implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    public MySchedulerFactory mySchedulerFactory;
    @Autowired
    private MyJobFactory myJobFactory;
   /**
    * springboot 啓動監聽
    */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        try {
            mySchedulerFactory.scheduleJobs();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    //注入SchedulerFactoryBean
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setJobFactory(myJobFactory);
        return schedulerFactoryBean;
    }

}

測試類

package com.app.base.modules.common.quartzutil;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;

@Configuration
@Component
@EnableScheduling
public class ScheduleTask1 implements Job {
 
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        try {
            System.out.print("任務執行1 :");
            System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
 
        } catch (Exception e) {
        	System.out.println(e.getMessage());
        }
        }
}

在線修改定時運行與停止管理

/**
     * 修改定時任務
     * @param data
     * @return
     * @throws Exception 
     * @author pangxianhe
     * @date 2018年12月28日
     */
    @PostMapping("/changestatus")
    @Log(title = "修改定時任務", action = BusinessType.INSERT)
    @ApiOperation(value = "修改定時任務", notes = "修改定時任務")
    public Result changestatus(@RequestBody Map<String, Object> data){
    	
    	try {
			String status = (String) data.get("status");
			String configid = (String) data.get("configid");
			if (StringUtil.isNotBlank(configid)) {
				BaseQuartzConfigEntity quartzConfigEntity = baseQuartzConfigService.get(configid);
				if ("0".equals(status)) {
					//修改爲0,並且恢復運行
					quartzConfigEntity.setStatus(0);
					mySchedulerFactory.resumeJob(configid);
				}else {
					//修改爲1,並且暫停
					quartzConfigEntity.setStatus(1);
					mySchedulerFactory.pauseJob(configid);
				}
				baseQuartzConfigService.update(quartzConfigEntity);
			}
			  return Result.success().put("data","success");
		} catch (Exception e) {
			  return Result.error().put("data","fail");
		}
    }

文件目錄如:

效果如下:

 

 

 

完畢,以上如有疑問請評論溝通,互相學習,謝謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章