Spring Boot整合Quartz

一、前言

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:

1.持久性作业 - 就是保持调度定时的状态

2.作业管理 - 对调度作业进行有效的管理

二、引入依赖

xml 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

三、Task或Job实现

首先我们需要定义实现一个定时功能的接口,我们可以称之为Task(或Job),如定时发送邮件的task(Job),重启机器的task(Job),优惠券到期发送短信提醒的task(Job),在springboot中只需要继承QuartzJobBean并重写executeInternal方法。

java 复制代码
public class TestQuartz extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(sdf.format(new Date()) + "\t\tquartz task  app is running...... ");
    }
}

四、添加配置类

java 复制代码
@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail teatQuartzDetail() {
        return JobBuilder.newJob(TestQuartz.class).storeDurably().build();
    }
    
    @Bean
    public Trigger testQuartzTrigger() {
        SimpleScheduleBuilder scheduleBuilder =
                SimpleScheduleBuilder
                        .simpleSchedule()
                        .withIntervalInSeconds(10) // 每隔两秒实行一次
                        .repeatForever();
        return TriggerBuilder.newTrigger().startAt(new Date())
                .forJob(teatQuartzDetail())
                .withSchedule(scheduleBuilder).build();
    }

    @Bean
    public Trigger testQuartzTrigger2() {
        Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(teatQuartzDetail())
                .withIdentity("myTrigger1","myTriggerGroup1")
                .usingJobData("job_trigger_param","job_trigger_param1")
                .startNow()
                //.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
                .withSchedule(CronScheduleBuilder.cronSchedule("0/8 * * * * ? 2020"))
                .build()
        return trigger;
    }

}

五、测试

运行项目,日志打印如下:

2020-10-19 18:27:24 quartz task app is running......

2020-10-19 18:27:28 quartz task app is running......

2020-10-19 18:27:32 quartz task app is running......

2020-10-19 18:27:38 quartz task app is running......

2020-10-19 18:27:40 quartz task app is running......

2020-10-19 18:27:48 quartz task app is running......

2020-10-19 18:27:48 quartz task app is running......

我们发现执行频率一个是8秒一次,一个是10秒一次

六、quartz持久化

6.1 背景

最近在做项目,项目中有个需求:需要使用定时任务,这个定时任务需要即时生效。

Quartz提供两种基本作业存储类型:

1.RAMJobStore :RAM也就是内存,默认情况下Quartz会将任务调度存在内存中,这种方式性能是最好的,因为内存的速度是最快的。不好的地方就是数据缺乏持久性,但程序崩溃或者重新发布的时候,所有运行信息都会丢失

2.JDBC作业存储:存到数据库之后,可以做单点也可以做集群,当任务多了之后,可以统一进行管理。关闭或者重启服务器,运行的信息都不会丢失。缺点就是运行速度快慢取决于连接数据库的快慢。

只有持久化以后,这样定时任务就可以做集群,任务可以进行管理,随时停止、暂停、修改任务。

6.2 Quartz初始化

6.2.1 脚本下载

从官网下载的quartz文件。如下所示:

下载后解压,在目录src\org\quartz\impl\jdbcjobstore中有对应的数据库初始化脚本,这里我们选择mysql数据库,那么需要导入tables_mysql_innodb.sql。

6.2.2 创建数据库
sql 复制代码
    create database quartzdb default character set utf8 collate utf8_general_ci;
    create user 'quartz'@'%' identified by 'quartz#123';
    create user 'quartz'@'localhost' identified by 'quartz#123';
    grant all privileges on quartzdb.* to 'quartz'@'%' identified by 'quartz#123';
    grant all privileges on quartzdb.* to 'quartz'@'localhost' identified by 'quartz#123';
    flush  privileges;
6.2.3 导入数据

导入上面脚本后,具体表信息如下:

表解释如下:

表名 含义
qrtz_blob_triggers 以Blob 类型存储的触发器。
qrtz_calendars 存放日历信息, quartz可配置一个日历来指定一个时间范围。
qrtz_cron_triggers 存放cron类型的触发器。
qrtz_fired_triggers 存放已触发的触发器。
qrtz_job_details 存放一个jobDetail信息。
qrtz_locks 存储程序的悲观锁的信息(假如使用了悲观锁)。
qrtz_paused_trigger_graps 存放暂停掉的触发器。
qrtz_scheduler_state 调度器状态。
qrtz_simple_triggers 简单触发器的信息。
qrtz_simprop_listeners 触发器监听器。
qrtz_triggers 触发器的基本信息。

6.3 项目配置

6.3.1 依赖引入
xml 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.22</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
6.3.2 application.yml配置
yml 复制代码
server:
  port: 10005
spring:
  datasource:
    druid:
      # 数据库访问配置, 使用druid数据源
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/quartzdb?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&allowMultiQueries=true&useSSL=false
      username: quartz
      password: quartz#123
      # 连接池配置
      initial-size: 5
      min-idle: 5
      max-active: 20
      # 连接等待超时时间
      max-wait: 30000
      # 配置检测可以关闭的空闲连接间隔时间
      time-between-eviction-runs-millis: 60000
      # 配置连接在池中的最小生存时间
      min-evictable-idle-time-millis: 300000
      validation-query: select '1' from dual
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开PSCache,并且指定每个连接上PSCache的大小
      pool-prepared-statements: true
      max-open-prepared-statements: 20
      max-pool-prepared-statement-per-connection-size: 20
      # 配置监控统计拦截的filters, 去掉后监控界面sql无法统计, 'wall'用于防火墙
      filters: stat,wall
      # Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔
      aop-patterns: com.wno704.boot.service.*
      # WebStatFilter配置
      web-stat-filter:
        enabled: true
        # 添加过滤规则
        url-pattern: /*
        # 忽略过滤的格式
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
      # StatViewServlet配置
      stat-view-servlet:
        enabled: true
        # 访问路径为/druid时,跳转到StatViewServlet
        url-pattern: /druid/*
        # 是否能够重置数据
        reset-enable: false
        # 需要账号密码才能访问控制台
        login-username: druid
        login-password: druid123
        # IP白名单
        # allow: 127.0.0.1
        # IP黑名单(共同存在时,deny优先于allow)
        # deny: 192.168.1.218
      # 配置StatFilter
      filter:
        stat:
          log-slow-sql: true
6.3.3 quartz.properties配置
xml 复制代码
org.quartz.scheduler.instanceName=MyScheduler
#ID设置为自动获取 每一个必须不同 (所有调度器实例中是唯一的)
org.quartz.scheduler.instanceId=AUTO
#指定调度程序的主线程是否应该是守护线程
org.quartz.scheduler.makeSchedulerThreadDaemon=true
#ThreadPool实现的类名
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#ThreadPool配置线程守护进程
org.quartz.threadPool.makeThreadsDaemons=true
#线程数量
org.quartz.threadPool.threadCount=20
#线程优先级
org.quartz.threadPool.threadPriority=5
#数据保存方式为持久化
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#StdJDBCDelegate说明支持集群
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#quartz内部表的前缀
org.quartz.jobStore.tablePrefix=QRTZ_
#是否加入集群
org.quartz.jobStore.isClustered=true
#容许的最大作业延长时间
org.quartz.jobStore.misfireThreshold=25000
6.3.4 quartz配置类

ScheduleConfig.java

java 复制代码
@Configuration
public class ScheduleConfig {

    @Bean
    public ThreadPoolTaskExecutor scheduleJobExecutorService(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(20);
        executor.setKeepAliveSeconds(30);
        executor.setThreadNamePrefix("fb-Job-Thread");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);
        factory.setQuartzProperties(quartzProperties());
        factory.setSchedulerName("FB_Quartz_Scheduler");
        // 延时启动
        factory.setStartupDelay(1);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        // 启动时更新己存在的 Job
        factory.setOverwriteExistingJobs(true);
        // 设置自动启动,默认为 true
        factory.setAutoStartup(true);
        return factory;
    }
    
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

}

6.4 任务自定义配置与执行记录

6.4.1 定时任务配置表与记录表
sql 复制代码
DROP TABLE IF EXISTS QRTZ_JOB;
DROP TABLE IF EXISTS QRTZ_JOB_log;

CREATE TABLE `QRTZ_JOB`  (
  `JOB_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务id',
  `BEAN_NAME` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'spring bean名称',
  `METHOD_NAME` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '方法名',
  `PARAMS` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '参数',
  `CRON_EXPRESSION` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'cron表达式',
  `START_TIME` datetime(0) NOT NULL DEFAULT NOW() COMMENT '开始执行时间',
  `END_TIME` datetime(0) NOT NULL DEFAULT NOW() COMMENT '结束执行时间',
  `STATUS` char(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务状态  0:正常  1:暂停 2:删除',
  `UPDATE_STATUS` char(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '加载状态  0:未加载  1:已加载',
  `REMARK` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
  `CREATE_TIME` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`JOB_ID`),
  KEY `t_job_create_time` (`CREATE_TIME`)
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '定时任务表' ROW_FORMAT = Dynamic;


-- ----------------------------
-- Records of t_job
-- ----------------------------
INSERT INTO `QRTZ_JOB` VALUES (1, 'loadUpdateTask', 'loadUpdateJob', NULL, '0/30 * * * * ?','2018-03-20 21:55:14', '3000-01-01 00:00:00', '0', '0', '更新 task自动读取', now());

-- ----------------------------
-- Table structure for t_job_log
-- ----------------------------
CREATE TABLE `QRTZ_JOB_LOG`  (
  `LOG_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务日志id',
  `JOB_ID` bigint(20) NOT NULL COMMENT '任务id',
  `BEAN_NAME` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'spring bean名称',
  `METHOD_NAME` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '方法名',
  `PARAMS` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '参数',
  `STATUS` char(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务状态    0:成功    1:失败',
  `ERROR` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '失败信息',
  `TIMES` decimal(11, 0) NULL DEFAULT NULL COMMENT '耗时(单位:毫秒)',
  `CREATE_TIME` datetime(0) DEFAULT NOW() COMMENT '创建时间',
  PRIMARY KEY (`LOG_ID`),
  KEY `QRTZ_JOB_LOG_create_time` (`CREATE_TIME`)
) ENGINE = MyISAM AUTO_INCREMENT = 0 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '调度日志表' ROW_FORMAT = Dynamic;
6.4.2 配置表与记录表的实体

Job.java

java 复制代码
@Data
public class Job implements Serializable {

	private static final long serialVersionUID = 21312312424L;

    /**
     * 任务调度参数 key
     */
    public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY";

    public enum ScheduleStatus {
        /**
         * 正常
         */
        NORMAL("0"),
        /**
         * 暂停
         */
        PAUSE("1"),
        /**
         * 删除
         */
        DEL("2");
        private String value;
        ScheduleStatus(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }
    }

    private Long jobId;

    private String beanName;

    private String methodName;

    private String params;

    private String cronExpression;

    private String startTime;

    private String endTime;

    private String status;

    private String remark;
    
    private String createTime;

}

JobLog.java

java 复制代码
@Data
public class JobLog implements Serializable {

	private static final long serialVersionUID = 1L;

	/**
     * 任务执行成功
     */
    public static final String JOB_SUCCESS = "0";

    /**
     * 任务执行失败
     */
    public static final String JOB_FAIL = "1";

    private Long logId;

    private Long jobId;

    private String beanName;

    private String methodName;

    private String params;

    private String status;

    private String errorInfo;

    private Long times;

    private Date createTime;

}
6.4.3 配置表与记录表的实体与实现

JobService.java

java 复制代码
public interface JobService {

	public int init();
	
	/**
     * 获取定时任务
     *
     * @param jobId 任务id
     * @return 定时任务
     */
    Job findJob(Long jobId);

    /**
     * 创建定时任务
     *
     * @param job job
     */
    void createJob(Job job);

    /**
     * 更新定时任务
     *
     * @param job 定时任务
     */
    void updateJob(Job job);

    /**
     * 删除定时任务
     *
     * @param jobIds 定时任务id数组
     */
    void deleteJobs(String[] jobIds);

    /**
     * 批量更新定时任务状态
     *
     * @param jobIds 定时任务id
     * @param status 待更新状态
     */
    void updateBatch(String jobIds, String status);

    /**
     * 运行定时任务
     *
     * @param jobIds 定时任务id
     */
    void run(String jobIds);

    /**
     * 暂停定时任务
     *
     * @param jobIds 定时任务id
     */
    void pause(String jobIds);

    /**
     * 恢复定时任务
     *
     * @param jobIds 定时任务id
     */
    void resume(String jobIds);
}

JobServiceImpl.java

java 复制代码
@Service("JobService")
@RequiredArgsConstructor
public class JobServiceImpl implements JobService {

	private  final Scheduler scheduler;

	@Autowired
	private JdbcTemplate jdbcTemplateQuartz;

	@PostConstruct
	@Override
	public int init() {
		String sql = "SELECT T.JOB_ID,T.BEAN_NAME,T.METHOD_NAME,T.PARAMS,T.CRON_EXPRESSION,T.START_TIME,T.END_TIME,T.STATUS,T.REMARK,T.CREATE_TIME FROM QRTZ_JOB T WHERE T.UPDATE_STATUS = 0 ";
		List<Job> scheduleJobList = this.jdbcTemplateQuartz.query(sql, new BeanPropertyRowMapper<Job>(Job.class));
		scheduleJobList.forEach(scheduleJob -> {
            CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getJobId());
            if (cronTrigger == null) {
                ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
            } else {
                ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
            }
            String upSql = "UPDATE QRTZ_JOB T SET T.UPDATE_STATUS = '1' WHERE T.JOB_ID = ? ";
            this.jdbcTemplateQuartz.update(upSql, new Object[] {scheduleJob.getJobId()});
        });
		return scheduleJobList.size();
	}

	@Override
	public Job findJob(Long jobId) {
		// TODO Auto-generated method stub
		String sql = "SELECT T.JOB_ID,T.BEAN_NAME,T.METHOD_NAME,T.PARAMS,T.CRON_EXPRESSION,T.START_TIME,T.END_TIME,T.STATUS,T.REMARK,T.CREATE_TIME FROM QRTZ_JOB T WHERE T.JOB_ID = ? ";
		List<Job> scheduleJobList = this.jdbcTemplateQuartz.query(sql, new Object[] {jobId}, new BeanPropertyRowMapper<Job>(Job.class));
		return scheduleJobList.get(0);
	}

	@Override
	public void createJob(Job job) {
		// TODO Auto-generated method stub
	}

	@Override
	public void updateJob(Job job) {
		// TODO Auto-generated method stub
	}

	@Override
	public void deleteJobs(String[] jobIds) {
		// TODO Auto-generated method stub
	}

	@Override
	public void updateBatch(String jobIds, String status) {
		// TODO Auto-generated method stub
	}

	@Override
	public void run(String jobIds) {
		// TODO Auto-generated method stub
		String[] list = jobIds.split(",");
		Arrays.stream(list).forEach(jobId -> ScheduleUtils.run(scheduler, this.findJob(Long.valueOf(jobId))));
	}

	@Override
	public void pause(String jobIds) {
		// TODO Auto-generated method stub
	}

	@Override
	public void resume(String jobIds) {
		// TODO Auto-generated method stub
	}

}

JobLogService.java

java 复制代码
public interface JobLogService {

    /**
     * 保存定时任务日志
     *
     * @param log 定时任务日志
     */
    void saveJobLog(JobLog log);

}

JobLogServiceImpl.java

java 复制代码
@Service("JobLogService")
public class JobLogServiceImpl implements JobLogService {

	@Autowired
	private JdbcTemplate jdbcTemplateQuartz;

	@Override
	public void saveJobLog(JobLog jobLog) {
		String sql = "INSERT INTO QRTZ_JOB_LOG (JOB_ID,BEAN_NAME,METHOD_NAME,PARAMS,STATUS,ERROR,TIMES) VALUES (?,?,?,?,?,?,?)";
		this.jdbcTemplateQuartz.update(sql,
				new Object[] {jobLog.getJobId(),jobLog.getBeanName(),
						jobLog.getMethodName(),jobLog.getParams(),jobLog.getStatus(),
						jobLog.getErrorInfo(),jobLog.getTimes()
				});
	}
}

6.5 定时任务控制

6.5.1 基础工具类

工具类 SpringContextUtil.java

java 复制代码
@Component
public class SpringContextUtil implements ApplicationContextAware {

	private static ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		SpringContextUtil.applicationContext = applicationContext;
	}

	public static Object getBean(String name) {
		return applicationContext.getBean(name);
	}

	public static <T> T getBean(Class<T> clazz){
		return applicationContext.getBean(clazz);
	}

	public static <T> T getBean(String name, Class<T> requiredType) {
		return applicationContext.getBean(name, requiredType);
	}

	public static boolean containsBean(String name) {
		return applicationContext.containsBean(name);
	}

	public static boolean isSingleton(String name) {
		return applicationContext.isSingleton(name);
	}

	public static Class<?> getType(String name) {
		return applicationContext.getType(name);
	}

}

DateUtil.java

java 复制代码
public class DateUtil {

    public static final String FULL_TIME_SPLIT_PATTERN = "yyyy-MM-dd HH:mm:ss";

    public static Date getFormatDate(String dateStr) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat(FULL_TIME_SPLIT_PATTERN);
        return sdf.parse(dateStr);
    }

}
6.5.2 定时任务执行类

ScheduleRunnable.java

java 复制代码
@Slf4j
public class ScheduleRunnable implements Runnable {

    private Object target;

    private Method method;

    private String params;

    ScheduleRunnable(String beanName, String methodName, String params) throws NoSuchMethodException, SecurityException {
        this.target = SpringContextUtil.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("执行定时任务失败", e);
        }
    }

}
6.5.3 定时任务工具类

ScheduleUtils.java

java 复制代码
@Slf4j
public class ScheduleUtils {

    private static final String JOB_NAME_PREFIX = "TASK_";

    /**
     * 获取触发器 key
     */
    private static TriggerKey getTriggerKey(Long jobId) {
        return TriggerKey.triggerKey(JOB_NAME_PREFIX + jobId);
    }

    /**
     * 获取jobKey
     */
    private static JobKey getJobKey(Long jobId) {
        return JobKey.jobKey(JOB_NAME_PREFIX + jobId);
    }

    /**
     * 获取表达式触发器
     */
    public static CronTrigger getCronTrigger(Scheduler scheduler, Long jobId) {
        try {
            return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));
        } catch (SchedulerException e) {
            log.error("获取Cron表达式失败", e);
        }
        return null;
    }

    /**
     * 创建定时任务
     */
    public static void createScheduleJob(Scheduler scheduler, Job scheduleJob) {
        try {
            // 构建job信息
            JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getJobId()))
                    .build();
            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
                    .withMisfireHandlingInstructionDoNothing();
            // 按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().startAt(DateUtil.getFormatDate(scheduleJob.getStartTime())).endAt(DateUtil.getFormatDate(scheduleJob.getEndTime()))
                    .withIdentity(getTriggerKey(scheduleJob.getJobId()))
                    .withSchedule(scheduleBuilder).build();
            // 放入参数,运行时的方法可以获取
            trigger.getJobDataMap().put(Job.JOB_PARAM_KEY, scheduleJob);
            trigger.getJobDataMap().put("jobId", scheduleJob.getJobId());
            trigger.getJobDataMap().put("beanName", scheduleJob.getBeanName());
            trigger.getJobDataMap().put("methodName", scheduleJob.getMethodName());
            trigger.getJobDataMap().put("params", scheduleJob.getParams());
            scheduler.scheduleJob(jobDetail, trigger);
            // 暂停任务
            if (scheduleJob.getStatus().equals(Job.ScheduleStatus.PAUSE.getValue())) {
                pauseJob(scheduler, scheduleJob.getJobId());
            }else if (scheduleJob.getStatus().equals(Job.ScheduleStatus.DEL.getValue())) {
                deleteScheduleJob(scheduler, scheduleJob.getJobId());
            }
        } catch (SchedulerException | ParseException e) {
            log.error("创建定时任务失败", e);
        }
    }

    /**
     * 更新定时任务
     */
    public static void updateScheduleJob(Scheduler scheduler, Job scheduleJob) {
        try {
            deleteScheduleJob(scheduler, scheduleJob.getJobId());
            // 构建job信息
            JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getJobId()))
                    .build();
            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
                    .withMisfireHandlingInstructionDoNothing();
            // 按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().startAt(DateUtil.getFormatDate(scheduleJob.getStartTime())).endAt(DateUtil.getFormatDate(scheduleJob.getEndTime()))
                    .withIdentity(getTriggerKey(scheduleJob.getJobId()))
                    .withSchedule(scheduleBuilder).build();
            // 放入参数,运行时的方法可以获取
            trigger.getJobDataMap().put(Job.JOB_PARAM_KEY, scheduleJob);
            trigger.getJobDataMap().put("jobId", scheduleJob.getJobId());
            trigger.getJobDataMap().put("beanName", scheduleJob.getBeanName());
            trigger.getJobDataMap().put("methodName", scheduleJob.getMethodName());
            trigger.getJobDataMap().put("params", scheduleJob.getParams())
            scheduler.scheduleJob(jobDetail, trigger);
            // 暂停任务
            if (scheduleJob.getStatus().equals(Job.ScheduleStatus.PAUSE.getValue())) {
                pauseJob(scheduler, scheduleJob.getJobId());
            }else if (scheduleJob.getStatus().equals(Job.ScheduleStatus.DEL.getValue())) {
                deleteScheduleJob(scheduler, scheduleJob.getJobId());
            }
        } catch (SchedulerException | ParseException e) {
            log.error("更新定时任务失败", e);
        }
    }

    /**
     * 立即执行任务
     */
    public static void run(Scheduler scheduler, Job scheduleJob) {
        try {
            // 参数
            JobDataMap dataMap = new JobDataMap();
            dataMap.put(Job.JOB_PARAM_KEY, scheduleJob);
            dataMap.put("jobId", scheduleJob.getJobId());
            dataMap.put("beanName", scheduleJob.getBeanName());
            dataMap.put("methodName", scheduleJob.getMethodName());
            dataMap.put("params", scheduleJob.getParams());
            scheduler.triggerJob(getJobKey(scheduleJob.getJobId()), dataMap);
        } catch (SchedulerException e) {
            log.error("执行定时任务失败", e);
        }
    }

    /**
     * 暂停任务
     */
    public static void pauseJob(Scheduler scheduler, Long jobId) {
        try {
            scheduler.pauseJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            log.error("暂停定时任务失败", e);
        }
    }

    /**
     * 恢复任务
     */
    public static void resumeJob(Scheduler scheduler, Long jobId) {
        try {
            scheduler.resumeJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            log.error("恢复定时任务失败", e);
        }
    }

    /**
     * 删除定时任务
     */
    public static void deleteScheduleJob(Scheduler scheduler, Long jobId) {
        try {
            scheduler.deleteJob(getJobKey(jobId));
        } catch (SchedulerException e) {
            log.error("删除定时任务失败", e);
        }
    }
}
6.5.4 Job

ScheduleJob.java

java 复制代码
@Slf4j
public class ScheduleJob extends QuartzJobBean {

    private ThreadPoolTaskExecutor scheduleJobExecutorService = SpringContextUtil.getBean("scheduleJobExecutorService", ThreadPoolTaskExecutor.class);

    @Autowired
    private JobService jobService;

    @SneakyThrows
    @Override
    protected void executeInternal(JobExecutionContext context) {
        JobDataMap jobDataMap = context.getTrigger().getJobDataMap();
        Job scheduleJob = new Job();
        scheduleJob.setJobId((Long)context.getMergedJobDataMap().get("jobId"));
        scheduleJob.setBeanName((String)context.getMergedJobDataMap().get("beanName"));
        scheduleJob.setMethodName((String)context.getMergedJobDataMap().get("methodName"));
        scheduleJob.setParams((String)context.getMergedJobDataMap().get("params"));

        // 获取spring bean
        JobLogService scheduleJobLogService = SpringContextUtil.getBean(JobLogService.class);
        JobLog jobLog = new JobLog();
        jobLog.setJobId(scheduleJob.getJobId());
        jobLog.setBeanName(scheduleJob.getBeanName());
        jobLog.setMethodName(scheduleJob.getMethodName());
        jobLog.setParams(scheduleJob.getParams());
        jobLog.setCreateTime(new Date());
        long startTime = System.currentTimeMillis();
        try {
            // 执行任务
            log.info("任务准备执行,任务ID:{}", scheduleJob.getJobId());
            ScheduleRunnable task = new ScheduleRunnable(scheduleJob.getBeanName(), scheduleJob.getMethodName(),
                        scheduleJob.getParams());
            Future<?> future = scheduleJobExecutorService.submit(task);
            future.get();
            long times = System.currentTimeMillis() - startTime;
            jobLog.setTimes(times);
            // 任务状态 0:成功 1:失败
            jobLog.setStatus(JobLog.JOB_SUCCESS);
            log.info("任务执行完毕,任务ID:{} 总共耗时:{} 毫秒", scheduleJob.getJobId(), times);
        } catch (Exception e) {
            log.error("任务执行失败,任务ID:" + scheduleJob.getJobId(), e);
            long times = System.currentTimeMillis() - startTime;
            jobLog.setTimes(times);
            // 任务状态 0:成功 1:失败
            jobLog.setStatus(JobLog.JOB_FAIL);
            jobLog.setErrorInfo(StringUtils.substring(e.toString(), 0, 2000));
        } finally {
            scheduleJobLogService.saveJobLog(jobLog);
        }
    }
}

6.6 定时任务读取配置task

LoadUpdateTask.java

java 复制代码
@Slf4j
@Component
public class LoadUpdateTask {

	@Autowired
	private JobService jobService;

	public void loadUpdateJob() {
        log.info("加载需要修改的job-开始~~~");
        log.info("已加载{}个job~~~",jobService.init());
        log.info("加载需要加载的job-结束~~~");
    }
}

6.7 测试

2020-10-20 17:01:45.050 INFO 5480 --- eduler_Worker-1 com.wno704.boot.util.ScheduleJob : 任务准备执行,任务ID:1

2020-10-20 17:01:45.055 INFO 5480 --- fb-Job-Thread1 com.wno704.boot.job.LoadUpdateTask : 加载需要修改的job-开始~~~

2020-10-20 17:01:45.056 INFO 5480 --- fb-Job-Thread1 com.wno704.boot.job.LoadUpdateTask : 已加载0个job~~~

2020-10-20 17:01:45.057 INFO 5480 --- fb-Job-Thread1 com.wno704.boot.job.LoadUpdateTask : 加载需要加载的job-结束~~~

2020-10-20 17:01:45.057 INFO 5480 --- eduler_Worker-1 com.wno704.boot.util.ScheduleJob : 任务执行完毕,任务ID:1 总共耗时:7 毫秒

查看记录表

注意:如果定时器无法加载,有可能是已经加载,或者失效等,需要清空定时器相关表,执行以下删除脚本:

sql 复制代码
delete from qrtz_blob_triggers;
delete from qrtz_calendars;
delete from qrtz_cron_triggers;
delete from qrtz_fired_triggers;
delete from qrtz_job_log;
delete from qrtz_locks;
delete from qrtz_paused_trigger_grps;
delete from qrtz_scheduler_state;
delete from qrtz_simple_triggers;
delete from qrtz_simprop_triggers;
delete from qrtz_triggers;
delete from qrtz_job_details;
相关推荐
步行cgn2 小时前
MySQL 报错:Access denied for user ‘root‘@‘localhost‘ 详解
java·数据库·spring
颜进强2 小时前
26 · NestJS 基础篇 · 全专栏总结:五个阶段,一次收拢
前端·后端·ai编程
一个有温度的技术博主3 小时前
深拷贝和浅拷贝:一次“搬家”事故
java·后端
萧瑟余晖3 小时前
Spring Boot Actuator监控运维详解
spring boot
苍何3 小时前
又一 Personal Agent 发布,帮我省下不止200刀。。。
后端
程序员清风3 小时前
AI时代建议学点自己真正感兴趣的!
java·后端·面试
Duang007_3 小时前
生产可观测性:从“系统慢“到“根因“的完整链路(Go / TypeScript)
后端·python·golang·typescript·prometheus
Wang's Blog3 小时前
Java框架 SpringCloud 快速入门: Nacos 配置热更新
java·spring·spring cloud
西索斯coding3 小时前
Kimi k2.7-code-highspeed 接入 Cline 教程:baseURL 路由变更 + model_id 正确写法
java·服务器·数据库·ai