Quartz任务调度(一)

一、引入依赖

<!--quartz-->
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- druid的starter -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.9</version>
</dependency>
<!-- mybatis的starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>
<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!--log4j-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.0.6</version>
</dependency>

二、配置文件application.properties

## database
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://192.168.0.246:3306/hrz_task_center?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=1111

# Druid数据源配置
spring.datasource.driverClassName=com.mysql.jdbc.Driver
# 连接池配置
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.exceptionSorter=true
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.filters=stat,wall,log4j
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
spring.datasource.useGlobalDataSourceStat=true
# Druid监控器配置
#默认为stat,即开启sql监控。这里加了个wall,表示同时开启sql防火墙
spring.datasource.druid.filters=stat,wall,log4j
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
spring.datasource.druid.web-stat-filter.session-stat-enable=true
spring.datasource.druid.web-stat-filter.session-stat-max-count=10
spring.datasource.druid.web-stat-filter.principal-session-name=
# Druid管理页配置
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.reset-enable=true
#监控页面登录用户名
spring.datasource.druid.stat-view-servlet.login-username=admin
#监控页面登录密码
spring.datasource.druid.stat-view-servlet.login-password=admin
# Druid AOP 配置
# Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔
spring.datasource.druid.aop-patterns=com.hrz.task.service.*
spring.aop.proxy-target-class=true

#连接数据库
#mybaties配置
#扫描实力类的所在包
mybatis-plus.type-aliases-package=com.hrz.task.entity
#通用mapper的所在接口名称 不只是包名
mybatis-plus.mapper-locations=classpath*:/mappers/**/*.xml
#在格式:logging.level.Mapper类的包=debug  会在控制台打印出sql语句
logging.level.com.hrz.third.mapper=debug
mybatis-plus.global-config.id-type=0
mybatis-plus.global-config.field-strategy=1
mybatis-plus.global-config.db-column-underline=true
mybatis-plus.global-config.refresh-mapper=true
mybatis-plus.global-config.db-config.table-underline=true
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.configuration.cache-enabled=false
mybatis-plus.configuration.call-setters-on-nulls=true
mybatis-plus.global-config.db-config.table-prefix=task_

三、配置类ScheduleConfig

@Configuration
public class ScheduleConfig {
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        //配置数据源
        factory.setDataSource(dataSource);
        //quartz参数
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "HrzScheduler");
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        //线程池配置
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "20");
        prop.put("org.quartz.threadPool.threadPriority", "5");
        //JobStore配置
        prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
        //集群配置
        prop.put("org.quartz.jobStore.isClustered", "true");
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
        //前缀
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
        prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");

        //PostgreSQL数据库,需要打开此注释
        //prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");
        factory.setQuartzProperties(prop);
        //调度名称
        factory.setSchedulerName("HrzScheduler");
        //延时启动
        factory.setStartupDelay(30);
        //可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
        factory.setOverwriteExistingJobs(true);
        //设置自动启动,默认为true
        factory.setAutoStartup(true);
        return factory;
    }
}

四、工具类

1、ScheduleJob
@Slf4j
public class ScheduleJob extends QuartzJobBean {
    private ExecutorService service = Executors.newSingleThreadExecutor();

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        JobKey key = context.getJobDetail().getKey();
        log.info( "任务key:"+key);
        String json = context.getMergedJobDataMap().getString(ScheduleJobs.JOB_PARAM_KEY);
        //将获取的对象序列化的json 转化为实体类对象
        ScheduleJobs scheduleJobs = JSON.parseObject(json, ScheduleJobs.class);
        //获取spring bean
        ScheduleJobLogService scheduleJobLogService = (ScheduleJobLogService) SpringUtils.getBean("scheduleJobLogService");

        //数据库保存执行记录
        ScheduleJobLog scheduleJobLog = new ScheduleJobLog();

        scheduleJobLog.setJobId(scheduleJobs.getId());
        scheduleJobLog.setBeanName(scheduleJobs.getBeanName());
        scheduleJobLog.setMethodName(scheduleJobs.getMethodName());
        scheduleJobLog.setParams(scheduleJobs.getParams());
        scheduleJobLog.setCreateTime(new Date());

        //任务开始时间
        long startTime = System.currentTimeMillis();

        try {
            //执行任务
            log.info("任务准备执行,任务ID:" + scheduleJobs.getId());
            ScheduleRunnable task = new ScheduleRunnable(scheduleJobs.getBeanName(),
                    scheduleJobs.getMethodName(), scheduleJobs.getParams());
            //接收多线程的执行结果
            Future<?> future = service.submit(task);
            future.get();
            //任务执行总时长
            long times = System.currentTimeMillis() - startTime;
            scheduleJobLog.setTimes((int)times);
            //任务状态    0:成功    1:失败
            scheduleJobLog.setStatus(0);
            log.info("任务执行完毕,任务ID:" + scheduleJobs.getId() + "  总共耗时:" + times + "毫秒");
        } catch (Exception e) {
            log.error("任务执行失败,任务ID:" + scheduleJobs.getId(), e);

            //任务执行总时长
            long times = System.currentTimeMillis() - startTime;
            scheduleJobLog.setTimes((int)times);

            //任务状态    0:成功    1:失败
            scheduleJobLog.setStatus(1);
            scheduleJobLog.setError(StringUtils.substring(e.toString(), 0, 2000));
        }finally {
            //插入日志
            scheduleJobLogService.saveLog(scheduleJobLog);
        }
    }
}
2、ScheduleUtils
@Slf4j
public class ScheduleUtils {
    private final static String JOB_NAME = "TASK_";

    /**
     * 获取触发器key
     */
    public static TriggerKey getTriggerKey(String jobId) {
        return TriggerKey.triggerKey(JOB_NAME + jobId);
    }

    /**
     * 获取jobKey
     */
    public static JobKey getJobKey(String jobId) {
        return JobKey.jobKey(JOB_NAME + jobId);
    }

    /**
     * 获取表达式触发器
     */
    public static CronTrigger getCronTrigger(Scheduler scheduler, String jobId) throws HrzException {
        try {
            log.info("获取表达式触发器:" + jobId + ":" + scheduler);
            return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));
        } catch (SchedulerException e) {
            log.error("获取定时任务CronTrigger出现异常" + e);
            throw new HrzException("获取定时任务CronTrigger出现异常", 500);
        }
    }

    /**
     * 创建定时任务
     */
    public static void createScheduleJob(Scheduler scheduler, ScheduleJobs scheduleJobs) throws HrzException {
        try {
            //构建job信息
            JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJobs.getId())).build();

            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJobs.getCronExpression())
                    .withMisfireHandlingInstructionDoNothing();
            //按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJobs.getId())).withSchedule(scheduleBuilder).build();

            //放入参数,运行时的方法可以获取
            //将对象josn序列化存储到Job的getJobDataMap()方法中,为后续根据获取属性执行对应的类的任务
            jobDetail.getJobDataMap().put(ScheduleJobs.JOB_PARAM_KEY, JSON.toJSONString(scheduleJobs));
            scheduler.scheduleJob(jobDetail, trigger);

            //暂停任务
            if (scheduleJobs.getStatus() == GlobalConstant.ScheduleStatus.PAUSE.getValue()) {
                pauseJob(scheduler, scheduleJobs.getId());
            }
        } catch (SchedulerException e) {
            throw new HrzException("创建定时任务失败" + e, 500);
        }
    }

    /**
     * 更新定时任务
     */
    public static void updateScheduleJob(Scheduler scheduler, ScheduleJobs scheduleJobs) throws HrzException {
        try {
            TriggerKey triggerKey = getTriggerKey(scheduleJobs.getId());

            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJobs.getCronExpression())
                    .withMisfireHandlingInstructionDoNothing();

            CronTrigger trigger = getCronTrigger(scheduler, scheduleJobs.getId());

            //按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

            //将对象放入触发器中
            trigger.getJobDataMap().put(ScheduleJobs.JOB_PARAM_KEY, JSON.toJSONString(scheduleJobs));
            //接新的触发器重新设置job执行
            scheduler.rescheduleJob(triggerKey, trigger);

            //暂停任务
            if (scheduleJobs.getStatus() == GlobalConstant.ScheduleStatus.PAUSE.getValue()) {
                pauseJob(scheduler, scheduleJobs.getId());
            }

        } catch (SchedulerException e) {
            throw new HrzException("更新定时任务失败", 500);
        }
    }

    /**
     * 立即执行任务
     */
    public static void run(Scheduler scheduler, ScheduleJobs scheduleJobs) throws HrzException {
        try {
            log.info("立即执行任务:" + scheduleJobs.getId());
            //参数
            JobDataMap dataMap = new JobDataMap();
            dataMap.put(ScheduleJobs.JOB_PARAM_KEY, JSON.toJSONString(scheduleJobs));
            scheduler.triggerJob(getJobKey(scheduleJobs.getId()), dataMap);
        } catch (SchedulerException e) {
            throw new HrzException("立即执行定时任务失败" + e, 500);
        }
    }

    /**
     * 暂停任务
     */
    public static void pauseJob(Scheduler scheduler, String jobId) throws HrzException {
        try {
            scheduler.pauseJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new HrzException("暂停定时任务失败", 500);
        }
    }

    /**
     * 恢复任务
     */
    public static void resumeJob(Scheduler scheduler, String jobId) throws HrzException {
        try {
            scheduler.resumeJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new HrzException("暂停定时任务失败", 500);
        }
    }

    /**
     * 删除定时任务
     */
    public static void deleteScheduleJob(Scheduler scheduler, String jobId) throws HrzException {
        try {
            scheduler.deleteJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            throw new HrzException("删除定时任务失败", 500);
        }
    }
}
3、
Slf4j
public class ScheduleRunnable implements Runnable {
    private Object target;
    private Method method;
    private String params;

    public ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException {
        this.target = SpringUtils.getBean(beanName);
        this.params = params;

        if(StringUtils.isNotBlank(params)){
            this.method = target.getClass().getDeclaredMethod(methodName, String.class);
        }else{
            this.method = target.getClass().getDeclaredMethod(methodName);
        }
    }

    @Override
    public void run() {
        try {
            ReflectionUtils.makeAccessible(method);
            if(StringUtils.isNotBlank(params)){
                method.invoke(target, params);
            }else{
                method.invoke(target);
            }
        }catch (Exception e) {
            log.error("执行定时任务失败", 500);
        }
    }
}
3、ScheduleRunnable
@Slf4j
public class ScheduleRunnable implements Runnable {
    private Object target;
    private Method method;
    private String params;

    public ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException {
        this.target = SpringUtils.getBean(beanName);
        this.params = params;

        if(StringUtils.isNotBlank(params)){
            this.method = target.getClass().getDeclaredMethod(methodName, String.class);
        }else{
            this.method = target.getClass().getDeclaredMethod(methodName);
        }
    }

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

推荐阅读更多精彩内容

  • Quartz是一个完全由java编写的功能丰富的开源作业调度库,可以集成到几乎任何Java应用程序中,小到独立应用...
    ProteanBear阅读 7,042评论 3 24
  • SpringMVC原理分析 Spring Boot学习 5、Hello World探究 1、POM文件 1、父项目...
    jack_jerry阅读 1,277评论 0 1
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • SpringBoot基础 学习目标: 能够理解Spring的优缺点 能够理解SpringBoot的特点 能够理解S...
    dwwl阅读 5,445评论 4 81
  • 莎士比亚的智慧:论朋友 投资就是投人,诚信比能力更重要;莎士比亚的人生智慧: 凡事三思而行,不要想到什么就说什么;...
    肖彬_用户增长_数据分析阅读 293评论 0 0