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

目录

吃水不忘挖井人系列

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

 

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
首先,需要在 Spring Boot 中集成 Quartz。可以通过添加以下依赖来实现: ```xml <!-- Quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency> ``` 然后,在 Spring Boot 中配置 Quartz。可以创建一个 `SchedulerFactoryBean` 实例,并设置相关属性,例如线程池大小、任务调度器等等。可以在 `application.properties` 文件中设置相关属性: ```properties # Quartz Scheduler spring.quartz.job-store-type=jdbc spring.quartz.jdbc.initialize-schema=always spring.quartz.properties.org.quartz.scheduler.instanceName=QuartzScheduler spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate spring.quartz.properties.org.quartz.jobStore.useProperties=true spring.quartz.properties.org.quartz.jobStore.dataSource=myDataSource spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_ spring.quartz.properties.org.quartz.dataSource.myDataSource.driverClassName=com.mysql.jdbc.Driver spring.quartz.properties.org.quartz.dataSource.myDataSource.URL=jdbc:mysql://localhost:3306/quartz spring.quartz.properties.org.quartz.dataSource.myDataSource.user=root spring.quartz.properties.org.quartz.dataSource.myDataSource.password=root spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool spring.quartz.properties.org.quartz.threadPool.threadCount=10 spring.quartz.properties.org.quartz.threadPool.threadPriority=5 spring.quartz.properties.org.quartz.jobStore.isClustered=true spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=20000 spring.quartz.properties.org.quartz.jobStore.maxMisfiresToHandleAtATime=1 ``` 然后,需要创建一个 `Job` 来执行具体的任务。例如: ```java public class MyJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 执行具体的任务逻辑 System.out.println("Hello Quartz!"); } } ``` 最后,需要创建一个 `CronTriggerFactoryBean` 实例,并设置相关属性。例如: ```java @Configuration public class QuartzConfig { @Bean public JobDetailFactoryBean myJobDetail() { JobDetailFactoryBean factory = new JobDetailFactoryBean(); factory.setJobClass(MyJob.class); factory.setDurability(true); // 任务持久化 return factory; } @Bean public CronTriggerFactoryBean myCronTrigger(@Qualifier("myJobDetail") JobDetail jobDetail) { CronTriggerFactoryBean factory = new CronTriggerFactoryBean(); factory.setJobDetail(jobDetail); factory.setCronExpression("0/5 * * * * ?"); // 每隔 5 秒触发一次 return factory; } @Bean public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("myCronTrigger") CronTrigger cronTrigger) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setTriggers(cronTrigger); return factory; } } ``` 这里的 `myJobDetail` 方法返回一个 `JobDetailFactoryBean` 实例,它指定了具体的任务。`myCronTrigger` 方法返回一个 `CronTriggerFactoryBean` 实例,它指定了触发器的具体规则。`schedulerFactoryBean` 方法返回一个 `SchedulerFactoryBean` 实例,它将任务和触发器关联起来,并返回一个 `Scheduler` 实例,可以通过它来启动和停止任务调度器。 最后,在 Vue 中实现调用后端接口来启动和停止任务调度器。可以使用 Axios 进行 HTTP 请求。例如: ```javascript import axios from 'axios' // 启动任务调度器 export function startScheduler () { return axios.post('/scheduler/start') } // 停止任务调度器 export function stopScheduler () { return axios.post('/scheduler/stop') } ``` 这里的 `/scheduler/start` 和 `/scheduler/stop` 是后端接口的 URL。可以在后端使用 Spring Boot 的 MVC 模块来实现这两个接口,例如: ```java @RestController @RequestMapping("/scheduler") public class SchedulerController { @Autowired private Scheduler scheduler; @PostMapping("/start") public void start() throws SchedulerException { scheduler.start(); } @PostMapping("/stop") public void stop() throws SchedulerException { scheduler.shutdown(); } } ``` 这里的 `scheduler` 是通过 `SchedulerFactoryBean` 创建的 `Scheduler` 实例,可以通过 `start()` 和 `shutdown()` 方法来启动和停止任务调度器。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值