目录

吃水不忘挖井人系列

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定时任务的三种实现方式

java若依微服务跨模块定时任务_springboot_02

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