Spring MVC Quartz 动态任务实现方式

1.项目结构

这篇文章是对上篇文章《Spring Boot Quartz 动态任务实现方式》的稍作变动, 但最终实现效果是一致的。
持久层变动:Spring Boot使用spring data jpa, Spring MVC 使用 Mybatis

2.Maven

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
    <modelVersion>4.0.0</modelVersion>  
  
    <groupId>com.littlefxc</groupId>  
    <artifactId>learn-quartz-SpringMVC</artifactId>  
    <version>1.0-snapshot</version>  
  
    <properties>  
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>  
        <java.version>1.8</java.version>  
        <spring-framework-bom.version>5.0.8.RELEASE</spring-framework-bom.version>  
        <mysql.verison>5.1.47</mysql.verison>  
        <druid.version>1.1.10</druid.version>  
        <quartz.version>2.3.0</quartz.version>  
        <spring-data.version>2.0.9.RELEASE</spring-data.version>  
        <junit.version>4.12</junit.version>  
        <slf4j.version>1.7.25</slf4j.version>  
        <log4j2.version>2.11.1</log4j2.version>  
        <mybatis.version>3.4.6</mybatis.version>  
        <mybatis-spring.version>1.3.2</mybatis-spring.version>  
        <aopalliance.version>1.0</aopalliance.version>  
        <pagehelper.version>5.1.8</pagehelper.version>  
    </properties>  
  
    <dependencyManagement>  
        <dependencies>  
            <dependency>  
                <groupId>org.springframework</groupId>  
                <artifactId>spring-framework-bom</artifactId>  
                <version>${spring-framework-bom.version}</version>  
                <scope>import</scope>  
                <type>pom</type>  
            </dependency>  
        </dependencies>  
    </dependencyManagement>  
  
    <dependencies>  
        <dependency>  
            <groupId>org.projectlombok</groupId>  
            <artifactId>lombok</artifactId>  
            <version>1.18.4</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-web</artifactId>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-webmvc</artifactId>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-aspects</artifactId>  
        </dependency>  
        <dependency>  
            <groupId>aopalliance</groupId>  
            <artifactId>aopalliance</artifactId>  
            <version>${aopalliance.version}</version>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-jdbc</artifactId>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-context-support</artifactId>  
        </dependency>  
        <!-- quartz -->  
        <dependency>  
            <groupId>org.quartz-scheduler</groupId>  
            <artifactId>quartz</artifactId>  
            <version>${quartz.version}</version>  
            <exclusions>  
                <exclusion>  
                    <artifactId>slf4j-api</artifactId>  
                    <groupId>org.slf4j</groupId>  
                </exclusion>  
            </exclusions>  
        </dependency>  
        <dependency>  
            <groupId>org.quartz-scheduler</groupId>  
            <artifactId>quartz-jobs</artifactId>  
            <version>${quartz.version}</version>  
        </dependency>  
        <!-- mybatis -->  
        <dependency>  
            <groupId>org.mybatis</groupId>  
            <artifactId>mybatis</artifactId>  
            <version>${mybatis.version}</version>  
        </dependency>  
        <!-- mybatis/spring包 -->  
        <dependency>  
            <groupId>org.mybatis</groupId>  
            <artifactId>mybatis-spring</artifactId>  
            <version>${mybatis-spring.version}</version>  
        </dependency>  
        <!-- pagehelper -->  
        <dependency>  
            <groupId>com.github.pagehelper</groupId>  
            <artifactId>pagehelper</artifactId>  
            <version>${pagehelper.version}</version>  
        </dependency>  
        <!-- druid -->  
        <dependency>  
            <groupId>com.alibaba</groupId>  
            <artifactId>druid</artifactId>  
            <version>${druid.version}</version>  
        </dependency>  
        <!-- mysql -->  
        <dependency>  
            <groupId>mysql</groupId>  
            <artifactId>mysql-connector-java</artifactId>  
            <version>${mysql.verison}</version>  
        </dependency>  
        <dependency>  
            <groupId>javax.servlet</groupId>  
            <artifactId>javax.servlet-api</artifactId>  
            <version>3.1.0</version>  
            <scope>provided</scope>  
        </dependency>  
        <!-- test -->  
        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-test</artifactId>  
            <scope>test</scope>  
        </dependency>  
        <dependency>  
            <groupId>junit</groupId>  
            <artifactId>junit</artifactId>  
            <version>${junit.version}</version>  
        </dependency>  
        <!-- 日志管理 -->  
        <dependency>  
            <groupId>org.apache.logging.log4j</groupId>  
            <artifactId>log4j-slf4j-impl</artifactId>  
            <version>${log4j2.version}</version>  
            <scope>compile</scope>  
        </dependency>  
        <dependency>  
            <groupId>org.apache.logging.log4j</groupId>  
            <artifactId>log4j-core</artifactId>  
            <version>${log4j2.version}</version>  
            <scope>compile</scope>  
        </dependency>  
        <dependency>  
            <groupId>org.apache.logging.log4j</groupId>  
            <artifactId>log4j-api</artifactId>  
            <version>${log4j2.version}</version>  
            <scope>compile</scope>  
        </dependency>  
        <dependency>  
            <groupId>org.apache.logging.log4j</groupId>  
            <artifactId>log4j-web</artifactId>  
            <version>${log4j2.version}</version>  
            <scope>runtime</scope>  
        </dependency>  
        <dependency>  
            <groupId>org.slf4j</groupId>  
            <artifactId>jcl-over-slf4j</artifactId>  
            <version>${slf4j.version}</version>  
            <scope>compile</scope>  
        </dependency>  
        <dependency>  
            <groupId>org.slf4j</groupId>  
            <artifactId>slf4j-api</artifactId>  
            <version>${slf4j.version}</version>  
            <scope>compile</scope>  
        </dependency>  
        <!-- JSON -->  
        <dependency>  
            <groupId>com.fasterxml.jackson.core</groupId>  
            <artifactId>jackson-databind</artifactId>  
            <version>2.9.7</version>  
            <optional>true</optional>  
        </dependency>  
    </dependencies>  
  
    <build>  
        <plugins>  
            <plugin>  
                <groupId>org.apache.maven.plugins</groupId>  
                <artifactId>maven-compiler-plugin</artifactId>  
                <configuration>  
                    <source>8</source>  
                    <target>8</target>  
                </configuration>  
            </plugin>  
        </plugins>  
        <!-- 默认源代码和资源文件目录配置 -->  
        <sourceDirectory>src/main/java</sourceDirectory>  
        <resources>  
            <resource>  
                <directory>src/main/resources</directory>  
            </resource>  
        </resources>  
    </build>  
</project>  

3.数据库-模型

在jar包quartz-2.3.0.jar下有数据库sql文件.
sql文件的包路径地址:org.quartz.impl.jdbcjobstore,选择tables_mysql_innodb.sql

3.1. scheduler_job_info.sql

DROP TABLE IF EXISTS `scheduler_job_info`;  
CREATE TABLE `scheduler_job_info`  (  
  `id` bigint(20) NOT NULL AUTO_INCREMENT,  
  `cron_expression` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  
  `cron_job` bit(1) NULL DEFAULT NULL,  
  `job_class` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  
  `job_group` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  
  `job_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,  
  `scheduler_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,  
  `repeat_time` bigint(20) NULL DEFAULT NULL,  
  PRIMARY KEY (`id`) USING BTREE,  
  UNIQUE INDEX `uk_job_name`(`job_name`) USING BTREE  
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;  
  
SET FOREIGN_KEY_CHECKS = 1;  

3.2.实体类

package com.littlefxc.example.quartz.enitiy;  
  
import lombok.Data;  
  
import javax.persistence.*;  
import java.io.Serializable;  
  
/** 
 * @author fengxuechao 
 * @date 12/19/2018 
 */  
@Data  
@Entity  
@Table(name = "scheduler_job_info")  
public class SchedulerJob implements Serializable {  
  
    private static final long serialVersionUID = -8990533448070839127L;  
  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
  
    @Column(unique = true)  
    private String jobName;  
  
    private String jobGroup;  
  
    private String jobClass;  
  
    private String cronExpression;  
  
    private Long repeatTime;  
  
    private Boolean cronJob;  
  
    private String schedulerName;  
}  

4.配置

##4.1.quartz.properties
文件应用在spring-context.xml中,详见4.3章节黄色高亮代码。
org.quartz.scheduler.instanceIdGenerator.class=com.littlefxc.example.quartz.component.CustomQuartzInstanceIdGenerator表示自定义的实例名生成策略,该类代码可以在5.1章节中看到,在数据库上的代码实际效果可以查看到(表qrtz_scheduler_state, 字段INSTANCE_NAME)。
请注意对比与上篇文章《Spring Boot Quartz 动态任务实现方式》中4.1章节中的配置(application.properties)以spring.quartz.properties为前缀的属性。两者之间的区别仅在于SpringBoot以spring.quartz.properties为前缀。

# 属性 org.quartz.impl.StdSchedulerFactory  
# 属性可为任何值,(最好别用DefaultQuartzScheduler),用在 JDBC JobStore 中来唯一标识实例,但是所有集群节点中必须相同  
org.quartz.scheduler.instanceName=lean-quartz-SpringMVC  
org.quartz.scheduler.instanceId=AUTO  
org.quartz.scheduler.instanceIdGenerator.class=com.littlefxc.example.quartz.component.CustomQuartzInstanceIdGenerator  
org.quartz.threadPool.threadCount=20  
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.misfireThreshold=60000  
org.quartz.jobStore.tablePrefix=qrtz_  
# 集群配置  
org.quartz.jobStore.isClustered=true  
org.quartz.plugin.shutdownHook.class=org.quartz.plugins.management.ShutdownHookPlugin  
org.quartz.plugin.shutdownHook.cleanShutdown=TRUE  

4.2.spring-context.xml

spring-tx.xml, spring-mvc.xml的配置不是重点,以免浪费篇幅,按照你自己的个人习惯来配置.
同时我也将Druid数据源的配置省略了,请重点查看有黄色高亮的bean配置,参照上篇文章《Spring Boot Quartz 动态任务实现方式》中的配置
spring-tx.xml :配置Spring事务.
spring-mvc.xml:配置spring mvc.

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">  
  
    <context:annotation-config/>  
    <context:component-scan base-package="com.littlefxc.example.quartz.service"/>  
    <context:component-scan base-package="com.littlefxc.example.quartz.dao"/>  
    <context:component-scan base-package="com.littlefxc.example.quartz.component"/>  
  
    <context:property-placeholder location="classpath:druid.properties"/>  
  
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">  
        ... Druid数据源的配置
     </bean>  
  
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="dataSource" />  
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />  
        <property name="typeAliasesPackage" value="com.littlefxc.example.quartz.enitiy" />  
        <property name="mapperLocations" value="classpath:mybatis/mapper/*.xml" />  
        <property name="plugins">  
            <array>  
                <bean class="com.github.pagehelper.PageInterceptor">  
                    <!-- 参考链接:https://pagehelper.github.io/docs/howtouse/#1-%E5%BC%95%E5%85%A5%E5%88%86%E9%A1%B5%E6%8F%92%E4%BB%B6 -->  
                    <property name="properties">  
                        <value>  
                            helperDialect=mysql  
                            reasonable=true  
                            supportMethodsArguments=true  
                            params=count=countSql  
                            rowBoundsWithCount=true  
                            autoRuntimeDialect=true  
                        </value>  
                    </property>  
                </bean>  
            </array>  
        </property>  
    </bean>  
  
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />  
        <property name="basePackage" value="com.littlefxc.example.quartz.dao" />  
    </bean>  
  
    <!-- 创建SchedulerFactoryBean,参考模块learn-quartz-SpringBoot#com.littlefxc.example.quartz.config.SchedulerConfig -->  
    <bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">  
        <property name="dataSource" ref="dataSource"/>  
        <property name="jobFactory" ref="jobFactory"/>  
        <property name="schedulerName" value="scheduler-spring-mvc"/>  
        <!--可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 -->  
        <property name="overwriteExistingJobs" value="true"/>  
        <!--可以在web关闭的时候关闭线程-->  
        <property name="waitForJobsToCompleteOnShutdown" value="true"/>  
        <property name="configLocation" value="classpath:quartz.properties"/>  
    </bean>  
  
    <bean id="jobFactory" class="com.littlefxc.example.quartz.component.SchedulerJobFactory" />  
  
    <import resource="spring-tx.xml"/>  
    <import resource="spring-mvc.xml"/>  
</beans>  

5.组件

##5.1.CustomQuartzInstanceIdGenerator
用法详见4.1章节

package com.littlefxc.example.quartz.component;  
  
import org.quartz.SchedulerException;  
import org.quartz.spi.InstanceIdGenerator;  
  
import java.util.UUID;  
  
/** 
 * @author fengxuechao 
 * @date 12/19/2018 
 */  
public class CustomQuartzInstanceIdGenerator implements InstanceIdGenerator {  
  
    @Override  
    public String generateInstanceId() throws SchedulerException {  
        try {  
            return UUID.randomUUID().toString();  
        } catch (Exception ex) {  
            throw new SchedulerException("Couldn't generate UUID!", ex);  
        }  
    }  
  
}  

5.2.SchedulerJobFactory

Quartz与Spring结合。
在SchedulerFactory中引入Spring上下文。
用法详见4.2章节。

package com.littlefxc.example.quartz.component;  
  
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;  
  
/** 
 * 模仿了:{@link org.springframework.boot.autoconfigure.quartz.AutowireCapableBeanJobFactory} 
 * 
 * @author fengxuechao 
 * @date 12/19/2018 
 * @see <a href="http://blog.btmatthews.com/?p=40#comment-33797">注入Spring上下文(applicationContext) 
 */  
public class SchedulerJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {  
  
    private AutowireCapableBeanFactory beanFactory;  
  
    @Override  
    public void setApplicationContext(final ApplicationContext context) {  
        beanFactory = context.getAutowireCapableBeanFactory();  
    }  
  
    @Override  
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {  
        final Object job = super.createJobInstance(bundle);  
        beanFactory.autowireBean(job);  
        return job;  
    }  
}  

5.3.JobScheduleCreator

Scheduler 创建Job,SimpleTrigger,CronTrigger的封装类。
用法在service 层体现。

package com.littlefxc.example.quartz.component;  
  
import lombok.extern.slf4j.Slf4j;  
import org.quartz.CronTrigger;  
import org.quartz.JobDataMap;  
import org.quartz.JobDetail;  
import org.quartz.SimpleTrigger;  
import org.springframework.context.ApplicationContext;  
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;  
import org.springframework.scheduling.quartz.JobDetailFactoryBean;  
import org.springframework.scheduling.quartz.QuartzJobBean;  
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;  
import org.springframework.stereotype.Component;  
  
import java.text.ParseException;  
import java.util.Date;  
  
/** 
 * Scheduler创建Job, SimpleTrigger, CronTrigger 
 * 
 * @author fengxuechao 
 * @date 12/19/2018 
 * @see <a href="https://blog.csdn.net/yangshangwei/article/details/78539433#withmisfirehandlinginstructiondonothing">Quartz-错过触发机制</a> 
 */  
@Slf4j  
@Component  
public class JobScheduleCreator {  
  
    /** 
     * Create Quartz Job. 
     * 
     * @param jobClass  Class whose executeInternal() method needs to be called. 
     * @param isDurable Job needs to be persisted even after completion. if true, job will be persisted, not otherwise. 
     * @param context   Spring application context. 
     * @param jobName   Job name. 
     * @param jobGroup  Job group. 
     * @return JobDetail object 
     */  
    public JobDetail createJob(Class<? extends QuartzJobBean> jobClass, boolean isDurable,  
                               ApplicationContext context, String jobName, String jobGroup) {  
        JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();  
        factoryBean.setJobClass(jobClass);  
        factoryBean.setDurability(isDurable);  
        factoryBean.setApplicationContext(context);  
        factoryBean.setName(jobName);  
        factoryBean.setGroup(jobGroup);  
  
        // set job data map  
        JobDataMap jobDataMap = new JobDataMap();  
        jobDataMap.put(jobName + jobGroup, jobClass.getName());  
        factoryBean.setJobDataMap(jobDataMap);  
  
        factoryBean.afterPropertiesSet();  
  
        return factoryBean.getObject();  
    }  
  
    /** 
     * Create cron trigger. 
     * 
     * @param triggerName        Trigger name. 
     * @param startTime          Trigger start time. 
     * @param cronExpression     Cron expression. 
     * @param misFireInstruction Misfire instruction (what to do in case of misfire happens). 
     * @return {@link CronTrigger} 
     */  
    public CronTrigger createCronTrigger(String triggerName, Date startTime, String cronExpression, int misFireInstruction) {  
        CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();  
        factoryBean.setName(triggerName);  
        factoryBean.setStartTime(startTime);  
        factoryBean.setCronExpression(cronExpression);  
        factoryBean.setMisfireInstruction(misFireInstruction);  
        try {  
            factoryBean.afterPropertiesSet();  
        } catch (ParseException e) {  
            log.error(e.getMessage(), e);  
        }  
        return factoryBean.getObject();  
    }  
  
    /** 
     * Create simple trigger. 
     * 
     * @param triggerName        Trigger name. 
     * @param startTime          Trigger start time. 
     * @param repeatTime         Job repeat period mills 
     * @param misFireInstruction Misfire instruction (what to do in case of misfire happens). 
     * @return {@link SimpleTrigger} 
     */  
    public SimpleTrigger createSimpleTrigger(String triggerName, Date startTime, Long repeatTime, int misFireInstruction) {  
        SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();  
        factoryBean.setName(triggerName);  
        factoryBean.setStartTime(startTime);  
        factoryBean.setRepeatInterval(repeatTime);  
        factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);  
        factoryBean.setMisfireInstruction(misFireInstruction);  
        factoryBean.afterPropertiesSet();  
        return factoryBean.getObject();  
    }  
}  

6.Jobs

Simple Job

package com.littlefxc.example.quartz.jobs;  
  
import lombok.extern.slf4j.Slf4j;  
import org.quartz.JobExecutionContext;  
import org.quartz.JobExecutionException;  
import org.springframework.scheduling.quartz.QuartzJobBean;  
  
import java.util.stream.IntStream;  
  
/** 
 * @author fengxuechao 
 * @date 12/19/2018 
 */  
@Slf4j  
public class SimpleJob extends QuartzJobBean {  
    @Override  
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {  
        log.info("{} Start................", context.getJobDetail().getKey());  
        IntStream.range(0, 5).forEach(i -> {  
            log.info("Counting - {}", i);  
            try {  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
                log.error(e.getMessage(), e);  
            }  
        });  
        log.info("{} End................", context.getJobDetail().getKey());  
    }  
}  

Cron Job

package com.littlefxc.example.quartz.jobs;  
  
import lombok.extern.slf4j.Slf4j;  
import org.quartz.DisallowConcurrentExecution;  
import org.quartz.JobExecutionContext;  
import org.quartz.JobExecutionException;  
import org.springframework.scheduling.quartz.QuartzJobBean;  
  
import java.util.stream.IntStream;  
  
/** 
 * @author fengxuechao 
 * @date 12/19/2018 
 */  
@Slf4j  
@DisallowConcurrentExecution // 这个注解告诉Quartz,一个给定的Job定义(也就是一个JobDetail实例),不并发运行。  
public class SampleCronJob extends QuartzJobBean {  
    @Override  
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {  
        log.info("{} Start................", context.getJobDetail().getKey());  
        IntStream.range(0, 10).forEach(i -> {  
            log.info("Counting - {}", i);  
            try {  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
                log.error(e.getMessage(), e);  
            }  
        });  
        log.info("{} End................", context.getJobDetail().getKey());  
    }  
}  

7.控制器层

7.1.QuartzController

工作调度的主要代码。
与上篇文章《Spring Boot Quartz 动态任务实现方式》的不同点在于queryjob()方法,直接使用了pageNum, pageSize而不是Pageable(Spring data jpa 自带分页类)

package com.littlefxc.example.quartz.controller;  
  
import com.github.pagehelper.PageInfo;  
import com.littlefxc.example.quartz.enitiy.SchedulerJob;  
import com.littlefxc.example.quartz.service.SchedulerService;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.*;  
  
import java.util.Map;  
  
/** 
 * @author fengxuechao 
 * @date 12/19/2018 
 **/  
@RestController  
@RequestMapping("/job")  
@Slf4j  
public class QuartzController {  
  
    private final SchedulerService schedulerService;  
  
    @Autowired  
    public QuartzController(SchedulerService schedulerService) {  
        this.schedulerService = schedulerService;  
    }  
  
    /** 
     * 添加 
     * 
     * @param jobInfo 
     */  
    @PostMapping(value = "/addjob")  
    public void addjob(@RequestBody SchedulerJob jobInfo) {  
        schedulerService.scheduleNewJob(jobInfo);  
    }  
  
    /** 
     * 暂停 
     * 
     * @param jobName 
     * @param jobGroup 
     */  
    @PostMapping(value = "/pausejob")  
    public void pausejob(  
            @RequestParam String jobName, @RequestParam String jobGroup) {  
        schedulerService.pauseJob(jobName, jobGroup);  
    }  
  
    /** 
     * 恢复启动 
     * 
     * @param jobName 
     * @param jobGroup 
     */  
    @PostMapping(value = "/resumejob")  
    public void resumejob(@RequestParam String jobName, @RequestParam String jobGroup) {  
        schedulerService.resumeJob(jobName, jobGroup);  
    }  
  
    /** 
     * 更新:移除older trigger,添加new trigger 
     * 
     * @param jobInfo 
     */  
    @PostMapping(value = "/reschedulejob")  
    public void rescheduleJob(@RequestBody SchedulerJob jobInfo) {  
        schedulerService.updateScheduleJob(jobInfo);  
    }  
  
    /** 
     * 删除 
     * 
     * @param jobName 
     * @param jobGroup 
     */  
    @PostMapping(value = "/deletejob")  
    public void deletejob(@RequestParam String jobName, @RequestParam String jobGroup) {  
        schedulerService.deleteJob(jobName, jobGroup);  
    }  
  
    /** 
     * 查询 
     * 
     * @param pageNum  default 1 
     * @param pageSize default 10 
     * @param cron     true:cron trigger, false:simple trigger 
     * @return 
     */  
    @GetMapping(value = "/queryjob")  
    public PageInfo<Map<String, Object>> queryjob(  
            @RequestParam(defaultValue = "0") Integer pageNum,  
            @RequestParam(defaultValue = "10") Integer pageSize,  
            @RequestParam Boolean cron) {  
        return schedulerService.findAll(cron, pageNum, pageSize);  
    }  
}  

7.2.SchedulerController

仅对自定义数据库(scheduler_job_info)操作的控制器。

package com.littlefxc.example.quartz.controller;  
  
import com.littlefxc.example.quartz.enitiy.SchedulerJob;  
import com.littlefxc.example.quartz.service.SchedulerService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/** 
 * @author fengxuechao 
 * @date 12/20/2018 
 **/  
@RestController  
@RequestMapping("/job-info")  
public class SchedulerController {  
  
    @Autowired  
    private SchedulerService schedulerService;  
  
    /** 
     * 根据jobName查询 
     * @param jobName 
     * @return {@link SchedulerJob} 
     */  
    @GetMapping("/findOne")  
    public SchedulerJob findOne(@RequestParam String jobName) {  
        return schedulerService.findOne(jobName);  
    }  
}  

8.Service层

##8.1.SchedulerServiceImpl

package com.littlefxc.example.quartz.service.impl;  
  
import com.github.pagehelper.PageInfo;  
import com.littlefxc.example.quartz.component.JobScheduleCreator;  
import com.littlefxc.example.quartz.dao.SchedulerDao;  
import com.littlefxc.example.quartz.enitiy.SchedulerJob;  
import com.littlefxc.example.quartz.service.SchedulerService;  
import lombok.extern.slf4j.Slf4j;  
import org.apache.ibatis.session.RowBounds;  
import org.quartz.*;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.context.ApplicationContext;  
import org.springframework.scheduling.quartz.QuartzJobBean;  
import org.springframework.scheduling.quartz.SchedulerFactoryBean;  
import org.springframework.stereotype.Service;  
import org.springframework.transaction.annotation.Transactional;  
  
import java.util.Date;  
import java.util.List;  
import java.util.Map;  
  
/** 
 * @author fengxuechao 
 * @date 12/19/2018 
 */  
@Slf4j  
@Service  
@Transactional(rollbackFor = Exception.class)  
public class SchedulerServiceImpl implements SchedulerService {  
  
    @Autowired  
    private SchedulerFactoryBean schedulerFactoryBean;  
  
    @Autowired  
    private SchedulerDao schedulerDao;  
  
    @Autowired  
    private ApplicationContext context;  
  
    @Autowired  
    private JobScheduleCreator scheduleCreator;  
  
    /** 
     * 启动所有的在表scheduler_job_info中记录的job 
     */  
    @Override  
    public void startAllSchedulers() {  
        List<SchedulerJob> jobInfoList = schedulerDao.findAll();  
        if (jobInfoList != null) {  
            Scheduler scheduler = schedulerFactoryBean.getScheduler();  
            jobInfoList.forEach(jobInfo -> {  
                try {  
                    JobDetail jobDetail = JobBuilder.newJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()))  
                            .withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()).build();  
                    if (!scheduler.checkExists(jobDetail.getKey())) {  
                        Trigger trigger;  
                        jobDetail = scheduleCreator.createJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()),  
                                false, context, jobInfo.getJobName(), jobInfo.getJobGroup());  
  
                        if (jobInfo.getCronJob() && CronExpression.isValidExpression(jobInfo.getCronExpression())) {  
                            trigger = scheduleCreator.createCronTrigger(jobInfo.getJobName(), new Date(),  
                                    jobInfo.getCronExpression(), CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);  
                        } else {  
                            trigger = scheduleCreator.createSimpleTrigger(jobInfo.getJobName(), new Date(),  
                                    jobInfo.getRepeatTime(), SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);  
                        }  
  
                        scheduler.scheduleJob(jobDetail, trigger);  
  
                    }  
                } catch (ClassNotFoundException e) {  
                    log.error("Class Not Found - {}", jobInfo.getJobClass(), e);  
                } catch (SchedulerException e) {  
                    log.error(e.getMessage(), e);  
                }  
            });  
        }  
    }  
  
    @Override  
    public void scheduleNewJob(SchedulerJob jobInfo) {  
        try {  
            Scheduler scheduler = schedulerFactoryBean.getScheduler();  
  
            JobDetail jobDetail = JobBuilder.newJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()))  
                    .withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()).build();  
            if (!scheduler.checkExists(jobDetail.getKey())) {  
  
                jobDetail = scheduleCreator.createJob((Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClass()),  
                        false, context, jobInfo.getJobName(), jobInfo.getJobGroup());  
  
                Trigger trigger;  
                if (jobInfo.getCronJob()) {  
                    trigger = scheduleCreator.createCronTrigger(jobInfo.getJobName(), new Date(), jobInfo.getCronExpression(),  
                            CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);  
                } else {  
                    trigger = scheduleCreator.createSimpleTrigger(jobInfo.getJobName(), new Date(), jobInfo.getRepeatTime(),  
                            SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);  
                }  
  
                scheduler.scheduleJob(jobDetail, trigger);  
                jobInfo.setSchedulerName(schedulerFactoryBean.getScheduler().getSchedulerName());  
                schedulerDao.save(jobInfo);  
            } else {  
                log.error("scheduleNewJobRequest.jobAlreadyExist");  
            }  
        } catch (ClassNotFoundException e) {  
            log.error("Class Not Found - {}", jobInfo.getJobClass(), e);  
        } catch (SchedulerException e) {  
            log.error(e.getMessage(), e);  
        }  
    }  
  
    @Override  
    public void updateScheduleJob(SchedulerJob jobInfo) {  
        Trigger newTrigger;  
        System.out.println(jobInfo);  
        if (jobInfo.getCronJob()) {  
            newTrigger = scheduleCreator.createCronTrigger(jobInfo.getJobName(), new Date(), jobInfo.getCronExpression(),  
                    CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);  
        } else {  
            newTrigger = scheduleCreator.createSimpleTrigger(jobInfo.getJobName(), new Date(), jobInfo.getRepeatTime(),  
                    SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);  
        }  
        try {  
            schedulerFactoryBean.getScheduler().rescheduleJob(TriggerKey.triggerKey(jobInfo.getJobName()), newTrigger);  
            jobInfo.setSchedulerName(schedulerFactoryBean.getScheduler().getSchedulerName());  
            schedulerDao.save(jobInfo);  
        } catch (SchedulerException e) {  
            log.error(e.getMessage(), e);  
        }  
    }  
  
    /** 
     * unscheduleJob(TriggerKey triggerKey)只是不再调度触发器,所以,当其他的触发器引用了这个Job,它们不会被改变 
     * 
     * @param jobName 
     * @return 
     */  
    @Override  
    public boolean unScheduleJob(String jobName) {  
        try {  
            return schedulerFactoryBean.getScheduler().unscheduleJob(new TriggerKey(jobName));  
        } catch (SchedulerException e) {  
            log.error("Failed to un-schedule job - {}", jobName, e);  
            return false;  
        }  
    }  
  
    /** 
     * deleteJob(JobKey jobKey):<br> 
     * 1.循环遍历所有引用此Job的触发器,以取消它们的调度(to unschedule them)<br> 
     * 2.从jobstore中删除Job 
     * 
     * @param jobName  job name 
     * @param jobGroup job group 
     * @return 
     */  
    @Override  
    public boolean deleteJob(String jobName, String jobGroup) {  
        try {  
            boolean deleteJob = schedulerFactoryBean.getScheduler().deleteJob(new JobKey(jobName, jobGroup));  
            if (deleteJob) {  
                SchedulerJob job = schedulerDao.findSchedulerJobByJobName(jobName);  
                schedulerDao.deleteByJobName(jobName);  
            }  
            return deleteJob;  
        } catch (SchedulerException e) {  
            log.error("Failed to delete job - {}", jobName, e);  
            return false;  
        }  
    }  
  
    /** 
     * 暂停 
     * 
     * @param jobName  job name 
     * @param jobGroup job group 
     * @return 
     */  
    @Override  
    public boolean pauseJob(String jobName, String jobGroup) {  
        try {  
            schedulerFactoryBean.getScheduler().pauseJob(new JobKey(jobName, jobGroup));  
            return true;  
        } catch (SchedulerException e) {  
            log.error("Failed to pause job - {}", jobName, e);  
            return false;  
        }  
    }  
  
    /** 
     * 恢复 
     * 
     * @param jobName  job name 
     * @param jobGroup job group 
     * @return 
     */  
    @Override  
    public boolean resumeJob(String jobName, String jobGroup) {  
        try {  
            schedulerFactoryBean.getScheduler().resumeJob(new JobKey(jobName, jobGroup));  
            return true;  
        } catch (SchedulerException e) {  
            log.error("Failed to resume job - {}", jobName, e);  
            return false;  
        }  
    }  
  
    @Override  
    public boolean startJobNow(String jobName, String jobGroup) {  
        try {  
            schedulerFactoryBean.getScheduler().triggerJob(new JobKey(jobName, jobGroup));  
            return true;  
        } catch (SchedulerException e) {  
            log.error("Failed to start new job - {}", jobName, e);  
            return false;  
        }  
    }  
  
    @Transactional(readOnly = true)  
    @Override  
    public PageInfo<Map<String, Object>> findAll(Boolean cron, Integer pageNum, Integer pageSize) {  
        List<Map<String, Object>> list;  
        if (cron) {  
            list = schedulerDao.getJobWithCronTrigger(new RowBounds(pageNum, pageSize));  
        } else {  
            list = schedulerDao.getJobWithSimpleTrigger(new RowBounds(pageNum, pageSize));  
        }  
        return new PageInfo<>(list);  
    }  
  
    @Transactional(readOnly = true)  
    @Override  
    public SchedulerJob findOne(String jobName) {  
        return schedulerDao.findSchedulerJobByJobName(jobName);  
    }  
  
}  

9.Dao层

9.1.SchedulerDao

package com.littlefxc.example.quartz.dao;  
  
import com.littlefxc.example.quartz.enitiy.SchedulerJob;  
import org.apache.ibatis.session.RowBounds;  
import org.springframework.stereotype.Repository;  
  
import java.util.List;  
import java.util.Map;  
  
/** 
 * @author fengxuechao 
 * @date 12/19/2018 
 */  
@Repository  
public interface SchedulerDao {  
  
    /** 
     * 仅查询simple trigger关联的Job 
     * 不查询cron trigger关联的job 
     * 
     * @param rowBounds 分页插件 
     * @return 
     */  
    List<Map<String, Object>> getJobWithSimpleTrigger(RowBounds rowBounds);  
  
    /** 
     * 仅查询cron trigger关联的Job 
     * 不查询simple trigger关联的job 
     * 
     * @param rowBounds 分页插件 
     * @return 
     */  
    List<Map<String, Object>> getJobWithCronTrigger(RowBounds rowBounds);  
  
    /** 
     * 根据JobName查询SchedulerJob 
     * 
     * @param jobName 
     * @return SchedulerJob 
     */  
    SchedulerJob findSchedulerJobByJobName(String jobName);  
  
    List<SchedulerJob> findAll();  
  
    /** 
     * 保存和更新 
     * 当id==null时,更新 
     * @param jobInfo 
     * @return 
     */  
    Integer save(SchedulerJob jobInfo);  
  
    Integer deleteByJobName(String jobName);  
}  

9.2.SchedulerDao.xml

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
<mapper namespace="com.littlefxc.example.quartz.dao.SchedulerDao">  
  
    <resultMap id="SchedulerJobMap" type="SchedulerJob">  
        <id property="id" column="id"/>  
        <result property="cronExpression" column="cron_expression"/>  
        <result property="jobClass" column="job_class"/>  
        <result property="jobName" column="job_name"/>  
        <result property="jobGroup" column="job_group"/>  
        <result property="repeatTime" column="repeat_time"/>  
        <result property="cronJob" column="cron_job"/>  
        <result property="schedulerName" column="scheduler_name"/>  
    </resultMap>  
  
    <!-- @formatter:off -->  
    <sql id="sql_scheduler_job_info">cron_expression,cron_job,job_class,job_group,job_name,repeat_time, scheduler_name</sql>  
    <!-- @formatter:on -->  
  
    <select id="getJobWithSimpleTrigger" resultType="java.util.HashMap">  
        select j.JOB_NAME,  
               j.JOB_GROUP,  
               j.JOB_CLASS_NAME,  
               t.TRIGGER_NAME,  
               t.TRIGGER_GROUP,  
               s.REPEAT_INTERVAL,  
               s.TIMES_TRIGGERED  
        from qrtz_job_details as j  
                 join qrtz_triggers as t  
                 join qrtz_simple_triggers as s  
                      ON j.JOB_NAME = t.JOB_NAME  
                          and t.TRIGGER_NAME = s.TRIGGER_NAME  
                          and t.TRIGGER_GROUP = s.TRIGGER_GROUP  
        where j.SCHED_NAME = 'scheduler-spring-mvc'  
    </select>  
  
    <select id="getJobWithCronTrigger" resultType="java.util.HashMap">  
        select j.JOB_NAME,  
               j.JOB_GROUP,  
               j.JOB_CLASS_NAME,  
               t.TRIGGER_NAME,  
               t.TRIGGER_GROUP,  
               c.CRON_EXPRESSION,  
               c.TIME_ZONE_ID  
        from qrtz_job_details as j  
                 join qrtz_triggers as t  
                 join qrtz_cron_triggers as c  
                      ON j.JOB_NAME = t.JOB_NAME  
                          and t.TRIGGER_NAME = c.TRIGGER_NAME  
                          and t.TRIGGER_GROUP = c.TRIGGER_GROUP  
        where j.SCHED_NAME = 'scheduler-spring-mvc'  
    </select>  
  
    <select id="findSchedulerJobByJobName" parameterType="string" resultMap="SchedulerJobMap">  
        select id,  
        <include refid="sql_scheduler_job_info"/>  
        from scheduler_job_info  
        where job_name = #{jobName}  
        and scheduler_name = 'scheduler-spring-mvc'  
    </select>  
  
    <select id="findAll" resultMap="SchedulerJobMap">  
        select id,  
        <include refid="sql_scheduler_job_info"/>  
        from scheduler_job_info where scheduler_name = 'scheduler-spring-mvc'  
    </select>  
  
    <delete id="deleteByJobName" parameterType="string">  
        delete  
        from scheduler_job_info  
        where job_name = #{jobName}  
          and scheduler_name = 'scheduler-spring-mvc'  
    </delete>  
  
    <insert id="save" parameterType="SchedulerJob">  
        <!-- @formatter:off -->  
        <if test="id == null">  
            insert into scheduler_job_info(<include refid="sql_scheduler_job_info"/>)  
            values(#{cronExpression}, #{cronJob}, #{jobClass}, #{jobGroup}, #{jobName}, #{repeatTime}, 'scheduler-spring-mvc')  
        </if>  
        <!-- @formatter:on -->  
        <if test="id != null">  
            update scheduler_job_info  
            <set>  
                <if test="cronExpression!=null">cron_expression = #{cronExpression}</if>  
                <if test="cronJob!=null">cron_job = #{cronJob}</if>  
                <if test="jobClass!=null">job_class = #{jobClass}</if>  
                <if test="jobGroup!=null">job_group = #{jobGroup}</if>  
                <if test="jobName!=null">job_name = #{jobName}</if>  
                <if test="repeatTime!=null">repeat_time = #{repeatTime}</if>  
                <if test="scheduler_name!=null">scheduler_name = #{schedulerName}</if>  
            </set>  
            <where>  
                id = #{id}  
            </where>  
        </if>  
    </insert>  
</mapper>  

10.网页Vue+ElementUI实现

网页基本没什么变化,参考上篇文章《Spring Boot Quartz 动态任务实现方式》

11. 引用

https://blog.csdn.net/u012907049/article/details/73801122
https://blog.csdn.net/yangshangwei/article/details/78539433#withmisfirehandlinginstructiondonothing
http://blog.btmatthews.com/?p=40#comment-33797
https://www.baeldung.com/spring-quartz-schedule

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值