Springboot定时任务实现——Quartz

Quartz 是一个功能全面、开源的企业作业调度服务器。

定时任务框架:
1)简单的有Java自带的Timer、 ScheduledExecutorService, Spring自带的Task。
2)相较复杂的分布式定时任务中间件有XXL-JOB、ElasticJob等。

选Quartz理由:
1)任务Tigger能够被持久化,这样即使在发布后,任务依然能够执行,不需要重新设定。
2)能够轻松暂停恢复触发器(即下次不会被调度)。
3)支持Calander,Cron表达式等复杂的触发器,可以灵活的编写复杂触发器。

Quartz中有三个基本”组件”,由它们共同来定义,运行一个定时任务:
1)JobDetail,定时任务中的“任务”;Job接口是真正需要执行的任务。JobDetail接口相当于将Job接口包装了一下,Trigger和Scheduler实际用到的都是JobDetail。
2)Trigger,定时任务中的“定时”;通过cron表达式或是SimpleScheduleBuilder等类,指定任务执行的周期。
3)Scheduler,定时任务的调度器(组装器);Quartz通过调度器来注册、暂停、删除Trigger和JobDetail。

具体使用:

  1. 引入springboot官方启动器
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  1. 创建任务
    1)在Quartz中创建的所有定时任务都要实现Job接口,但是在SpringBoot中所有的定时任务只要继承QuartzJobBean类即可。
    2)QuartzJobBean是一个抽象类,实现了Quartz的Job接口。
    2)与Thread的run()方法类似,定时任务的具体实现写在executeInternal()方法中。
    3)每创建一个新的定时任务,都需要新建一个Java类并继承QuartzJobBean、实现executeInternal()。
@Component
public class FlowRestartJob extends QuartzJobBean {
    private final Logger logger = LoggerFactory.getLogger(FlowRestartJob.class);

    @Autowired
    private RestartFlowHandler restartFlowHandler;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        try {
            restartFlowHandler.execute();  // 具体任务
        } catch (Exception e) {
            logger.error("任务失败 error:{}", e.getMessage());
        }
    }
}
  1. 提供 Quartz 相关配置 Bean,创建JobDetail和Trigger

使用建造者模式的JobBuilder来创建一个JobDetail对象。

JobDetail simpleJob = JobBuilder.newJob(SimpleJob.class)        //传入一个Job类
                                .withIdentity("SimpleJob", "AnchorJobs")    //(name, group)标识唯一一个JobDetail
                                .storeDurably()        //在没有Trigger关联的情况下保存该任务到调度器
                                .build();

1)newJob()中传入的Job类必须是继承了QuartzJobBean的类。
2)withIdentity()中group可不传,不传时默认设为”DEFAULT”。
3)storeDurably()使JobDetail可在没有关联Trigger的情况下添加到调度器中,否则会抛异常。建议调用此方法。

常用Trigger有两种:SimpleTrigger和CronTrigger。二者最大的区别是CronTrigger支持Cron表达式。创建CronTrigger:

CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");    //Cron表达式,每5秒执行一次
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                                    .withIdentity("CronJob", "AnchorTriggers")    //(name, group)唯一标识一个Trigger
                                    .startNow()                         //调用scheduler.scheduleJob()后立即开始执行定时任务
                                    .withSchedule(scheduleBuilder)      //不同的scheduleBuilder
                                    .build();

具体实现:

@Configuration
public class QuartzConfig {

    @Value("${quartz.restartCron}")
    private String restartCron; // corn表达式

    @Bean
    public JobDetail restartJob() {
        return JobBuilder.newJob(FlowRestartJob.class).withIdentity("FlowRestartJob").storeDurably().build();
    }

    @Bean
    public Trigger restartTrigger() {
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(restartCron);
        return TriggerBuilder.newTrigger().forJob(restartJob())
                .withIdentity("FlowRestartJob").withSchedule(scheduleBuilder).build();
    }

}

创建并启动一个定时任务的正常流程是:创建任务类 ——> 创建JobDetail ——> 创建Trigger

Quartz和Springboot一起使用时为什么不用创建Scheduler?
SpringBoot官方写了 spring-boot-starter-quartz,这是一个官方提供的启动器,有了这个启动器,集成的操作就会被大大简化。SpingBoot2.2.6官方文档,其中第4.20小节 Quartz Scheduler 就谈到了Quartz。
原文如下:
Spring Boot offers several conveniences for working with the Quartz scheduler, including the
spring-boot-starter-quartz “Starter”. If Quartz is available, a Scheduler is auto-configured (through the SchedulerFactoryBean abstraction).
Beans of the following types are automatically picked up and associated with the Scheduler:
• JobDetail: defines a particular Job. JobDetail instances can be built with the JobBuilder API.
• Calendar.
• Trigger: defines when a particular job is triggered.
即:
SpringBoot提供了一些便捷的方法来和Quartz协同工作,这些方法里面包括spring-boot-starter-quartz这个启动器。
如果Quartz可用,Scheduler会通过SchedulerFactoryBean这个工厂bean自动配置到SpringBoot里。JobDetail、Calendar、Trigger这些类型的bean会被自动采集并关联到Scheduler上。
Job可以定义setter(也就是set方法)来注入配置信息。也可以用同样的方法注入普通的bean。

注册无周期性定时任务:
某些任务不是周期性的,只存在执行时间,必须完成的两点:
1)Job类需要获取到一些数据用于任务的执行。
2)任务执行完成后删除Job和Trigger。

具体代码实现如下:
1.创建任务

@Component
public class RestartJob extends QuartzJobBean {
    private final Logger logger = LoggerFactory.getLogger(FlowRestartJob.class);
    private Scheduler scheduler;
    private SystemUserMapperPlus systemUserMapperPlus;

    @Autowired
    public RestartJob(Scheduler scheduler, SystemUserMapperPlus systemUserMapperPlus) {
        this.scheduler = scheduler;
        this.systemUserMapperPlus = systemUserMapperPlus;
    }

    @Autowired
    private RestartFlowHandler restartFlowHandler;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Trigger trigger = jobExecutionContext.getTrigger();
        // 将添加任务的时候存进去的数据拿出来
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        long username = jobDataMap.getLongValue("username");
        LocalDateTime time = LocalDateTime.parse(jobDataMap.getString("time"));

        try {
        	// 具体任务逻辑
            restartFlowHandler.execute();  

			// 执行之后删除任务      
            scheduler.pauseTrigger(trigger.getKey()); // 暂停触发器的计时
            scheduler.unscheduleJob(trigger.getKey()); // 移除触发器中的任务
            scheduler.deleteJob(jobDetail.getKey()); // 删除任务
        } catch (Exception e) {
            logger.error("任务失败 error:{}", e.getMessage());
        }   
    }
}

2.service层逻辑

@Service
public class LeaveApplicationServiceImpl implements LeaveApplicationService {
    @Autowired
    private Scheduler scheduler;

    // 添加job和trigger到scheduler
    private void addJobAndTrigger(LeaveApplication leaveApplication) {
        // 创建请假开始Job
        Long proposerUsername = leaveApplication.getProposerUsername();
        LocalDateTime startTime = leaveApplication.getStartTime();
        JobDetail startJobDetail = JobBuilder.newJob(RestartJob.class)
                // 指定任务组名和任务名
                .withIdentity(leaveApplication.getStartTime().toString(), proposerUsername + "_start")
                // 添加一些参数,执行的时候用于取出
                .usingJobData("username", proposerUsername)
                .usingJobData("time", startTime.toString())
                .build();

        // 创建请假开始任务的触发器
        // 创建cron表达式指定任务执行的时间,由于请假时间是确定的,所以年月日时分秒都是确定的,这也符合任务只执行一次的要求。
        String startCron = String.format("%d %d %d %d %d ? %d",
                startTime.getSecond(),
                startTime.getMinute(),
                startTime.getHour(),
                startTime.getDayOfMonth(),
                startTime.getMonth().getValue(),
                startTime.getYear());
        CronTrigger startCronTrigger = TriggerBuilder.newTrigger()
                // 指定触发器组名和触发器名
                .withIdentity(leaveApplication.getStartTime().toString(), proposerUsername + "_start")
                .withSchedule(CronScheduleBuilder.cronSchedule(startCron))
                .build();

        // 将job和trigger添加到scheduler里
        try {
            scheduler.scheduleJob(startJobDetail, startCronTrigger);
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new CustomizedException("添加请假任务失败");
        }
    }
}

欢迎大家关注我的公众号!pavel随笔,分享工作时遇见的问题与解决方案,再就是一些学习笔记。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值