【MedusaSTears】复杂定时任务SpringBoot+Quartz实例,解决jobclass如何注入一个service类,以及实现简单业务逻辑

2023-11-20

目录

吃水不忘挖井人系列

1.认识了解各种定时任务实现方式:

2.本文主要参考

3.其他参考

一.业务需求

这里提一下我对@Scheduled和Quartz的一点小看法(如有误解还请指正)

二.软件环境

java版本

 SpringBoot版本

 Quartz版本(maven的dependency)

三.操作流程

1.加入maven依赖(第二步已写,忽略)

2-1.创建一个测试类自己去理解一下quartz的流程

2-2.实现任务代码

在业务流程中,加入task的方法代码,并在主要逻辑流程里,调用此方法

重点是Task.class应该怎么写 (注意FIXME的 那几行)

3.注意事项

@Component

ApplicationContext的使用

如何从JobDataMap里面获取数据

没有返回值的void方法如何停止代码,既不能break也不是exit?


吃水不忘挖井人系列

1.认识了解各种定时任务实现方式:

  Java定时任务调度详解 

​​​​

Java定时任务的三种实现方式

Java定时任务 (spring整合quartz,用xml方式配置)

SpringBoot 使用@Scheduled注解配置定时任务 

 

2.本文主要参考

定时任务框架Quartz-(一)Quartz入门与Demo搭建            小试身手,入门demo

Quartz学习笔记(二)Job、JobDetail、JobDataMap     传参重点,JobDataMap怎么获取?

SpringBoot集成Quartz动态定时任务                                 进阶提炼,Quartz的Controller分层

ApplicationContextAware使用理解                                    最后点睛之笔@Component注解

 

3.其他参考

在线Cron表达式生成器

通过这个生成器,您可以在线生成任务调度比如Quartz的Cron表达式,对Quartz Cron 表达式的可视化双向解析和生成

 

SpringBoot+Quartz定时任务:Job类对象注入(Demo)

这篇文章倒是用IOC的另一种注入解决方案,不过我没用

 

一.业务需求

 最近遇到几个项目都需要定时任务这个功能,而且是不能用@Scheduled的复杂定时任务,所以研究一下Quartz

这里提一下我对@Scheduled和Quartz的一点小看法(如有误解还请指正)

1.@Scheduled 只能指定固定时间(cron语法)或者延迟x时间后,从项目启动(或延迟x时间后)启动任务,然后一直循环执行

2. Quartz 不仅可以支持以上方式,而主要符合我这次需求的是"指定多久后启动任务,执行几次后结束"

 

项目有涉及调用银行支付,支付成功后有回调我的方法,查询是否支付成功,成功的话我会写入数据库保存支付交易信息

但是银行的回调方法只有一次,如果这一次遇到某些问题,很可能用户支付成功了,但是因为只有一次回调导致成功的订单没有写入数据库,

所以打算,在这次回调接收到的一开始,就启动Quartz执行业务流程,并每隔5分钟调用一次,一共调用3次,用来查看支付结果

 

二.软件环境

网上大多都是spring+quartz的文章,还用的是xml的注入方式,和我目前对接的项目不匹配,所以找了一圈之后自己总结一下

java版本

java version "1.8.0_192"
Java(TM) SE Runtime Environment (build 1.8.0_192-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.192-b12, mixed mode)

 SpringBoot版本

  <!-- spring boot -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9.RELEASE</version>
    <relativePath/>
  </parent>

 Quartz版本(maven的dependency)

    <!-- quartz  https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
    <dependency>
      <groupId>org.quartz-scheduler</groupId>
      <artifactId>quartz</artifactId>
      <version>2.3.0</version>
    </dependency>
    <!--调度器核心包-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>4.3.4.RELEASE</version>
    </dependency>

 

三.操作流程

1.加入maven依赖(第二步已写,忽略)

2-1.创建一个测试类自己去理解一下quartz的流程

这个类直接运行就可以了,里面的代码注释也很详细了

package com.test.scheduled.test;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class ScheduledTasks implements Job {

    public static void main(String[] args) {
        ScheduledTasks eg = new ScheduledTasks();
        try {
            eg.simpleSchedule();
        } catch (SchedulerException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("开始job任务,   PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));

    }

    public void simpleSchedule() throws SchedulerException, InterruptedException {
        // 1、创建调度器Scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        // 2、 通过JobBuilder构建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
        JobDetail jobDetail = JobBuilder.newJob(ScheduledTasks.class)
                .withIdentity("job1", "jobGroup1")//给job命名并分组
                .build();
        // 3、构建Trigger实例,每隔1s执行一次
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "triggerGroup1")//给trigger命名并分组
                .startNow()//立即生效
//                .startAt(new Date(System.currentTimeMillis() + 1000 * 60 * 5)) // 5分钟后生效
                .withSchedule(
                    SimpleScheduleBuilder.simpleSchedule()
//                        .withIntervalInMilliseconds(200) // 每隔 200毫秒 执行一次
                        .withIntervalInSeconds(5) // 每隔5秒执行一次
//                        .withIntervalInMinutes(10) // 每隔10分钟执行一次
                        .withRepeatCount(3) // 重复3次
//                        .repeatForever()//一直执行
                )
                .build();


        //4、执行
        scheduler.scheduleJob(jobDetail, trigger);
        System.out.println("--------scheduler start ! ------------");
        scheduler.start();

        //睡眠
        TimeUnit.MINUTES.sleep(1);
        scheduler.shutdown();
        System.out.println("--------scheduler shutdown ! ------------");
    }

    public void cronSchedule() throws SchedulerException, InterruptedException {
        // 1、创建调度器Scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        // 2、通过JobBuilder构建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
        JobDetail jobDetail = JobBuilder.newJob(ScheduledTasks.class)
                .usingJobData("jobDetail1", "这个Job用来测试的")
                .withIdentity("job1", "group1")//给job命名并分组
                .build();
        // 3-1、基于表达式构建触发器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
        // 3-2、通过TriggerBuilder构建CronTrigger触发器实例(继承于Trigger)
        Date startDate = new Date();
        startDate.setTime(startDate.getTime() + 5000);

        Date endDate = new Date();
        endDate.setTime(startDate.getTime() + 5000);

        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .usingJobData("trigger1", "这是jobDetail1的trigger")
                .withIdentity("trigger1", "triggerGroup1") //给trigger命名并分组
//                    .startNow()//立即生效
                .startAt(startDate) //startNow 和 startAt 有一个坑!这两个方法是对同一个成员变量进行修改的 也就是说startAt和startNow同时调用的时候任务开始的时间是按后面调用的方法为主的
                .endAt(endDate)
                .withSchedule(cronScheduleBuilder)
                .build();

        //4、执行
        scheduler.scheduleJob(jobDetail, cronTrigger);
        System.out.println("--------scheduler start ! ------------");
        scheduler.start();
        System.out.println("--------scheduler shutdown ! ------------");

    }
}

2-2.实现任务代码

在业务流程中,加入task的方法代码,并在主要逻辑流程里,调用此方法


    /**
     * @Title: checkStatusSchedule
     * @Description: 定时任务, 回调开始后, 调用这个任务
     * @param in:
     * @return void
     * @Author: Tyler
     * @Date: 2019/12/12
     */
    public void checkStatusSchedule(QueryPaymentResultIn in) throws SchedulerException, InterruptedException {
        // 1、创建调度器Scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        // 2、 通过JobBuilder构建JobDetail实例,并与Task类绑定(Job执行内容)
        JobDetail jobDetail = JobBuilder.newJob(Task.class)
                .usingJobData("in", JSON.toJSONString(in))
                .withIdentity("job1", "jobGroup1")//给job命名并分组
                .build();
        // 3、构建Trigger实例
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "triggerGroup1")//给trigger命名并分组
                .startAt(new Date(System.currentTimeMillis() + 1000 * 60 * 5)) // 5分钟后启动任务,调用Task.class
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(5)// 每隔5秒执行一次
//                        .withIntervalInMinutes(5)//每隔5分钟执行一次
                        .withRepeatCount(3)// 重复3次
//                        .repeatForever()//一直执行
                )
                .build();

        //4、执行
        scheduler.scheduleJob(jobDetail, trigger);
        System.out.println("--------scheduler start ! ------------");
        scheduler.start();

//        //睡眠
//        TimeUnit.MINUTES.sleep(1);
//        scheduler.shutdown();
//        System.out.println("--------scheduler shutdown ! ------------");
    }

调用checkStatusSchedule方法时,传入 查询交易状态的传入参数 QueryPaymentResultIn in

此QueryPaymentResultIn对象内属性无非是 订单号,订单日期,交易金额之类的,不展示了

重点是Task.class应该怎么写 (注意FIXME的 那几行)

package com.test.scheduled;

import java.text.SimpleDateFormat;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;

/**
 * @description: 支付回调后触发的定时任务:查询3次支付是否成功
 * @author: Tyler
 * @create: 2019-12-12 11:08
 **/
@Component  // FIXME 这个注解很重要,不要忘记,不加的话applicationContext是null
public class Task implements Job, ApplicationContextAware {

    protected final Log logger = LogFactory.getLog(getClass());

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // FIXME 通过获取spring的上下文来getBean()就不需要另一种方式注入了
        this.applicationContext = applicationContext;
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        // FIXME 如何从JobDataMap里面获取数据
        Object object = jobExecutionContext.getJobDetail().getJobDataMap().get("in");
        QueryPaymentResultIn in = JSON.parseObject(object.toString(), QueryPaymentResultIn.class);

        // FIXME 如何利用applicationContext来调用Service里的方法
        PayService payService= applicationContext.getBean(PayService.class);
        QueryPaymentResult payStatus = payService.queryPaymentResult(in);

        if (payStatus == null || !"3".equals(payStatus.getOrder_status())) {
            // 订单支付失败,不作处理,等待其它2次调用定时任务
            return;  // FIXME void的方法如何停止代码不往下运行
        }

        //订单支付成功:  ..... 剩余的业务逻辑
}

 

3.注意事项

@Component

这个注解很重要,不要忘记,不加的话applicationContext是null

 

ApplicationContext的使用

1. 继承ApplicationContextAware----public class Task implements Job, ApplicationContextAware {}

2.必须是static的,否则还是null  private static ApplicationContext applicationContext;

3.重写方法

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // FIXME 通过获取spring的上下文来getBean()就不需要另一种方式注入了
        this.applicationContext = applicationContext;
    }

4.如何获取需要的service?   

PayService payService= applicationContext.getBean(PayService.class);
QueryPaymentResult payStatus = payService.queryPaymentResult(in);

 

如何从JobDataMap里面获取数据

目前我没找到更好的代码表达方式,只能先序列化存到JobDataMap里,然后获取出来是Object对象再反序列化

尝试过 强制类型转换(QueryPaymentResultIn)object 可是会报错

Object object = jobExecutionContext.getJobDetail().getJobDataMap().get("in");
QueryPaymentResultIn in = JSON.parseObject(object.toString(), QueryPaymentResultIn.class);

 

没有返回值的void方法如何停止代码,既不能break也不是exit?

答案是,在需要停止代码的地方,直接 return;  就可以了

这个看着简单,不知道之前还真挺蛋疼

 

 

--------ALL BY MedusaSTears

--------2019.12.16

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【MedusaSTears】复杂定时任务SpringBoot+Quartz实例,解决jobclass如何注入一个service类,以及实现简单业务逻辑 的相关文章

随机推荐

  • mybatis中type-aliases-package的用法

    springboot项目中的application yml文件中的mybatis type aliases package 什么时候用 mapper xml文件中resultMap的type parameterType resultType
  • Unity中级客户端开发工程师的进阶之路

    上期UWA技能成长系统之 Unity高级客户端开发工程师的进阶之路 得到了很多Unity开发者的肯定 通过系统的学习 可以掌握游戏性能瓶颈定位的方法和常见的CPU GPU 内存相关的性能优化方法 UWA技能成长系统是UWA根据学员的职业发展
  • 1分钟快速掌握Vue Router的使用?

    简介 Vue Router 是 Vue js 官方的路由管理器 它和 Vue js 的核心深度集成 让构建单页面应用变得易如反掌 包含的功能有 嵌套的路由 视图表 模块化的 基于组件的路由配置 路由参数 查询 通配符 基于 Vue js 过
  • AI工程化:各家的AI平台、AI中台架构图

    中台的概念 AI 中台是用来构建大规模智能服务的基础设施 是一套完整的人工智能模型全生命周期管理平台和服务体系 提供模型设计训练 模型 算法库 复用标注管理 模型监控服务等能力支持 AI中台旨在让企业业务前台可以短兵作战 小步快跑 降低试错
  • Unity 粒子特效、材质发光 HDR ShaderGraph图文教程[完成lit发光设置]

    效果如图 准备工作 在hdr模式下 关闭Directional Light 相机设置 移动球挂一个点光源作为子节点 设置自行调节 0 创建移动球的材质及shader shader gt 在Project Create Shader Grap
  • 查找出某表字段数据不为空

    DECLARE CURSOR temp IS SELECT COLUMN NAME FROM ALL TAB COLUMNS WHERE TABLE NAME Upper TEST v num NUMBER BEGIN FOR i IN t
  • 如何在 Vultr 上部署 ONLYOFFICE 文档 v7.3

    现在您可使用通过 Vultr 市场提供的一键式应用在 Vultr 架构中轻松部署 Docker 版本的 ONLYOFFICE 文档 一键式应用是什么 一键式应用是一个包含所有必要预配置组件的镜像 可用于便捷地在运行有 Ubuntu OS 的
  • 有向图的邻接表的建立,深度遍历并输出(c语言实现有向网)

    有向图的邻接表的建立 深度遍历并输出 c语言实现有向网 为方便理解 首先先为图的邻接表画一个模型 邻接表可以分为两部分 1 表头节点 2 弧节点 如上图 因为写的代码是有向网 所以选择上图 首先在脑海里建立一个模型 代码如下 include
  • 【PrimeTime 基本命令】

    https www micro ip com drchip php mode 2 cid 17
  • 毕业设计 STM32人体红外测温枪温度采集系统 - 单片机

    文章目录 1 前言 2 主要器件 3 实现效果 4 设计原理 MLX90614 红外温度传感器 5 部分实现代码 6 最后 1 前言 这两年开始毕业设计和毕业答辩的要求和难度不断提升 传统的毕设题目缺少创新和亮点 往往达不到毕业答辩的要求
  • go语言的安装已经调试

    go语言作为google的一个主推语言 最近很多人都在研究 也花了一点时间对他的安装进行了测试 本人使用Sublime Text 2 GoSublime gocode 顾名思义首先是安装Go 这里有很详细的安装说明 http code go
  • 单相逆变器的建模与仿真

    1 电路拓扑 本次设计采用单相全桥逆变电路 使用LC滤波器 负载使用单相桥式整流 电路如图所示 2 控制思路 控制部分采用PI控制 包含电压外环和电流内环 而电流内环又分为电感电流反馈和电容电流反馈两种 其中电感电流内环电压外环的控制框图和
  • pip install 国内镜像源

    经验证 阿里的云最快 记得是https不是http 对于Python开发用户来讲 PIP安装软件包是家常便饭 而国外的源下载速度太慢 浪费时间 而且常出现下载后安装出错问题 故把pip安装源替换成国内镜像 可大幅提高下载速度 还可以提高安装
  • 计算机网络学习笔记第四章(网络层)超详细整理

    目录 4 1 网络层概述 1 简介 2 总结 4 2 网络层提供的两种服务 1 面向连接的虚电路服务 2 无连接的数据报服务 3 虚电路服务与数据报服务的对比 4 3 IPv4 1 概述 2 分类编制的IPv4地址 2 1 简介 2 2 总
  • 第六篇:进阶篇 车内的吸声性能及测试方法

    本专栏分享传统NVH知识点 从声学理论 材料声学 汽车噪声振动分析 车辆及其零部件甚至原材料的声学测试方法等多维度介绍汽车NVH 一些专用术语同时给出了中英文对照 欢迎新人 同行 爱好者一起交流 由于内容写的较为仓促 有误的地方欢迎大家批评
  • 【安装】win下的openvino安装及onnx模型转换.xml和.bin

    安装文档参考 openvino安装指导 包含相应依赖的安装 三步到位 我装的是python3 8 vs2019 cmake3 24 0 一般最新版本 这几个安装及注意的细节在文档中都有描述 一定一定要按上述文档安装 并且细节要注意 尤其是o
  • 一道面试题:JVM老年代空间担保机制

    面试问题 昨天面试的时候 面试官问的问题 什么是老年代空间担保机制 担保的过程是什么 老年代空间担保机制是谁给谁担保 为什么要有老年代空间担保机制 或者说空间担保机制的目的是什么 如果没有老年代空间担保机制会有什么不好 下面我们就带着这些问
  • Nuttx操作系统(三):构建模式

    1 1 Nuttx构建配置以及模式 Nuttx有三种不同的构建配置 FLAT构建 这种构建是所代码驻留在公共地址空间中 1 应用 内核以及board logic在一个flat地址环境中 2 所有的地址空间具有相同的属性 PROTECTED构
  • expected primary-expression before 'unsigned' 解决方案

    问题描述 语句result unsigned int 1 lt lt i 在本地可以编译运行 但是上传至LeetCode后出现编译错误 expected primary expression before unsigned 分析 语句太复杂
  • 【MedusaSTears】复杂定时任务SpringBoot+Quartz实例,解决jobclass如何注入一个service类,以及实现简单业务逻辑

    目录 吃水不忘挖井人系列 1 认识了解各种定时任务实现方式 2 本文主要参考 3 其他参考 一 业务需求 这里提一下我对 Scheduled和Quartz的一点小看法 如有误解还请指正 二 软件环境 java版本 SpringBoot版本