项目实战中的Quartz定时器

前言

这里是我真实项目中使用的,个人认为是比较全一点的,如果不足,还希望多多包涵或者补充

接2文章的基础上又简单更改了下,变得更加规范

1、依赖


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

        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.1</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.5</version>
        </dependency>

2、添加quartz.properties配置




#默认或是自己改名字都行
org.quartz.scheduler.instanceName=HeaelrJeanQuartzScheduler
#如果使用集群,instanceId必须唯一,设置成AUTO
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.jobFactory.class=org.quartz.simpl.SimpleJobFactory
org.quartz.scheduler.autoStartup=true
org.quartz.scheduler.skipUpdateCheck=true



org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=30
org.quartz.threadPool.threadPriority=5



#是否使用集群(如果项目只部署到 一台服务器,就不用了)
rg.quartz.jobStore.isClustered=true
org.quartz.jobStore.clusterCheckinInterval=20000
#存储方式使用JobStoreTX,也就是数据库
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#misfireThreshold是用来设置调度引擎对触发器超时的忍耐时间,简单来说 假设misfireThreshold=6000(单位毫秒)。
#那么它的意思说当一个触发器超时时间如果大于misfireThreshold的值 就认为这个触发器真正的超时(也叫Misfires)。
org.quartz.jobStore.misfireThreshold=60000
#存储的JobDataMaps是否都为String类型
org.quartz.jobStore.useProperties=true

# 数据源 我们自己配置
org.quartz.jobStore.dataSource=healerjean




# 数据源使用quartz配置
#org.quartz.jobStore.tablePrefix = QRTZ_
#org.quartz.jobStore.dataSource = myDS
#
#
##配置数据源
#数据库中quartz表的表名前缀
#
#org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
#org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/com_hlj_quartz?characterEncoding=utf-8
#org.quartz.dataSource.myDS.user = root
#org.quartz.dataSource.myDS.password = 123456
#org.quartz.dataSource.myDS.maxConnections = 5


4、sprinboot配置文件

4.1、主配置文件

spring.application.name=quartz
spring.profiles.active=demo


#freemarker
spring.freemarker.charset=UTF-8
spring.freemarker.cache=true
spring.freemarker.enabled=true
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-session-attributes=true
spring.freemarker.expose-spring-macro-helpers=true
spring.freemarker.request-context-attribute=request
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.suffix=



#aop
spring.aop.auto=true
spring.aop.proxy-target-class=true

#spring data jpa properties
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.database=MYSQL
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.data.jpa.repositories.enabled=true




#log level
logging.level.root=info
logging.level.org.springframework=ERROR
logging.level.org.mybatis=ERROR
logging.level.org.mybatis.example.BlogMapper=debug



#logging.level.com.duodian.youhui.dao.mybatis.coupon=debug

#static
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
#
#
##出现错误时, 直接抛出异常 路径访问错误也抛出异常
#spring.mvc.throw-exception-if-no-handler-found=true
#spring.resources.add-mappings=false



4.2、启动配置文件


server.port=8080


########################################################
###datasource  mysql
########################################################
spring.datasource.url=jdbc:mysql://localhost:3306/com_hlj_demo?useUnicode=true&allowMultiQueries=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driverClassName=com.mysql.jdbc.Driver
# 初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000



#datasource healerjean 这个我们自己配置
healerjean.datasource.url=jdbc:mysql://localhost:3306/com_hlj_quartz?characterEncoding=utf-8
healerjean.datasource.username=root
healerjean.datasource.password=123456
healerjean.datasource.driver-class-name=com.mysql.jdbc.Driver



logging.config=classpath:logback.xml

3、数据表采用文章2中的数据库表,这里就不粘贴了

4、定时器配置,使用配置文件以及数据源

4.1、单独为定时任务创建了数据源(其实和主数据源是一起的)

4.2、添加了定时器监听入口

package com.hlj;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

import javax.sql.DataSource;

/**
 * 类描述:
 * 创建人: HealerJean
 */
@Configuration
public class QuartzConfig {

    @Value("${healerjean.datasource.url}")
    private String healerjeanUrl;
    @Value("${healerjean.datasource.username}")
    private String healerjeanname;
    @Value("${healerjean.datasource.password}")
    private String healerjeanPassword;

    @Bean
    public SpringBeanJobFactory jobFactory (){
        return new SpringBeanJobFactory();
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean (SpringBeanJobFactory jobFactory) {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setDataSource(createAdmore());
        schedulerFactoryBean.setConfigLocation(new ClassPathResource("quartz.properties"));
        schedulerFactoryBean.setJobFactory(jobFactory);
        schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true);
        schedulerFactoryBean.setGlobalJobListeners(new com.duodian.admore.quartz.core.event.HealerJeanJobListener());
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        return schedulerFactoryBean;
    }

    /**
     * 单独为定时任务创建一个单独的datasource
     */
    private DataSource createAdmore(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(healerjeanUrl);
        druidDataSource.setUsername(healerjeanname);
        druidDataSource.setPassword(healerjeanPassword);
        druidDataSource.setMaxActive(10);
        druidDataSource.setInitialSize(5);
        druidDataSource.setTestWhileIdle(true);
        druidDataSource.setMaxWait(2500);
        druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
        druidDataSource.setMinEvictableIdleTimeMillis(300000);

        return druidDataSource;
    }

}

5、定时器监听器

在定时器 执行之前,任务取消(一般情况下不执行,博主没遇到过),执行完成(有异常执行完和无异常执行完)

其实有用的,也就是第3个

package com.duodian.admore.quartz.core.event;

import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

/**
 * 类描述:
 * 创建人: HealerJean
 */
@Slf4j
public class HealerJeanJobListener implements JobListener {

    private static final String LISTENER_NAME = "healerjean.job.listener";

    @Override
    public String getName() {
        return LISTENER_NAME;
    }

    /**
     * 任务执行之前执行
     */
    @Override
    public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
        log.info(jobExecutionContext.getJobDetail().getKey() + "  to be execute");
    }

    /**
     *
     * 任务取消
     * 这个方法正常情况下不执行,但是如果当TriggerListener中的vetoJobExecution方法返回true时,那么执行这个方法.
     * 需要注意的是 如果方法(2)执行 那么(1),(3)这个俩个方法不会执行,因为任务被终止了嘛.
     */
    @Override
    public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
        log.info(jobExecutionContext.getJobDetail().getKey() + " vetoed");
    }

    /**
     任务执行完成后执行,jobException如果它不为空则说明任务在执行过程中出现了异常
     */
    @Override
    public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
        if (e != null){
            String jobName = jobExecutionContext.getJobDetail().getKey().getName();
            String errMsg = e.getMessage();
            log.error(jobExecutionContext.getJobDetail().getKey() + "  execute failure");
            log.error(e.getMessage(),e);
        } else {
            log.info(jobExecutionContext.getJobDetail().getKey() + "  execute success");
        }
    }
}


6、定时器任务调度(最核心的)

package com.hlj.quartz.core.schedule;

import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Set;

/**
 *  HealerJean
 * quartz job调度核心类
 */
@Component
public class HealerJeanScheduler {

    private Logger logger = LoggerFactory.getLogger(HealerJeanScheduler.class);

    private final static String TRIGGER_PERFIX = "t_";

    @Resource
    private Scheduler scheduler;

    public <T extends Job> void startJob(String jobId, String cron, Class<T> t, String jobDesc) throws SchedulerException {
        JobDetail job = JobBuilder.newJob(t).withIdentity(jobId).withDescription(jobDesc).build();
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(TRIGGER_PERFIX + jobId).withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
        scheduler.scheduleJob(job, trigger);
        logger.info(jobId + " start at " + new Date());
    }

    public void resetJob(String jobId,String corn) {
        try {
            if (scheduler.checkExists(new JobKey(jobId))){
                JobDetail jobDetail = scheduler.getJobDetail(new JobKey(jobId));
                logger.info("name:"+jobDetail.getKey().getName());
                logger.info("group:"+jobDetail.getKey().getGroup());
                TriggerKey triggerKey = TriggerKey.triggerKey(TRIGGER_PERFIX+jobDetail.getKey().getName(), jobDetail.getKey().getGroup());
                //获取trigger,即在spring配置文件中定义的 bean id="myTrigger"
                CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
                //表达式调度构建器
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(corn);
                //按新的cronExpression表达式重新构建trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
                //按新的trigger重新设置job执行
                scheduler.rescheduleJob(triggerKey,trigger);
                logger.info(jobId + " reset at " + new Date());
            }
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage(),e);
        }
    }


    /****
     * 暂停任务
     */
    public void pauseJob(String jobId) {
        try {
            if (scheduler.checkExists(new JobKey(jobId))){
                scheduler.pauseJob(new JobKey(jobId));
                logger.info(jobId + " pause at " + new Date());
            }
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage(),e);
        }
    }

    /**
     * 删除任务
     * @param jobId
     */
    public void deleteJob(String jobId) {
        try {
            if (scheduler.checkExists(new JobKey(jobId))){
                scheduler.deleteJob(new JobKey(jobId));
                logger.info(jobId + " delete at " + new Date());
            }
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage(),e);
        }
    }

    /**
     * 暂停重启
     * @param jobId
     */
    public void resumeJob(String jobId) {
        try {
            if (scheduler.checkExists(new JobKey(jobId))){
                scheduler.resumeJob(new JobKey(jobId));
                logger.info(jobId + " resume at " + new Date());
            }
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage(),e);
        }
    }

    /**
     * 获取所有的任务 的 JobKey
     * @return
     */
    public Set<JobKey> currentJobs(){
        try {
            GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
            return scheduler.getJobKeys(matcher);
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage(),e);
        }
    }

    /**
     * 获取任务详情 JobDetail
     * @param jobKey
     * @return
     */
    public JobDetail getJobDetail(JobKey jobKey){
        try {
            return scheduler.getJobDetail(jobKey);
        } catch (SchedulerException e) {
            throw new RuntimeException();
        }
    }

    /**
     * 获取触发器 Trigger
     * @param jobKey
     * @return
     */
    public Trigger getJobTrigger(JobKey jobKey){
        try {
            return scheduler.getTrigger(new TriggerKey(TRIGGER_PERFIX + jobKey.getName()));
        } catch (SchedulerException e) {
            throw new RuntimeException();
        }
    }

    /**
     * 获取触发器 任务的执行状态
     * @param jobKey
     * @return
     */
    public Trigger.TriggerState getTriggerState(JobKey jobKey){
        try {
            return scheduler.getTriggerState(new TriggerKey(TRIGGER_PERFIX + jobKey.getName()));
        } catch (SchedulerException e) {
            throw new RuntimeException();
        }
    }

}


7、添加一个测试任务


package com.hlj.quartz.job;

import org.quartz.*;
import org.springframework.stereotype.Component;

/**
 * @Description
 * @Author HealerJean
 * @Date 2018/3/23  下午4:19.
 */

@Component
public class MyTask implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        try {

            //可以通过context拿到执行当前任务的quartz中的很多信息,如当前是哪个trigger在执行该任务
            CronTrigger trigger = (CronTrigger) context.getTrigger();

            TriggerKey triggerKey =trigger.getKey() ;
            String corn = trigger.getCronExpression();
            System.out.println("任务规则 :"+corn);

            System.out.println("triggerKey  Name:"+triggerKey.getName()); //t_jobId
            System.out.println("triggerKey group:"+triggerKey.getGroup()); //t_jobId

            System.out.println("----------------------");
            JobKey jobKey =   context.getJobDetail().getKey() ;
            System.out.println("jobKey getName"+jobKey.getName()); //jobId
            System.out.println("jobKey getGroup"+jobKey.getGroup()); //DEFAULT

            Scheduler scheduler =  context.getScheduler();
            System.out.println("scheduler.getCalendarNames()"+scheduler.getCalendarNames());

            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            System.out.println(jobDetail.getClass().toString());


        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("执行任务中");
    }
}

8、测试


package com.hlj.web.quartz;

import com.hlj.data.general.ResponseBean;
import com.hlj.quartz.core.schedule.HealerJeanScheduler;
import com.hlj.web.quartz.vo.JobDetailBean;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * 类描述:
 * 创建人: HealerJean
 */
@Controller
@RequestMapping("quartz")
public class QuartzController {

    private Logger logger = LoggerFactory.getLogger(QuartzController.class);

    @Resource
    private HealerJeanScheduler scheduler;

    @RequestMapping("jobList")
    @ResponseBody
    public List<JobDetailBean> login(){

        Set<JobKey> jobKeySet = scheduler.currentJobs();
        List<JobDetailBean> jobDetailList = new ArrayList<>();

        for (JobKey jobKey : jobKeySet){
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            Trigger trigger = scheduler.getJobTrigger(jobKey);

            JobDetailBean detailBean = new JobDetailBean();
            detailBean.setJobId(jobKey.getName());
            detailBean.setJobDesc(jobDetail.getDescription());
            detailBean.setCron(((CronTrigger)trigger).getCronExpression());
            detailBean.setJobClass(jobDetail.getJobClass().toString());
            detailBean.setPreviousFireTime(trigger.getPreviousFireTime());
            detailBean.setNextFireTime(trigger.getNextFireTime());

            Trigger.TriggerState triggerState = scheduler.getTriggerState(jobKey);
            detailBean.setJobStatus(triggerState.name());

            jobDetailList.add(detailBean);
        }
        return jobDetailList;
    }


    @RequestMapping("create")
    @ResponseBody
    public ResponseBean create(String jobId, String jobCron, String jobClass, String jobDesc){
        try {
            scheduler.startJob(jobId,jobCron,Class.forName(jobClass).asSubclass(Job.class),jobDesc);
            return ResponseBean.buildSuccess();
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
            return ResponseBean.buildFailure(e.getMessage());
        }
    }

    @RequestMapping("update")
    @ResponseBody
    public ResponseBean update(String jobId,String jobCron,String jobClass,String jobDesc){
        try {
            scheduler.resetJob(jobId,jobCron);
            return ResponseBean.buildSuccess();
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
            return ResponseBean.buildFailure(e.getMessage());
        }
    }

    @RequestMapping("delete")
    @ResponseBody
    public ResponseBean delete(String jobId){
        try {
            scheduler.deleteJob(jobId);
            return ResponseBean.buildSuccess();
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
            return ResponseBean.buildFailure(e.getMessage());
        }
    }

    @RequestMapping("pause")
    @ResponseBody
    public ResponseBean pause(String jobId){
        try {
            scheduler.pauseJob(jobId);
            return ResponseBean.buildSuccess();
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
            return ResponseBean.buildFailure(e.getMessage());
        }
    }

    @RequestMapping("resume")
    @ResponseBody
    public ResponseBean start(String jobId){
        try {
            scheduler.resumeJob(jobId);
            return ResponseBean.buildSuccess();
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
            return ResponseBean.buildFailure(e.getMessage());
        }
    }

}



2019-02-20 17:57:25.830 [http-nio-8080-exec-6] INFO  c.h.q.c.schedule.HealerJeanScheduler - name:test
2019-02-20 17:57:25.830 [http-nio-8080-exec-6] INFO  c.h.q.c.schedule.HealerJeanScheduler - group:DEFAULT
2019-02-20 17:57:25.853 [http-nio-8080-exec-6] INFO  c.h.q.c.schedule.HealerJeanScheduler - test reset at Wed Feb 20 17:57:25 CST 2019
2019-02-20 17:58:00.019 [HeaelrJeanQuartzScheduler_Worker-1] INFO  c.d.a.q.c.e.HealerJeanJobListener - DEFAULT.test  to be execute
任务规则 :0 58 17 * * ?
triggerKey  Name:t_test
triggerKey group:DEFAULT
----------------------
jobKey getNametest
jobKey getGroupDEFAULT
scheduler.getCalendarNames()[]
class com.hlj.quartz.job.MyTask
执行任务中
2019-02-20 17:58:00.029 [HeaelrJeanQuartzScheduler_Worker-1] INFO  c.d.a.q.c.e.HealerJeanJobListener - DEFAULT.test  execute success



源码下载

WX20190220-175746@2x




感兴趣的,欢迎添加博主微信,

哈,博主很乐意和各路好友交流,如果满意,请打赏博主任意金额,感兴趣的在微信转账的时候,备注您的微信或者其他联系方式。添加博主微信哦。


请下方留言吧。可与博主自由讨论哦

微信 微信公众号 支付宝
微信 微信公众号 支付宝
posted @ 2019-02-21 18:30  HealerJean  阅读(143)  评论(0编辑  收藏  举报