SpringBoot Quartz页面交互控制

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: SpringBoot Quartz页面交互控制

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务。

SpringBoot对quartz进行了集成。

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

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

<!-- mysql -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
</dependency>

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

JobStore任务存储

RAMJobStoreJDBCJobStore

若使用jdbc存储需指定数据库如mysql及数据源datasource如jdbc或者mybatis

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.3.2</version>
</dependency>

application.yml配置

spring:
  server:
    port: 8080
    servlet:
      context-path: /lovin
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/quartz?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  quartz:
    job-store-type: jdbc #数据库方式
    jdbc:
      initialize-schema: never #不初始化表结构 never always
    properties:
      org:
        quartz:
          scheduler:
            instanceId: AUTO #默认主机名和时间戳生成实例ID,可以是任何字符串,但对于所有调度程序来说,必须是唯一的 对应qrtz_scheduler_state INSTANCE_NAME字段
            #instanceName: clusteredScheduler #quartzScheduler
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX #持久化配置
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #我们仅为数据库制作了特定于数据库的代理
            useProperties: false #以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题。
            tablePrefix: QRTZ_  #数据库表前缀
            misfireThreshold: 60000 #在被认为“失火”之前,调度程序将“容忍”一个Triggers将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)。
            clusterCheckinInterval: 5000 #设置此实例“检入”*与群集的其他实例的频率(以毫秒为单位)。影响检测失败实例的速度。
            isClustered: true #打开群集功能
          threadPool: #连接池
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true

Quartz.jdbc.initialize-schema 改成always 会初始化表结构,生成标后改为never

注意tablePrefix: QRTZ_ 的大小写,查看数据库生成的表进行设置。

表名称 说明
qrtz_blob_triggers Trigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore 并不知道如何存储实例的时候)
qrtz_calendars 以Blob类型存储Quartz的Calendar日历信息, quartz可配置一个日历来指定一个时间范围
qrtz_cron_triggers 存储Cron Trigger,包括Cron表达式和时区信息。
qrtz_fired_triggers 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
qrtz_job_details 存储每一个已配置的Job的详细信息
qrtz_locks 存储程序的非观锁的信息(假如使用了悲观锁)
qrtz_paused_trigger_graps 存储已暂停的Trigger组的信息
qrtz_scheduler_state 存储少量的有关 Scheduler的状态信息,和别的 Scheduler 实例(假如是用于一个集群中)
qrtz_simple_triggers 存储简单的 Trigger,包括重复次数,间隔,以及已触的次数
qrtz_triggers 存储已配置的 Trigger的信息
qrzt_simprop_triggers

编写任务执行器

继承自QuartzJobBean, 此处添加@Component加入IOC容器,可以不添加通过class使用

@Slf4j
@Component
public class HelloJob extends QuartzJobBean {

  @Override
  protected void executeInternal (JobExecutionContext context) throws JobExecutionException {
    log.info("HelloJob "+System.currentTimeMillis());
  }
}

操作job的服务

spring-boot-starter-quartz集成注入了Scheduler,通过PostConstruct启动调度器。

@Service
public class QuartzService {

  @Autowired
  private Scheduler scheduler;

  @PostConstruct
  public void startScheduler () {
    try {
      scheduler.start();
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 增加一个job
   *
   * @param jobClass     任务实现类
   * @param jobName      任务名称
   * @param jobGroupName 任务组名
   * @param jobTime      时间表达式 (这是每隔多少秒为一次任务)
   * @param jobTimes     运行的次数 (<0:表示不限次数)
   * @param jobData      参数
   */
  public void addJob (Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, int jobTime,
      int jobTimes, Map jobData) {
    try {
      // 任务名称和组构成任务key
      JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)
          .build();
      // 设置job参数
      if (jobData != null && jobData.size() > 0) {
        jobDetail.getJobDataMap().putAll(jobData);
      }
      // 使用simpleTrigger规则
      Trigger trigger = null;
      if (jobTimes < 0) {
        trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
            .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1).withIntervalInSeconds(jobTime))
            .startNow().build();
      } else {
        trigger = TriggerBuilder
            .newTrigger().withIdentity(jobName, jobGroupName).withSchedule(SimpleScheduleBuilder
                .repeatSecondlyForever(1).withIntervalInSeconds(jobTime).withRepeatCount(jobTimes))
            .startNow().build();
      }
      // 注册jobDetail与trigger到调度器
      scheduler.scheduleJob(jobDetail, trigger);
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 增加一个job
   *
   * @param jobClass     任务实现类
   * @param jobName      任务名称(建议唯一)
   * @param jobGroupName 任务组名
   * @param jobTime      时间表达式 (如:0/5 * * * * ? )
   * @param jobData      参数
   */
  public void addJob (Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String jobTime, Map jobData) {
    try {
      // 创建jobDetail实例,绑定Job实现类
      // 指明job的名称,所在组的名称,以及绑定job类
      // 任务名称和组构成任务key
      JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)
          .build();
      // 设置job参数
      if (jobData != null && jobData.size() > 0) {
        jobDetail.getJobDataMap().putAll(jobData);
      }
      // 定义调度触发规则
      // 使用cornTrigger规则
      // 触发器key
      Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)
          .startAt(DateBuilder.futureDate(1, IntervalUnit.SECOND))
          .withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).startNow().build();
      // 把作业和触发器注册到任务调度中
      scheduler.scheduleJob(jobDetail, trigger);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * 修改 一个job的 时间表达式
   *
   * @param jobName
   * @param jobGroupName
   * @param jobTime
   */
  public void updateJob (String jobName, String jobGroupName, String jobTime) {
    try {
      TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
      CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
      trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
          .withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).build();
      // 重启触发器
      scheduler.rescheduleJob(triggerKey, trigger);
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 删除任务一个job
   *
   * @param jobName      任务名称
   * @param jobGroupName 任务组名
   */
  public void deleteJob (String jobName, String jobGroupName) {
    try {
      scheduler.deleteJob(new JobKey(jobName, jobGroupName));
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * 暂停一个job
   *
   * @param jobName
   * @param jobGroupName
   */
  public void pauseJob (String jobName, String jobGroupName) {
    try {
      JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
      scheduler.pauseJob(jobKey);
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 恢复一个job
   *
   * @param jobName
   * @param jobGroupName
   */
  public void resumeJob (String jobName, String jobGroupName) {
    try {
      JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
      scheduler.resumeJob(jobKey);
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 立即执行一个job
   *
   * @param jobName
   * @param jobGroupName
   */
  public void runAJobNow (String jobName, String jobGroupName) {
    try {
      JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
      scheduler.triggerJob(jobKey);
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
  }

  /**
   * 获取所有计划中的任务列表
   *
   * @return
   */
  public List<Map<String, Object>> queryAllJob () {
    List<Map<String, Object>> jobList = null;
    try {
      GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
      Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
      jobList = new ArrayList<Map<String, Object>>();
      for (JobKey jobKey : jobKeys) {
        List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
        for (Trigger trigger : triggers) {
          Map<String, Object> map = new HashMap<>();
          map.put("jobName", jobKey.getName());
          map.put("jobGroupName", jobKey.getGroup());
          map.put("description", "触发器:" + trigger.getKey());
          Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
          map.put("jobStatus", triggerState.name());
          if (trigger instanceof CronTrigger) {
            CronTrigger cronTrigger = (CronTrigger) trigger;
            String cronExpression = cronTrigger.getCronExpression();
            map.put("jobTime", cronExpression);
          }
          jobList.add(map);
        }
      }
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
    return jobList;
  }

  /**
   * 获取所有正在运行的job
   *
   * @return
   */
  public List<Map<String, Object>> queryRunJob () {
    List<Map<String, Object>> jobList = null;
    try {
      List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
      jobList = new ArrayList<Map<String, Object>>(executingJobs.size());
      for (JobExecutionContext executingJob : executingJobs) {
        Map<String, Object> map = new HashMap<String, Object>();
        JobDetail jobDetail = executingJob.getJobDetail();
        JobKey jobKey = jobDetail.getKey();
        Trigger trigger = executingJob.getTrigger();
        map.put("jobName", jobKey.getName());
        map.put("jobGroupName", jobKey.getGroup());
        map.put("description", "触发器:" + trigger.getKey());
        Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
        map.put("jobStatus", triggerState.name());
        if (trigger instanceof CronTrigger) {
          CronTrigger cronTrigger = (CronTrigger) trigger;
          String cronExpression = cronTrigger.getCronExpression();
          map.put("jobTime", cronExpression);
        }
        jobList.add(map);
      }
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
    return jobList;
  }
}

Quartz控制器

与前端页面交互的control,前端页面交互时调用service实现对job的操控。

@Api(tags = "Quartz控制器")
@RestController
public class QuartzController {


  @Autowired
  private QuartzService quartzService;

  @ApiOperation("添加任务")
  @PostMapping("/addJob")
  public Object addJob (@RequestBody JobRequestDto requestDto){
    Object bean = SpringUtil.getBean(requestDto.getBeanName());
    if(!(bean instanceof QuartzJobBean)){
      return "bean type is not QuartzJobBean";
    }
    QuartzJobBean jobBean = (QuartzJobBean) bean;
    quartzService.addJob(jobBean.getClass(),requestDto.getJobName(),requestDto.getJobGroupName(),requestDto.getCron(),requestDto.getParams());

    return "success";
  }

  // ... updateJob deleteJob pauseJob resumeJob  runAJobNow queryAllJob queryRunJob
}
@Data
public class JobRequestDto {

  String beanName;

  String jobName;

  String jobGroupName;

  String cron;

  Map<String,Object> params;
}

启动服务,发送请求

{
  "beanName": "helloJob",
  "cron": "0/5 * * * * ?",
  "jobGroupName": "paw-demo",
  "jobName": "helloJob",
  "params": {}
}

就会看到控制台输出,HelloJob已启动

2021-08-12 14:20:30.029  INFO 17620 --- [eduler_Worker-7] com.paw.quartz.jobs.HelloJob             : HelloJob 1628749230029
2021-08-12 14:20:35.035  INFO 17620 --- [eduler_Worker-8] com.paw.quartz.jobs.HelloJob             : HelloJob 1628749235035
2021-08-12 14:20:40.030  INFO 17620 --- [eduler_Worker-9] com.paw.quartz.jobs.HelloJob             : HelloJob 1628749240030
2021-08-12 14:20:45.031  INFO 17620 --- [duler_Worker-10] com.paw.quartz.jobs.HelloJob             : HelloJob 1628749245031

查看数据库

相关数据已保存,如QRTZ_JOB_DETAILS

SCHED_NAME    JOB_NAME    JOB_GROUP    DESCRIPTION    JOB_CLASS_NAME    IS_DURABLE    IS_NONCONCURRENT    IS_UPDATE_DATA    REQUESTS_RECOVERY    JOB_DATA
quartzScheduler    helloJob    paw-demo        com.paw.quartz.jobs.HelloJob    0    0    0    0    

停止服务再次启动

quartz会通过jdbc从数据库获取配置,启动任务。

总结

Springboot提供了quartz的集成spring-boot-starter-quartz,可以采用jdbcStore的方式将job存储在数据库,服务重启任务不丢失。通过API接口对job进行控制,从而实现页面交互控制。用户只需要继承QuartzJobBean实现业务逻辑,然后通过页面将业务类bean提交到job中即可实现定时服务。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
62 2
|
2月前
|
前端开发 Java Spring
SpringBoot项目thymeleaf页面支持词条国际化切换
SpringBoot项目thymeleaf页面支持词条国际化切换
89 2
|
2月前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
43 0
|
4月前
|
Java Spring
springboot静态资源目录访问,及自定义静态资源路径,index页面的访问
本文介绍了Spring Boot中静态资源的访问位置、如何进行静态资源访问测试、自定义静态资源路径和静态资源请求映射,以及如何处理自定义静态资源映射对index页面访问的影响。提供了两种解决方案:取消自定义静态资源映射或编写Controller来截获index.html的请求并重定向。
springboot静态资源目录访问,及自定义静态资源路径,index页面的访问
|
3月前
|
前端开发 Java
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
文章介绍了如何使用SpringBoot创建简单的后端服务器来处理HTTP请求,包括建立连接、编写Controller处理请求,并返回响应给前端或网址。
64 0
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
|
4月前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
57 6
|
4月前
|
Java 网络架构
springboot配合thymeleaf,调用接口不跳转页面只显示文本
springboot配合thymeleaf,调用接口不跳转页面只显示文本
180 0
|
5月前
|
Java 关系型数据库 MySQL
SpringBoot 集成 Quartz + MySQL
SpringBoot 集成 Quartz + MySQL
135 1
|
5月前
|
XML SQL JavaScript
在vue页面引入echarts,图表的数据来自数据库 springboot+mybatis+vue+elementui+echarts实现图表的制作
这篇文章介绍了如何在Vue页面中结合SpringBoot、MyBatis、ElementUI和ECharts,实现从数据库获取数据并展示为图表的过程,包括前端和后端的代码实现以及遇到的问题和解决方法。
在vue页面引入echarts,图表的数据来自数据库 springboot+mybatis+vue+elementui+echarts实现图表的制作
|
5月前
|
前端开发 Java Spring
springboot+thymeleaf+bootstrap 超级无敌简洁的页面展示 商城管理页面
这篇文章展示了一个使用Spring Boot、Thymeleaf和Bootstrap框架开发的简洁、响应式的商城管理页面,包括美食介绍、产品详情、购物车等功能,适合初学者学习和使用。
springboot+thymeleaf+bootstrap 超级无敌简洁的页面展示 商城管理页面
下一篇
开通oss服务