Quartz

quartz原理
quartz

Quartz调度核心元素

  • Scheduler:任务调度器,是实际执行任务调度的控制器。在spring中通过SchedulerFactoryBean封装起来。

  • Trigger:触发器,用于定义任务调度的时间规则,有SimpleTrigger,CronTrigger,DateIntervalTrigger和NthIncludedDayTrigger,其中CronTrigger用的比较多,本文主要介绍这种方式。CronTrigger在spring中封装在CronTriggerFactoryBean中。
    Calendar:它是一些日历特定时间点的集合。一个trigger可以包含多个Calendar,以便排除或包含某些时间点。

  • JobDetail:用来描述Job实现类及其它相关的静态信息,如Job名字、关联监听器等信息。在spring中有JobDetailFactoryBean和 MethodInvokingJobDetailFactoryBean两种实现,如果任务调度只需要执行某个类的某个方法,就可以通过MethodInvokingJobDetailFactoryBean来调用。

  • Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。实现Job接口的任务,默认是无状态的,若要将Job设置成有状态的,在quartz中是给实现的Job添加@DisallowConcurrentExecution注解(以前是实现StatefulJob接口,现在已被Deprecated),在与spring结合中可以在spring配置文件的job detail中配置concurrent参数。

Quartz 集群

quartz集群是通过数据库表来感知其他的应用的,各个节点之间并没有直接的通信。只有使用持久的JobStore才能完成Quartz集群。
数据库表:有11张表,表信息如下

image.png

image.png

QRTZ_LOCKS就是Quartz集群实现同步机制的行锁表,包括以下几个锁:CALENDAR_ACCESS 、JOB_ACCESS、MISFIRE_ACCESS 、STATE_ACCESS 、TRIGGER_ACCESS

数据存储

Quartz 中的 trigger 和 job 需要存储下来才能被使用。Quartz 中有两种存储方式:RAMJobStore, JobStoreSupport,其中 RAMJobStore 是将 trigger 和 job 存储在内存中,而 JobStoreSupport 是基于 jdbc 将 trigger 和 job 存储到数据库中。RAMJobStore 的存取速度非常快,但是由于其在系统被停止后所有的数据都会丢失,所以在通常应用中,都是使用 JobStoreSupport。

在 Quartz 中,JobStoreSupport 使用一个驱动代理来操作 trigger 和 job 的数据存储:StdJDBCDelegate。StdJDBCDelegate 实现了大部分基于标准 JDBC 的功能接口,但是对于各种数据库来说,需要根据其具体实现的特点做某些特殊处理,因此各种数据库需要扩展 StdJDBCDelegate 以实现这些特殊处理。

在Hap中使用的就是JobStoreSupport持久化存储,下面简单介绍一下常见几张表对应的关系:

image.png

这个界面的数据,存放在 qrtz_job_details 表中,但是这里还是有一些需要注意的地方。并不是所有的参数都在这个表中,比如 上次执行时间、下次执行时间、corn表达式等信息,实际上是存在别的表中,这个视 任务的类型来判断。当简单任务的时候,存在 qrtz_simple_triggers 表中;当 corn 任务的时候,存在 qrtz_triggers 表中。


image.png

不知道大家有没有想过这个问题,任务中的参数是放在哪里?我一开始以为会放到一张表里,最后发现参数其实是存在 qrtz_job_details 表里的一个字段上的。

image.png

是一个blob类型。

其实Hap在Job这一块封装的东西不算是很多。我们在开发时主要用到的就是一个 AbstractJob 类,之后会介绍到。还有就是任务运行记录,在Hap中是将任务执行记录存在 sys_job_running_info 表中,其实也是在 quratz 的监听器基础上做到的,之后会有介绍。

配置

applicationContext-job.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- ~ #{copyright}# -->

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:property-placeholder location="classpath:config.properties"/>
    <bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- DO NOT AUTO STARTUP DURING LOCAL DEV TEST -->
        <property name="autoStartup" value="${job.autoStartup}" />
        <property name="jobFactory">
            <bean class="com.hand.hap.job.AutowiringSpringBeanJobFactory" />
        </property>
        <property name="applicationContextSchedulerContextKey" value="applicationContext" />
        <property name="configLocation" value="classpath:quartz.properties" />
    </bean>
</beans>

quartz.properties

#============================================================================
# Configure Scheduler
#============================================================================

org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.instanceId = AUTO
#org.quartz.scheduler.rmi.export = false
#org.quartz.scheduler.rmi.proxy = false
#org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
org.quartz.scheduler.skipUpdateCheck = true


#============================================================================
# Configure ThreadPool
#============================================================================
#org.quartz.threadPooln.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
#org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true


#============================================================================
# Configure JobStore
#============================================================================
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.maxMisfiresToHandleAtATime=1

#org.quartz.jobStore.misfireThreshold = 60000
#org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#org.quartz.scheduler.classLoadHelper.class=org.quartz.simpl.CascadingClassLoadHelper
#org.quartz.jobStore.useProperties = true

#============================================================================
# Microsoft SQL Server
#============================================================================
#org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.MSSQLDelegate
#org.quartz.jobStore.selectWithLockSQL=SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?




#============================================================================
# Configure Plugins 
#============================================================================
#org.quartz.plugin.triggHistory.class=org.quartz.plugins.history.LoggingJobHistoryPlugin
org.quartz.plugin.runningListener.class=com.hand.hap.job.plugin.RunningListenerPlugin
org.quartz.plugin.runningListener.LogRunningInfo=true
org.quartz.plugin.runningListener.mailTemplate=email_job_running_notification

quratz默认是使用RAMJobStore存储,这是将信息存储再内存中,这种方式有一个好处就是速度快,单缺点也很明显:不能持久化存储,如果系统出问题 就得从头开始,所以在项目上我没一般是采用持久化存储这种方式。如果需要数据库持久化存储,一般需要在quartz.properties配置文件中将存储方式改成jdbcjobstore,例如:

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

但在Hap中并没有直接在quartz.properties配置文件中定义数据库得连接方式,而是在 applicationContext-job.xml 配置文件中将数据源注入进去了。

    <context:property-placeholder location="classpath:config.properties"/>
    <bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- DO NOT AUTO STARTUP DURING LOCAL DEV TEST -->
        <property name="autoStartup" value="${job.autoStartup}" />
        <property name="jobFactory">
            <bean class="com.hand.hap.job.AutowiringSpringBeanJobFactory" />
        </property>
        <property name="applicationContextSchedulerContextKey" value="applicationContext" />
        <property name="configLocation" value="classpath:quartz.properties" />
    </bean>

因为spring 不能在quartz中注入bean的,所以这里自定义了 一个 jobFactory

package com.hand.hap.job;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

/**
 * 支持 AutoWired.
 * 
 */
public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
    private transient AutowireCapableBeanFactory beanFactory;

    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    public Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}
<property name="applicationContextSchedulerContextKey" value="applicationContext" />

applicationContextSchedulerContextKey 是 org.springframework.scheduling.quartz.SchedulerFactoryBean这个类中把spring上下 文以key/value的方式存放在了quartz的上下文中了,可以用applicationContextSchedulerContextKey所定义的key得到对应的spring上下文。

Listeners

这是Hap中Job模块的关键部分。Listeners是您创建的对象,用于根据调度程序中发生的事件执行操作。

TriggerListeners和JobListeners

您可能猜到,TriggerListeners接收到与触发器(trigger)相关的事件,JobListeners 接收与jobs相关的事件。
与触发相关的事件包括:触发器触发,触发失灵(在本文档的“触发器”部分中讨论),并触发完成(触发器关闭的作业完成)。
org.quartz.TriggerListener接口

public interface TriggerListener {

    public String getName();

    public void triggerFired(Trigger trigger, JobExecutionContext context);

    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);

    public void triggerMisfired(Trigger trigger);

    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            int triggerInstructionCode);
}

job相关事件包括:job即将执行的通知,以及job完成执行时的通知。
org.quartz.JobListener接口

public interface JobListener {

    public String getName();

    public void jobToBeExecuted(JobExecutionContext context);

    public void jobExecutionVetoed(JobExecutionContext context);

    public void jobWasExecuted(JobExecutionContext context,
            JobExecutionException jobException);

}

使用TriggerListeners和JobListeners

要创建一个listener,只需创建一个实现org.quartz.TriggerListener和/或org.quartz.JobListener接口的对象。然后,listener在运行时会向调度程序注册,并且必须给出一个名称(或者,他们必须通过他们的getName()方法来宣传自己的名字)。
为了方便起见,实现这些接口,您的类也可以扩展JobListenerSupport类或TriggerListenerSupport类,并且只需覆盖您感兴趣的事件。
listener与调度程序的ListenerManager一起注册,并配有描述listener希望接收事件的job/触发器的Matcher。
Listener在运行时间内与调度程序一起注册,并且不与jobs和触发器一起存储在JobStore中。这是因为听众通常是与应用程序的集成点。因此,每次运行应用程序时,都需要重新注册该调度程序。

添加对特定job感兴趣的JobListener

scheduler.getListenerManager().addJobListener(myJobListener,KeyMatcher.jobKeyEquals(new JobKey(“myJobName”,“myJobGroup”)));

这将上面的例子变成这样:

scheduler.getListenerManager().addJobListener(myJobListener, jobKeyEquals(jobKey("myJobName", "myJobGroup")));

添加对特定组的所有job感兴趣的JobListener:

scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));

添加对两个特定组的所有job感兴趣的JobListener:

scheduler.getListenerManager().addJobListener(myJobListener, or(jobGroupEquals("myJobGroup"), jobGroupEquals("yourGroup")));

添加对所有job感兴趣的JobListener:

scheduler.getListenerManager().addJobListener(myJobListener, allJobs());

注册TriggerListeners的工作原理相同。

Quartz的大多数用户并不使用Listeners,但是当应用程序需求创建需要事件通知时不需要Job本身就必须明确地通知应用程序,这些用户就很方便。

SchedulerListeners

SchedulerListeners非常类似于TriggerListeners和JobListeners,除了它们在Scheduler本身中接收到事件的通知 (不一定与特定触发器(trigger)或job相关的事件)
与计划程序相关的事件包括:添加job/触发器,删除job/触发器,调度程序中的严重错误,关闭调度程序的通知等。

org.quartz.SchedulerListener接口:

public interface SchedulerListener {

    public void jobScheduled(Trigger trigger);

    public void jobUnscheduled(String triggerName, String triggerGroup);

    public void triggerFinalized(Trigger trigger);

    public void triggersPaused(String triggerName, String triggerGroup);

    public void triggersResumed(String triggerName, String triggerGroup);

    public void jobsPaused(String jobName, String jobGroup);

    public void jobsResumed(String jobName, String jobGroup);

    public void schedulerError(String msg, SchedulerException cause);

    public void schedulerStarted();

    public void schedulerInStandbyMode();

    public void schedulerShutdown();

    public void schedulingDataCleared();
}

SchedulerListeners注册到调度程序的ListenerManager。SchedulerListeners几乎可以实现任何实现org.quartz.SchedulerListener接口的对象。

添加SchedulerListener:

scheduler.getListenerManager().addSchedulerListener(mySchedListener);

删除SchedulerListener:

scheduler.getListenerManager().removeSchedulerListener(mySchedListener);

Hap中的Listener

Hap中得Listener如下:

image.png

主要包括三类:JobListener、TriggerListener、SchedulerListener ;

前面那三个 Default开头得Listener,没有什么实质性得内容,只是分别单纯得实现了 JobListener、TriggerListener、SchedulerListener 这个三个接口,这样接下来得哪几个Listener分别继承Default开头得那几个Listener,就不用是实现JobListener、TriggerListener、SchedulerListener 接口中所有得方法了。这其实就是适配器模式中的 接口适配器模式。至于这几个适配器里面的逻辑,就不过多说明了。可以简单看看 SchedulerRunningListener:

/*
 * #{copyright}#
 */
package com.hand.hap.job.listener;

import com.hand.hap.job.service.IJobRunningInfoService;
import org.quartz.JobKey;
import org.quartz.listeners.SchedulerListenerSupport;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;

import com.hand.hap.job.dto.JobRunningInfoDto;

/**
 * @author shiliyan
 *
 */
public class SchedulerRunningListener extends SchedulerListenerSupport {

    private static final String JOB_INFO_HAS_DELETED = "Job Info [{}.{}] has deleted.";
    private static final String JOB_WAS_DELETED_FROM_SCHEDULER = "Job [{}.{}] was deleted from Scheduler.";
    private final ApplicationContext applicationContext;

    public SchedulerRunningListener(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /*
     * (non-Javadoc)
     * 
     * @see DefaultSchedulerListener#jobDeleted(org.quartz.JobKey)
     */
    @Override
    public void jobDeleted(JobKey jobKey) {
        JobRunningInfoDto dto = new JobRunningInfoDto();
        String group = jobKey.getGroup();
        String name = jobKey.getName();
        logInfo(JOB_WAS_DELETED_FROM_SCHEDULER, group, name);
        dto.setJobName(name);
        dto.setJobGroup(group);
        deleteJobInfo(dto);
        logInfo(JOB_INFO_HAS_DELETED, group, name);
    }

    private void deleteJobInfo(JobRunningInfoDto jobCreateDto) {
        IJobRunningInfoService jobRunningInfoService = applicationContext.getBean(IJobRunningInfoService.class);
        jobRunningInfoService.delete(jobCreateDto);
    }

    protected void logInfo(String info, Object... para) {
        if (getLog().isInfoEnabled()) {
            getLog().info(info, para);
        }
    }

    protected void logInfo(String info) {
        if (getLog().isInfoEnabled()) {
            getLog().info(info);
        }
    }

}

有个 jobDeleted 方法,根据代码可以看出,在删除job的时候,会删除 sys_job_running_info 表中对应Job的执行记录。

插件SchedulerPlugin

在Hap 中的 quartz.properties 配置文件中,有这样一行配置

org.quartz.plugin.runningListener.class=com.hand.hap.job.plugin.RunningListenerPlugin

这其实就是quartz的插件功能。
在上面我没已经提到了Hap中对应Listener类型分别定义了几个Listener,但是发现还少了点什么。并没有在哪个地方看到JobRunningListener添加了需要监听的Job。其实这些动作都在 RunningListenerPlugin 中。

image.png

DefaultSchedulerPlugin 实现了 SchedulerPlugin, 没什么实质的内容

image.png

RunningListenerPlugin 继承了 DefaultSchedulerPlugin,并重写了 start 方法,然后将所有 Job和 JobRunningListener关联起来

image.png

至此,有关于Hap中Job的内容到此结束。下一章节将介绍 quartz 在 soringboot2 中的应用。

整合Springboot

上一章节介绍了Quartz在Hap中的应用(其实就是在spring 中的应用)。本章节介绍 Quartz 在 springboot2中的应用。

添加依赖

        <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz-jobs -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
        </dependency>

有可莪能会报一个 c3p0的错误,这时候需要添加一个依赖

        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>

配置

///QuartzConfigure

package com.hand.sxy.config;

import org.quartz.spi.JobFactory;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

import javax.sql.DataSource;


/**
 * @author pavan.solapure
 */

@Configuration
@EnableScheduling
public class QuartzConfigure {


    /**
     * 继承org.springframework.scheduling.quartz.SpringBeanJobFactory
     * 实现任务实例化方式
     */
    public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
            ApplicationContextAware {

        private transient AutowireCapableBeanFactory beanFactory;

        @Override
        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }

        /**
         * 将job实例交给spring ioc托管
         * 我们在job实例实现类内可以直接使用spring注入的调用被spring ioc管理的实例
         *
         * @param bundle
         * @return
         * @throws Exception
         */
        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
            final Object job = super.createJobInstance(bundle);
            /**
             * 将job实例交付给spring ioc
             */
            beanFactory.autowireBean(job);
            return job;
        }
    }

    /**
     * 配置任务工厂实例
     *
     * @param applicationContext spring上下文实例
     * @return
     */
    @Bean
    public JobFactory jobFactory(ApplicationContext applicationContext) {
        /**
         * 采用自定义任务工厂 整合spring实例来完成构建任务
         * see {@link AutowiringSpringBeanJobFactory}
         */
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }

    /**
     * 配置任务调度器
     * 使用项目数据源作为quartz数据源
     *
     * @param jobFactory 自定义配置任务工厂
     * @param dataSource 数据源实例
     * @return
     * @throws Exception
     */
    @Bean(destroyMethod = "destroy", autowire = Autowire.NO)
    public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        //将spring管理job自定义工厂交由调度器维护
        schedulerFactoryBean.setJobFactory(jobFactory);
        //设置覆盖已存在的任务
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        //项目启动完成后,等待2秒后开始执行调度器初始化
        schedulerFactoryBean.setStartupDelay(2);
        //设置调度器自动运行
        schedulerFactoryBean.setAutoStartup(true);
        //设置数据源,使用与项目统一数据源
        schedulerFactoryBean.setDataSource(dataSource);
        //设置上下文spring bean name
        schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
        //设置配置文件位置
        schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
        return schedulerFactoryBean;
    }
}

//quartz.properties

# 固定前缀org.quartz
# 主要分为scheduler、threadPool、jobStore、plugin等部分
#
#
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

# threadCount和threadPriority将以setter的形式注入ThreadPool实例
# 并发个数
org.quartz.threadPool.threadCount = 5

# 优先级
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

org.quartz.jobStore.misfireThreshold = 5000

# 默认存储在内存中
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

#持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties = true 

org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.jobStore.dataSource = qzDS

org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver

org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/vue?characterEncoding=utf8&autoReconnect=true&useSSL=false

org.quartz.dataSource.qzDS.user = root

org.quartz.dataSource.qzDS.password = root

org.quartz.dataSource.qzDS.maxConnections = 10

使用

//BasalJob

package com.hand.sxy.job;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
 * @author spilledyear
 */
public interface BasalJob extends Job {

    /**
     * Job执行入口
     *
     * @param context
     * @throws JobExecutionException
     */
    @Override
    void execute(JobExecutionContext context) throws JobExecutionException;
}

//HelloJob

package com.hand.sxy.job.example;

import com.hand.sxy.job.BasalJob;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

/**
 * @author spilledyear
 */
public class HelloJob implements BasalJob {

    private static Logger logger = LoggerFactory.getLogger(HelloJob.class);

    public HelloJob() {

    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        logger.info("Hello Job执行时间: " + new Date());

    }
}  

//JobController

package com.hand.sxy.job.controller;

import com.hand.sxy.job.BasalJob;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


/**
 * @author spilledyear
 */
@RestController
@RequestMapping(value = "/job")
public class JobController {
    private static Logger log = LoggerFactory.getLogger(JobController.class);

    @Autowired
    private Scheduler scheduler;


    @RequestMapping(value = "/addjob", method = RequestMethod.GET)
    public void addjob(@RequestParam(value = "jobGroup") String jobGroup,
                       @RequestParam(value = "jobName") String jobName,
                       @RequestParam(value = "jobClassName") String jobClassName,
                       @RequestParam(value = "cronExpression") String cronExpression) throws Exception {
        addJob(jobGroup, jobName, jobClassName, cronExpression);
    }

    public void addJob(String jobGroup, String jobName, String jobClassName, String cronExpression) throws Exception {

        // 启动调度器
        scheduler.start();

        //构建job信息
        JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobGroup, jobName).build();

        //表达式调度构建器(即任务执行的时间)
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

        //按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobGroup, jobName)
                .withSchedule(scheduleBuilder).build();

        try {
            scheduler.scheduleJob(jobDetail, trigger);

        } catch (SchedulerException e) {
            System.out.println("创建定时任务失败" + e);
            throw new Exception("创建定时任务失败");
        }
    }
.....
}

在前端请求 /job/addJob接口(其实已经集成到了那两个前后端分离的应用中,前端可视化管理Job)

后台日志

image.png

数据库数据

image.png

image.png

前端可视化管理


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,884评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,212评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,351评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,412评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,438评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,127评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,714评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,636评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,173评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,264评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,402评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,073评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,763评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,253评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,382评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,749评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,403评论 2 358

推荐阅读更多精彩内容