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);
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

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