Springboot整合Quartz實現動態定時任務

簡介

Quartz是一款功能強大的任務調度器,能夠實現較爲複雜的調度功能,如每個月一號執行、天天凌晨執行、每週五執行等等,還支持分佈式調度。本文使用Springboot+Mybatis+Quartz實現對定時任務的增、刪、改、查、啓用、停用等功能。並把定時任務持久化到數據庫以及支持集羣。對於如何建立Springboot項目和與Mybatis整合能夠參考上篇文章:[spring-boot整合Mybatis如何部署在weblogic上
][1]。java

Quartz的3個基本要素

  1. Scheduler:調度器。全部的調度都是由它控制。
  2. Trigger: 觸發器。決定何時來執行任務。
  3. JobDetail & Job: JobDetail定義的是任務數據,而真正的執行邏輯是在Job中。使用JobDetail + Job而不是Job,這是由於任務是有可能併發執行,若是Scheduler直接使用Job,就會存在對同一個Job實例併發訪問的問題。而JobDetail & Job 方式,sheduler每次執行,都會根據JobDetail建立一個新的Job實例,這樣就能夠規避併發訪問的問題。

如何使用Quartz

  1. 添加依賴
<dependency>  
    <groupId>org.quartz-scheduler</groupId>  
    <artifactId>quartz</artifactId>  
    <version>2.2.3</version>  
</dependency> 
<dependency>  
    <groupId>org.quartz-scheduler</groupId>  
    <artifactId>quartz-jobs</artifactId>  
    <version>2.2.3</version>  
</dependency>

2.建立配置文件
在maven項目的resource目錄下建立quartz.propertiesweb

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

#線程池配置
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

#持久化配置
org.quartz.jobStore.misfireThreshold = 50000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#支持集羣
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.useProperties:true
org.quartz.jobStore.clusterCheckinInterval = 15000
#使用weblogic鏈接Oracle驅動
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate
#org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = qzDS
#數據源鏈接信息,quartz默認使用c3p0數據源能夠被自定義數據源覆蓋
org.quartz.dataSource.qzDS.driver = oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.qzDS.URL = jdbc:oracle:thin:@localhost:1521/XE
org.quartz.dataSource.qzDS.user = root
org.quartz.dataSource.qzDS.password = 123456
org.quartz.dataSource.qzDS.maxConnections = 10

說明:在使用quartz作持久化的時候須要用到quartz的11張表,能夠去quartz官網下載對應版本的quartz,解壓打開docs/dbTables裏面有對應數據庫的建表語句。關於quartz.properties配置的詳細解釋能夠查看quartz官網。另外新建一張表TB_APP_QUARTZ用於存放定時任務基本信息和描述等信息,定時任務的增、刪、改、執行等功能與此表沒有任何關係。
quartz的11張表:
圖片描述spring

//TB_APP_QUARTZ表的實體類
public class AppQuartz {
    private Integer quartzId;  //id  主鍵
    private String jobName;  //任務名稱
    private String jobGroup;  //任務分組
    private String startTime;  //任務開始時間
    private String cronExpression;  //corn表達式
    private String invokeParam;//須要傳遞的參數
    ...省略set get
}

Durid數據源配置sql

application.properties配置文件數據庫

server.port=8889
logging.level.=INFO
server.tomcat.uri-encoding=UTF-8
server.connection-timeout=5000
spring.resources.static-locations=classpath:static/,file:static/

mybatis.configuration.mapUnderscoreToCamelCase=true
mybatis.type-aliases-package=com.qz.bean
#-----------------------durid------------------------------------
spring.dataSource.primaryDataSource.type=com.alibaba.druid.pool.DruidDataSource
spring.dataSource.primaryDataSource.url=jdbc:oracle:thin:@10.2.14.238:1521/testdb
spring.dataSource.primaryDataSource.username=dev_monitor
spring.dataSource.primaryDataSource.password=welcome1
spring.dataSource.primaryDataSource.driverClassName = oracle.jdbc.driver.OracleDriver
spring.dataSource.primaryDataSource.initialSize = 5
spring.dataSource.primaryDataSource.minIdle = 5
spring.dataSource.primaryDataSource.maxActive = 15
spring.dataSource.primaryDataSource.maxWait = 60000
spring.dataSource.primaryDataSource.timeBetweenEvictionRunsMillis = 60000
spring.dataSource.primaryDataSource.minEvictableIdleTimeMillis = 300000
spring.dataSource.primaryDataSource.validationQuery = SELECT 1 FROM DUAL
spring.dataSource.primaryDataSource.testWhileIdle = true
spring.dataSource.primaryDataSource.testOnBorrow = true
spring.dataSource.primaryDataSource.testOnReturn = true
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "spring.dataSource.primaryDataSource")
public class DuridBean {
         private String type;

        private String url;

        private String username;

        private String password;

        private String driverClassName;

        private Integer initialSize;

        private Integer minIdle;

        private Integer maxActive;

        private Integer maxWait;

        private Integer timeBetweenEvictionRunsMillis;

        private Integer minEvictableIdleTimeMillis;

        private String validationQuery;

        private Boolean testWhileIdle;

        private Boolean testOnBorrow;

        private Boolean testOnReturn;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.alibaba.druid.pool.DruidDataSource;

@Configuration
public class DuridDatasource {
     @Autowired
     private DuridBean druidPrimaryDataSourceConfigProperties;

    @Bean(name="dataSource")
    @Primary
    public DataSource primaryDataSource (){
        DruidDataSource datasource = new DruidDataSource();

        datasource.setUrl(this.druidPrimaryDataSourceConfigProperties.getUrl());
        datasource.setUsername(this.druidPrimaryDataSourceConfigProperties.getUsername());
        datasource.setPassword(this.druidPrimaryDataSourceConfigProperties.getPassword());
        datasource.setDriverClassName(this.druidPrimaryDataSourceConfigProperties.getDriverClassName());


        datasource.setInitialSize(this.druidPrimaryDataSourceConfigProperties.getInitialSize());
        datasource.setMinIdle(this.druidPrimaryDataSourceConfigProperties.getMinIdle());
        datasource.setMaxActive(this.druidPrimaryDataSourceConfigProperties.getMaxActive());
        datasource.setMaxWait(this.druidPrimaryDataSourceConfigProperties.getMaxWait());
        datasource.setTimeBetweenEvictionRunsMillis(this.druidPrimaryDataSourceConfigProperties.getTimeBetweenEvictionRunsMillis());
        datasource.setMinEvictableIdleTimeMillis(this.druidPrimaryDataSourceConfigProperties.getMinEvictableIdleTimeMillis());
        datasource.setValidationQuery(this.druidPrimaryDataSourceConfigProperties.getValidationQuery());
        datasource.setTestWhileIdle(this.druidPrimaryDataSourceConfigProperties.getTestWhileIdle());
        datasource.setTestOnBorrow(this.druidPrimaryDataSourceConfigProperties.getTestOnBorrow());
        datasource.setTestOnReturn(this.druidPrimaryDataSourceConfigProperties.getTestOnReturn());

        return datasource;
    }

}

3.Quartz配置express

/**
 * 建立job 實例工廠,解決spring注入問題,若是使用默認會致使spring的@Autowired 沒法注入問題
 * @author LLQ
 *
 */
@Component
public class JobFactory extends AdaptableJobFactory{
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;
    
     @Override
        protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
            //調用父類的方法
            Object jobInstance = super.createJobInstance(bundle);
            //進行注入
            capableBeanFactory.autowireBean(jobInstance);
            return jobInstance;
        }

}
@Configuration
public class SchedulerConfig implements ApplicationListener<ContextRefreshedEvent>{    
    @Autowired
    private JobFactory jobFactory;
    @Autowired
    @Qualifier("dataSource")
    private DataSource primaryDataSource;
    
    @Override
     public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("任務已經啓動..."+event.getSource());
    }
    
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {        
       //獲取配置屬性
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        //在quartz.properties中的屬性被讀取並注入後再初始化對象
        propertiesFactoryBean.afterPropertiesSet();
        //建立SchedulerFactoryBean
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setQuartzProperties(propertiesFactoryBean.getObject());
        //使用數據源,自定義數據源
        factory.setDataSource(this.primaryDataSource);
        factory.setJobFactory(jobFactory);
        factory.setWaitForJobsToCompleteOnShutdown(true);//這樣當spring關閉時,會等待全部已經啓動的quartz job結束後spring才能徹底shutdown。
        factory.setOverwriteExistingJobs(false); 
        factory.setStartupDelay(1);  
        return factory;
    }
    
    
    /*
     * 經過SchedulerFactoryBean獲取Scheduler的實例
     */
    @Bean(name="scheduler")
    public Scheduler scheduler() throws IOException {
        return schedulerFactoryBean().getScheduler();
    }
    
    
    @Bean
    public QuartzInitializerListener executorListener() {
       return new QuartzInitializerListener();
    }
}

4.建立定時任務服務tomcat

@Service
public class JobUtil {
     @Autowired
     @Qualifier("scheduler")
     private Scheduler scheduler;
          
     
     /**
      * 新建一個任務
      * 
      */     
     public String addJob(AppQuartz appQuartz) throws Exception  {
         
             SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             Date date=df.parse(appQuartz.getStartTime());
         
             if (!CronExpression.isValidExpression(appQuartz.getCronExpression())) {           
                return "Illegal cron expression";   //表達式格式不正確
            }                            
            JobDetail jobDetail=null;
            //構建job信息
            if("JobOne".equals(appQuartz.getJobGroup())) {
                 jobDetail = JobBuilder.newJob(JobOne.class).withIdentity(appQuartz.getJobName(), appQuartz.getJobGroup()).build();
            }
            if("JobTwo".equals(appQuartz.getJobGroup())) {
                 jobDetail = JobBuilder.newJob(JobTwo.class).withIdentity(appQuartz.getJobName(), appQuartz.getJobGroup()).build();
            }
                    
            //表達式調度構建器(即任務執行的時間,不當即執行)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(appQuartz.getCronExpression()).withMisfireHandlingInstructionDoNothing();

            //按新的cronExpression表達式構建一個新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(appQuartz.getJobName(), appQuartz.getJobGroup()).startAt(date)
                    .withSchedule(scheduleBuilder).build();
                                 
            //傳遞參數
            if(appQuartz.getInvokeParam()!=null && !"".equals(appQuartz.getInvokeParam())) {
                trigger.getJobDataMap().put("invokeParam",appQuartz.getInvokeParam());    
            }                                
            scheduler.scheduleJob(jobDetail, trigger);
           // pauseJob(appQuartz.getJobName(),appQuartz.getJobGroup());
            return "success";
        } 
         /**
          * 獲取Job狀態
          * @param jobName
          * @param jobGroup
          * @return
          * @throws SchedulerException
          */
         public String getJobState(String jobName, String jobGroup) throws SchedulerException {             
             TriggerKey triggerKey = new TriggerKey(jobName, jobGroup);    
             return scheduler.getTriggerState(triggerKey).name();
           }
         
         //暫停全部任務
         public void pauseAllJob() throws SchedulerException {            
             scheduler.pauseAll();
          }
        
        //暫停任務
        public String pauseJob(String jobName, String jobGroup) throws SchedulerException {            
            JobKey jobKey = new JobKey(jobName, jobGroup);
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (jobDetail == null) {
                 return "fail";
            }else {
                 scheduler.pauseJob(jobKey);
                 return "success";
            }
                                         
        }
        
        //恢復全部任務
        public void resumeAllJob() throws SchedulerException {            
            scheduler.resumeAll();
        }
        
        // 恢復某個任務
        public String resumeJob(String jobName, String jobGroup) throws SchedulerException {
            
            JobKey jobKey = new JobKey(jobName, jobGroup);
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (jobDetail == null) {
                return "fail";
            }else {               
                scheduler.resumeJob(jobKey);
                return "success";
            }
        }
        
        //刪除某個任務
        public String  deleteJob(AppQuartz appQuartz) throws SchedulerException {            
            JobKey jobKey = new JobKey(appQuartz.getJobName(), appQuartz.getJobGroup());
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (jobDetail == null ) {
                 return "jobDetail is null";
            }else if(!scheduler.checkExists(jobKey)) {
                return "jobKey is not exists";
            }else {
                 scheduler.deleteJob(jobKey);
                 return "success";
            }  
           
        }
        
        //修改任務
        public String  modifyJob(AppQuartz appQuartz) throws SchedulerException {            
            if (!CronExpression.isValidExpression(appQuartz.getCronExpression())) {
                return "Illegal cron expression";
            }
            TriggerKey triggerKey = TriggerKey.triggerKey(appQuartz.getJobName(),appQuartz.getJobGroup());            
            JobKey jobKey = new JobKey(appQuartz.getJobName(),appQuartz.getJobGroup());
            if (scheduler.checkExists(jobKey) && scheduler.checkExists(triggerKey)) {
                CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);            
                //表達式調度構建器,不當即執行
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(appQuartz.getCronExpression()).withMisfireHandlingInstructionDoNothing();
                //按新的cronExpression表達式從新構建trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
                    .withSchedule(scheduleBuilder).build();
                //修改參數
                if(!trigger.getJobDataMap().get("invokeParam").equals(appQuartz.getInvokeParam())) {
                    trigger.getJobDataMap().put("invokeParam",appQuartz.getInvokeParam());
                }                
                //按新的trigger從新設置job執行
                scheduler.rescheduleJob(triggerKey, trigger);                                                
                return "success";                    
            }else {
                return "job or trigger not exists";
            }    
            
        }

}
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
@Component
public class JonOne implements Job{
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException{
        JobDataMap data=context.getTrigger().getJobDataMap();
        String invokeParam =(String) data.get("invokeParam");
        //在這裏實現業務邏輯
        }
}
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
@Component
public class JobTwo implements Job{
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException{
        JobDataMap data=context.getTrigger().getJobDataMap();
        String invokeParam =(String) data.get("invokeParam");
        //在這裏實現業務邏輯
        }
}

說明:每一個定時任務都必須有一個分組,名稱和corn表達式,corn表達式也就是定時任務的觸發時間,關於corn表達式格式以及含義能夠參考一些網絡資源。每一個定時任務都有一個入口類在這裏我把類名當成定時任務的分組名稱,例如:只要建立定時任務的分組是JobOne的都會執行JobOne這個任務類裏面的邏輯。若是定時任務須要額外的參數可使用JobDataMap傳遞參數,固然也能夠從數據庫中獲取須要的數據。@PersistJobDataAfterExecution和@DisallowConcurrentExecution註解是不讓某個定時任務併發執行,只有等當前任務完成下一個任務纔會去執行。網絡

5.封裝定時任務接口mybatis

@RestController
public class JobController {
    @Autowired
    private JobUtil jobUtil;    
    @Autowired
    private AppQuartzService appQuartzService;
    
    
    //添加一個job
    @RequestMapping(value="/addJob",method=RequestMethod.POST)
    public ReturnMsg addjob(@RequestBody AppQuartz appQuartz) throws Exception {    
        appQuartzService.insertAppQuartzSer(appQuartz);        
        result=jobUtil.addJob(appQuartz);                                                
    }
    
    //暫停job    
    @RequestMapping(value="/pauseJob",method=RequestMethod.POST)
    public ReturnMsg pausejob(@RequestBody Integer[]quartzIds) throws Exception {    
        AppQuartz appQuartz=null;            
        if(quartzIds.length>0){
            for(Integer quartzId:quartzIds) {
                appQuartz=appQuartzService.selectAppQuartzByIdSer(quartzId).get(0);
                jobUtil.pauseJob(appQuartz.getJobName(), appQuartz.getJobGroup());                        
            }
            return new ReturnMsg("200","success pauseJob");    
        }else {
            return new ReturnMsg("404","fail pauseJob");    
        }                                                                
    }
    
    //恢復job
    @RequestMapping(value="/resumeJob",method=RequestMethod.POST)
    public ReturnMsg resumejob(@RequestBody Integer[]quartzIds) throws Exception {    
        AppQuartz appQuartz=null;
        if(quartzIds.length>0) {
            for(Integer quartzId:quartzIds) {
                appQuartz=appQuartzService.selectAppQuartzByIdSer(quartzId).get(0);
                jobUtil.resumeJob(appQuartz.getJobName(), appQuartz.getJobGroup());                
            }
            return new ReturnMsg("200","success resumeJob");
        }else {
            return new ReturnMsg("404","fail resumeJob");
        }            
    } 
        
    
    //刪除job
    @RequestMapping(value="/deletJob",method=RequestMethod.POST)
    public ReturnMsg deletjob(@RequestBody Integer[]quartzIds) throws Exception {
        AppQuartz appQuartz=null;
        for(Integer quartzId:quartzIds) {
            appQuartz=appQuartzService.selectAppQuartzByIdSer(quartzId).get(0);
            String ret=jobUtil.deleteJob(appQuartz);
            if("success".equals(ret)) {
                appQuartzService.deleteAppQuartzByIdSer(quartzId);
            }
        }
        return new ReturnMsg("200","success deleteJob");    
    }
        
    //修改
    @RequestMapping(value="/updateJob",method=RequestMethod.POST)
    public ReturnMsg  modifyJob(@RequestBody AppQuartz appQuartz) throws Exception {
        String ret= jobUtil.modifyJob(appQuartz);            
        if("success".equals(ret)) {            
            appQuartzService.updateAppQuartzSer(appQuartz);
            return new ReturnMsg("200","success updateJob",ret);
        }else {
            return new ReturnMsg("404","fail updateJob",ret);
        }                
    }
    
    //暫停全部
    @RequestMapping(value="/pauseAll",method=RequestMethod.GET)
    public ReturnMsg pauseAllJob() throws Exception {
        jobUtil.pauseAllJob();
        return new ReturnMsg("200","success pauseAll");
    }
    
    //恢復全部
    @RequestMapping(value="/repauseAll",method=RequestMethod.GET)
    public ReturnMsg repauseAllJob() throws Exception {
        jobUtil.resumeAllJob();
        return new ReturnMsg("200","success repauseAll");
    }    
    
}
相關文章
相關標籤/搜索