1. 程式人生 > >Quartz Job & Spring 動態任務排程

Quartz Job & Spring 動態任務排程

Quartz Job & Spring

在實際專案應用中經常會用到定時任務,可通過Quartz框架輕鬆完成。在Web專案中,如果用Spring框架管理Quartz,在Web容器啟動或關閉時自動啟動、關閉Quartz中的任務,非常方便。

傳統的MethodInvokingJobDetailFactoryBean執行方式,配置複雜,且不夠靈活——如果要動態改變任務的狀態、cron表示式等就需要改變配置甚至程式碼需要重啟伺服器了。因此,我採取動態任務排程的方式,可自由控制任務進度,更可以顯示到html頁面上。

這裡寫圖片描述

配置

只需要在Spring配置檔案中加上SchedulerFactoryBean。

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean" autowire="no"></bean>

這樣,Spring就為我們建立了一個空的Scheduler,我們後面手動新增任務進去。

任務

我們定義一個Job類,任務都在這個Job類上執行

public class QuartzJobFactory implements Job {
    public void execute(JobExecutionContext context) 
            throws
JobExecutionException{ System.out.println("任務執行了"); } }

既然記錄任務狀態,那就需要定義一個類了,我們定義一個ScheduleJob

public class ScheduleJobDomain {
    private String jobId;    //任務id
    private String jobName;  //任務名稱
    /** 任務狀態 */
    private String jobStatus;

    private String quartz;   //cron表示式

    //省略get、set方法
}

再定義一個Service

/**
 * 管理quartz任務的service
 */
public interface ScheduleJobService {

    //獲取所有計劃中的任務
    List<ScheduleJobDomain> getPlanJobs() throws SchedulerException;

    //獲取所有執行中的任務
    List<ScheduleJobDomain> getRunningJobs() throws SchedulerException;

    //暫停任務
    void pauseJob(ScheduleJobDomain scheduleJob) throws SchedulerException;

    //恢復任務
    void resumeJob(ScheduleJobDomain scheduleJob) throws SchedulerException;

    //刪除任務
    void deleteJob(ScheduleJobDomain scheduleJob) throws SchedulerException;

    //立即執行任務 ,只會執行一次
    void runOnce(ScheduleJobDomain scheduleJob) throws SchedulerException;

    //更新任務的時間表達式
    void updateExpression(ScheduleJobDomain job,String expression) throws SchedulerException;

    //新增任務
    void addJob(ScheduleJobDomain scheduleJob) throws SchedulerException;
}

Service的實現類如下

@Service
public class ScheduleJobServiceImpl implements ScheduleJobService {

    @Autowired
    private Scheduler scheduler;

    @Override
    public List<ScheduleJobDomain> getPlanJobs() throws SchedulerException {
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        List<ScheduleJobDomain> jobList = new ArrayList<ScheduleJobDomain>();
        for(JobKey jobKey  : jobKeys){
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
            for (Trigger trigger : triggers){
                ScheduleJobDomain job = (ScheduleJobDomain) trigger.getJobDataMap().get("scheduleJob");
                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                job.setJobStatus(triggerState.name());
                if (trigger instanceof CronTrigger) {
                    CronTrigger cronTrigger = (CronTrigger) trigger;
                    String cronExpression = cronTrigger.getCronExpression();
                    job.setQuartz(cronExpression);
                }
                jobList.add(job);
            }
        }
        return jobList;
    }

    @Override
    public List<ScheduleJobDomain> getRunningJobs() throws SchedulerException {
        List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
        List<ScheduleJobDomain> jobList = new ArrayList<ScheduleJobDomain>();
        for (JobExecutionContext executingJob : executingJobs){
            Trigger trigger = executingJob.getTrigger();
            ScheduleJobDomain job = (ScheduleJobDomain) trigger.getJobDataMap().get("scheduleJob");
            Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
            job.setJobStatus(triggerState.name());
            if (trigger instanceof CronTrigger) {
                CronTrigger cronTrigger = (CronTrigger) trigger;
                String cronExpression = cronTrigger.getCronExpression();
                job.setQuartz(cronExpression);
            }
            jobList.add(job);
        }
        return jobList;
    }

    @Override
    public void pauseJob(ScheduleJobDomain scheduleJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.pauseJob(jobKey);
    }

    @Override
    public void resumeJob(ScheduleJobDomain scheduleJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.resumeJob(jobKey);
    }

    @Override
    public void deleteJob(ScheduleJobDomain scheduleJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.deleteJob(jobKey);
    }

    @Override
    public void runOnce(ScheduleJobDomain scheduleJob) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.triggerJob(jobKey);
    }

    @Override
    public void updateExpression(ScheduleJobDomain scheduleJob, String expression) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(),
                scheduleJob.getJobGroup());
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(expression);
        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
                .withSchedule(scheduleBuilder).build();
        scheduler.rescheduleJob(triggerKey, trigger);
    }

    @Override
    public void addJob(ScheduleJobDomain scheduleJob) throws SchedulerException {
        TriggerKey key = TriggerKey.triggerKey(scheduleJob.getJobName(),scheduleJob.getJobGroup());
        Trigger trigger = scheduler.getTrigger(key);
        if(trigger == null){
            //在建立任務時如果不存在新建一個
            JobDetail jobDetail = JobBuilder.newJob(QuartzJobFactory.class)
                    .withIdentity(scheduleJob.getJobName(),scheduleJob.getJobGroup()).build();
            jobDetail.getJobDataMap().put("scheduleJob",scheduleJob);


            //表示式排程構建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getQuartz());
            //按新的cronExpression表示式構建一個新的trigger
            trigger = TriggerBuilder.newTrigger().withIdentity(scheduleJob.getJobName(),scheduleJob.getJobGroup())
                    .withSchedule(scheduleBuilder).build();
            trigger.getJobDataMap().put("scheduleJob",scheduleJob);
            scheduler.scheduleJob(jobDetail,trigger);
        }else{
            // Trigger已存在,那麼更新相應的定時設定
            //表示式排程構建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getQuartz());
            trigger = TriggerBuilder.newTrigger().withIdentity(key).withSchedule(scheduleBuilder).build();
            trigger.getJobDataMap().put("scheduleJob",scheduleJob);
            //重新執行
            scheduler.rescheduleJob(key,trigger);
        }
    }

這樣,我們在頁面Controller中,就可以直接使用BankJobService來管理任務的狀態了。

在Job中如何得到Spring的Bean

由於Job物件是Quartz建立的,它沒有被註冊到Spring容器中,因此無法直接通過@Autowired得到Spring的Bean物件,解決方式是從BankJobServiceImpl中拿到需要的SpringBean,然後在addJob時放到JobDetail裡面去。

比如拿到Mybatis的SqlSessionTemplate。

public class BankJobServiceImpl{

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;
}

在addJob方法裡面, 修改

JobDetail jobDetail = JobBuilder.newJob(QuartzJobFactory.class)
                                 .withIdentity(scheduleJob.getJobName(),scheduleJob.getJobGroup()).build();
jobDetail.getJobDataMap().put("sqlSessionTemplate",sqlSessionTemplate);

然後在QuartzJobFactory中

public void execute(JobExecutionContext context) throws JobExecutionException{
    SqlSessionTemplate sqlSessionTemplate = (SqlSessionTemplate) context.getMergedJobDataMap().get("sqlSessionTemplate");
}