在项目应用中往往会用到任务定时器的功能,比如某某时间,或者多少多少秒然后执行某个骚操作等。
spring 支持多种定时任务的实现,其中不乏自身提供的定时器。
接下来介绍一下使用 spring 的定时器和使用 quartz 定时器。 

前言

spring 自身提供了定时任务,为什么还要使用 quartz 呢?

使用 spring 自带的定时任务可以很简单很方便的完成一些简单的定时任务,没错,这里提到的是简单,因此我们想动态的执行我们的定时任务是非常困难的。然而使用 quartz 却可以很容易的管理我们的定时任务,很容易动态的操作定时任务。

1、 使用spring的定时器  

spring 自带支持定时器的任务实现,其可通过简单配置来使用到简单的定时任务。

@Component
@Configurable
@EnableScheduling
public class ScheduledTasks{     /**
     * 方式一
     * 每6秒执行一次
     **/
    @Scheduled(fixedRate = 6000)
    public void reportCurrentByCron(){
        System.out.println ("Scheduling Tasks Examples By Cron: The time is now " + dateFormat ().format (new Date ()));
    }     /**
     * 方式二
     * 每6秒执行一次
     **/
    @Scheduled(cron = "*/6 * *  * * * ")
    public void reportCurrentByCron(){
        System.out.println ("Scheduling Tasks Examples By Cron: The time is now " + dateFormat ().format (new Date ()));
    } }
参数说明

@Scheduled 参数可以接受两种定时的设置,一种是我们常用的 cron="/6 * * * ?",一种是 fixedRate = 6000,两种都可表示固定周期执行定时任务。

fixedRate说明

  • @Scheduled(fixedRate = 6000):上一次开始执行时间点之后 6 秒再执行。
  • @Scheduled(fixedDelay = 6000):上一次执行完毕时间点之后 6 秒再执行。
  • @Scheduled(initialDelay=1000, fixedRate=6000):第一次延迟 1 秒后执行,之后按 fixedRate 的规则每 6 秒执行一次。

cron说明

cron 一定有七位数,最后一位是年,SpringBoot 定时方案只需要设置六位即可:

  • 第一位, 表示秒, 取值是0 ~ 59
  • 第二位, 表示分. 取值是0 ~ 59
  • 第三位, 表示小时, 取值是0 ~ 23
  • 第四位, 表示天/日, 取值是0 ~ 31
  • 第五位, 表示月份, 取值是1 ~ 12
  • 第六位, 表示星期, 取值是1 ~ 7, 星期一,星期二…, 还有 1 表示星期日
  • 第七位, 年份, 可以留空, 取值是1970 ~ 2099

cron中,还有一些特殊的符号,含义如下:

  • (*) 星号,可以理解为每的意思,每秒、每分、每天、每月、每年…。
  • (?) 问号,问号只能出现在日期和星期这两个位置,表示这个位置的值不确定,每天 3 点执行,因此第六位星期的位置,是不需要关注的,就是不确定的值;同时,日期和星期是两个相互排斥的元素,通过问号来表明不指定值,比如 1 月 10 日是星期一,如果在星期的位置另指定星期二,就前后冲突矛盾了。
  • (-) 减号,表达一个范围,如在小时字段中使用“10 - 12”,则表示从 10 到 12 点,即 10、11、12。
  • (,) 逗号,表达一个列表值,如在星期字段中使用“1,2,4”,则表示星期一、星期二、星期四。
  • (/) 斜杠,如 x/y,x 是开始值,y 是步长,比如在第一位(秒),0/15 就是从 0 秒开始,每隔 15 秒执行一次,最后就是 0、15、30、45、60,另 */y,等同于 0/y。

举几个例子熟悉一下:

  • 0 0 3 * * ? :每天 3 点执行;
  • 0 5 3 * * ? :每天 3 点 5 分执行;
  • 0 5 3 ? * * :每天 3 点 5 分执行,与上面作用相同;
  • 0 5/10 3 * * ?:每天 3 点的 5 分、15 分、25 分、35 分、45 分、55分这几个时间点执行;
  • 0 10 3 ? * 1:每周星期天,3 点 10 分执行,注,1 表示星期天;
  • 0 10 3 ? * 1#3:每个月的第三个星期,星期天执行,# 号只能出现在星期的位置。

ok,spring的定时器就像如上这么简单,涉及到的几个注解:

@EnableScheduling:标注启动定时任务。
@Scheduled: 定义某个定时任务。

2、使用quartz实现定时任务

quartz 的设计者做了一个设计选择来从调度分离开作业。
quartz 中的触发器用来告诉调度程序作业什么时候触发,框架提供了一把触发器类型,但两个最常用的是 SimpleTrigger 和 CronTrigger。

SimpleTrigger 为需要简单打火调度而设计。典型地,如果你需要在给定的时间和重复次数或者两次打火之间等待的秒数打火一个作业,那么SimpleTrigger适合你。

另一方面,如果你有许多复杂的作业调度,那么或许需要CronTrigger。

什么是复杂调度?

当你需要在除星期六和星期天外的每天上午10点半执行作业时,那么应该使用CronTrigger。正如它的名字所暗示的那样,CronTrigger是基于Unix克隆表达式的。

开始之前需要了解的几个概念:

  • Job:是一个接口,只定义一个方法 execute(JobExecutionContext context),在实现接口的 execute 方法中编写所需要定时执行的 Job(任务),JobExecutionContext 类提供了调度应用的一些信息;Job 运行时的信息保存在 JobDataMap 实例中。
  • JobDetail:Quartz 每次调度 Job 时,都重新创建一个 Job 实例,因此它不接受一个 Job 的实例,相反它接收一个 Job 实现类(JobDetail,描述 Job 的实现类及其他相关的静态信息,如 Job 名字、描述、关联监听器等信息),以便运行时通过 newInstance() 的反射机制实例化 Job。
  • Trigger:是一个类,描述触发 Job 执行的时间触发规则,主要有 SimpleTrigger 和 CronTrigger 这两个子类,上边刚刚有提到。当且仅当需调度一次或者以固定时间间隔周期执行调度,SimpleTrigger 是最适合的选择;而 CronTrigger 则可以通过 Cron 表达式定义出各种复杂时间规则的调度方案:如工作日周一到周五的 15:00 ~ 16:00 执行调度等。
  • Scheduler:调度器就相当于一个容器,装载着任务和触发器,该类是一个接口,代表一个 Quartz 的独立运行容器,Trigger 和 JobDetail 可以注册到 Scheduler 中,两者在 Scheduler 中拥有各自的组及名称,组及名称是 Scheduler 查找定位容器中某一对象的依据,Trigger 的组及名称必须唯一,JobDetail 的组和名称也必须唯一(但可以和 Trigger 的组和名称相同,因为它们是不同类型的)。Scheduler 定义了多个接口方法,允许外部通过组及名称访问和控制容器中 Trigger 和 JobDetail。

上边的四个概念,建议通读一遍,结合下方代码,思路更清晰。

SpringBoot2.x 之后,完成了对 Quartz 自动化配置集成,省去了很多繁琐的配置,下面进入正题吧。

2.1、引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

2.2、yml配置

spring:
    quartz:
        # 任务信息存储至?MEMORY(内存方式:默认)、JDBC(数据库方式)
        job-store-type: jdbc
        properties:
            org:
                quartz:
                    jobStore:
                        misfireThreshold: 100

2.3、用于增删改查定时任务的Controller

/**
 * @author niceyoo
 */
@Slf4j
@RestController
@Api(description = "定时任务管理接口")
@RequestMapping("/tmax/quartzJob")
public class TmaxQuartzJobController {     /**
     * 定时任务service
     */
    @Autowired
    private QuartzJobService quartzJobService;     /**
     * 调度器
     */
    @Autowired
    private Scheduler scheduler;     @RequestMapping(value = "/add", method = RequestMethod.POST)
    @ApiOperation(value = "添加定时任务")
    public Result<Object> addJob(@ModelAttribute QuartzJob job){         add(job.getJobClassName(),job.getCronExpression(),job.getParameter());
        quartzJobService.save(job);
        return new ResultUtil<Object>().setSuccessMsg("创建定时任务成功");
    }     @RequestMapping(value = "/edit", method = RequestMethod.POST)
    @ApiOperation(value = "更新定时任务")
    public Result<Object> editJob(@ModelAttribute QuartzJob job){         delete(job.getJobClassName());
        add(job.getJobClassName(),job.getCronExpression(),job.getParameter());
        job.setStatus(CommonConstant.STATUS_NORMAL);
        quartzJobService.update(job);
        return new ResultUtil<Object>().setSuccessMsg("更新定时任务成功");
    }     @RequestMapping(value = "/pause", method = RequestMethod.POST)
    @ApiOperation(value = "暂停定时任务")
    public Result<Object> pauseJob(@ModelAttribute QuartzJob job){         try {
            scheduler.pauseJob(JobKey.jobKey(job.getJobClassName()));
        } catch (SchedulerException e) {
            throw new TmaxException("暂停定时任务失败");
        }
        job.setStatus(CommonConstant.STATUS_DISABLE);
        quartzJobService.update(job);
        return new ResultUtil<Object>().setSuccessMsg("暂停定时任务成功");
    }     @RequestMapping(value = "/resume", method = RequestMethod.POST)
    @ApiOperation(value = "恢复定时任务")
    public Result<Object> resumeJob(@ModelAttribute QuartzJob job){         try {
            scheduler.resumeJob(JobKey.jobKey(job.getJobClassName()));
        } catch (SchedulerException e) {
            throw new TmaxException("恢复定时任务失败");
        }
        job.setStatus(CommonConstant.STATUS_NORMAL);
        quartzJobService.update(job);
        return new ResultUtil<Object>().setSuccessMsg("恢复定时任务成功");
    }     @RequestMapping(value = "/delByIds/{ids}", method = RequestMethod.DELETE)
    @ApiOperation(value = "删除定时任务")
    public Result<Object> deleteJob(@PathVariable String[] ids){         for(String id:ids){
            QuartzJob job = quartzJobService.get(id);
            delete(job.getJobClassName());
            quartzJobService.delete(job);
        }
        return new ResultUtil<Object>().setSuccessMsg("删除定时任务成功");
    }     /**
     * 添加定时任务
     * @param jobClassName
     * @param cronExpression
     * @param parameter
     */
    public void add(String jobClassName, String cronExpression, String parameter){         try {
            ##启动调度器
            scheduler.start();             ##构建job信息
            JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass())
                    .withIdentity(jobClassName)
                    .usingJobData("parameter", parameter)
                    .build();             ##表达式调度构建器(即任务执行的时间) 使用withMisfireHandlingInstructionDoNothing() 忽略掉调度暂停过程中没有执行的调度
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();             ##按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName)
                    .withSchedule(scheduleBuilder).build();             scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            log.error(e.toString());
            throw new TmaxException("创建定时任务失败");
        } catch (Exception e){
            throw new TmaxException("后台找不到该类名任务");
        }
    }     /**
     * 删除定时任务
     * @param jobClassName
     */
    public void delete(String jobClassName){         try {
            scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName));
            scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName));
            scheduler.deleteJob(JobKey.jobKey(jobClassName));
        } catch (Exception e) {
            throw new TmaxException("删除定时任务失败");
        }
    }     public static Job getClass(String classname) throws Exception {
        Class<?> class1 = Class.forName(classname);
        return (Job)class1.newInstance();
    } }
2.4、Job执行的任务类
@Slf4j
public class SampleJob implements Job {     @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {         log.info(String.format("打印时间:"+ DateUtil.now()));
    }
}

4小步代码看完后,我们再来分析一波 quartz 的4个核心概念,先脑补一张图。

主要看 TmaxQuartzJobController 的 add() 方法

  • Job 为作业的接口,为任务调度的对象,在代码中体现为 ,代码中是由用户传递过来的类绝对路径;
  • JobDetail 用来描述 Job 的实现类及其他相关的静态信息,可以看到代码中通过 JobBuilder 的静态方法 newJob(Class jobClass) 生成 JobBuilder 实例,其中 jobClass 的获取采用反射机制;
  • Trigger 做为作业的定时管理工具,一个 Trigger 只能对应一个作业实例,而一个作业实例可对应多个触发器,代码中采用的是 CronTrigger,通过表达式调度构建器构建任务的执行时间,注意,任务的执行时间是由前台传递过来的 cron 表达式,然后按新的 cronExpression 表达式构建一个新的 trigger,TriggerBuilder.newTrigger().withIdentity(jobClassName) 表达了一个 Trigger 只能对应一个作业实例;
  • Scheduler 做为定时任务容器,是 Quartz 最上层的东西,它提携了所有触发器和作业,使它们协调工作,每个 Scheduler 都存有 JobDetail 和 Trigger 的注册,一个 Scheduler 中可以注册多个 JobDetail 和多个 Trigger。

最后,调用 Controller 层的任务添加方法 /add 完成全部,效果如下:

2019-05-23 00:38:38.455  INFO 19856 --- [eduler_Worker-1] club.sscai.tmax.quartz.jobs.SampleJob   : 打印时间:2019-05-24 09:38:38
2019-05-23 00:38:39.035  INFO 19856 --- [eduler_Worker-2] club.sscai.tmax.quartz.jobs.SampleJob   : 打印时间:2019-05-24 09:38:39
2019-05-23 00:38:40.752  INFO 19856 --- [eduler_Worker-3] club.sscai.tmax.quartz.jobs.SampleJob   : 打印时间:2019-05-24 09:38:40
2019-05-23 00:38:41.033  INFO 19856 --- [eduler_Worker-4] club.sscai.tmax.quartz.jobs.SampleJob   : 打印时间:2019-05-24 09:38:41
2019-05-23 00:38:42.640  INFO 19856 --- [eduler_Worker-5] club.sscai.tmax.quartz.jobs.SampleJob   : 打印时间:2019-05-24 09:38:42
2019-05-23 00:38:43.023  INFO 19856 --- [eduler_Worker-6] club.sscai.tmax.quartz.jobs.SampleJob   : 打印时间:2019-05-24 09:38:43

习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:niceyoo

定时任务 Scheduled quartz的更多相关文章

  1. spring 定时任务@Scheduled

    1.配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http:/ ...

  2. spring boot 学习(八)定时任务 @Scheduled

    SpringBoot 定时任务 @Scheduled 前言 有时候,我们有这样的需求,需要在每天的某个固定时间或者每隔一段时间让应用去执行某一个任务.一般情况下,可以使用多线程来实现这个功能:在 Sp ...

  3. Spring 定时任务Scheduled 开发详细图文

    Spring 定时任务Scheduled 开发 文章目录 一.前言 1.1 定时任务 1.2 开发环境 1.3 技术实现 二.创建包含WEB.xml 的Maven 项目 2.1 创建多模块项目task ...

  4. 【spring boot】使用定时任务@Scheduled 报错:Encountered invalid @Scheduled method 'dealShelf': Cron expression must consist of 6 fields (found 7 in "0 30 14 * * ? *")

    在spring boot中使用使用定时任务@Scheduled 报错: org.springframework.beans.factory.BeanCreationException: Error c ...

  5. Spring Boot 定时任务 @Scheduled

    项目开发中经常需要执行一些定时任务,比如在每天凌晨,需要从 implala 数据库拉取产品功能活跃数据,分析处理后存入到 MySQL 数据库中.类似这样的需求还有许多,那么怎么去实现定时任务呢,有以下 ...

  6. 【使用篇二】SpringBoot定时任务Scheduled(14)

    在日常项目运行中,我们总会有需求在某一时间段周期性的执行某个动作.比如每天在某个时间段导出报表,或者每隔多久统计一次现在在线的用户量.在springboot中可以有很多方案去帮我们完成定时器的工作,有 ...

  7. Spring定时任务(@Scheduled)

    一.使用Spring的@Scheduled实现定时任务[1] 1.Spring配置文件xmlns加入 xmlns:task="http://www.springframework.org/s ...

  8. SpringBoot执行定时任务@Scheduled

    SpringBoot执行定时任务@Scheduled 在做项目时,需要一个定时任务来接收数据存入数据库,后端再写一个接口来提供该该数据的最新的那一条. 数据保持最新:设计字段sign的值(0,1)来设 ...

  9. [转][JAVA]定时任务之-Quartz使用篇

    [BAT][JAVA]定时任务之-Quartz使用篇 定时任务之-Quartz使用篇 Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与 ...

随机推荐

  1. 对JavaEE的简单理解

    本文是在学习软件工程与J2EE课程时的学习笔记,旨在从大体的概念上了解Java EE的一些主要组件在Web应用中的作用. 上图精炼的描述了MVC模型以及Java EE的部分组件如何分布在一个Web应用 ...

  2. 56 容器(十)——Iterator迭代器遍历容器

    迭代器的获取 LIst与Set容器统一使用他们的对象.Iterator()方法获得迭代器对象,然后使用while循环配合迭代器的方法hasNext()及next()来遍历容器. List<Str ...

  3. SQL Join连接大小表在前在后的重要性(小表在前提高执行效率)

    引用地址:https://blog.csdn.net/qq_30349961/article/details/82662550 http://blog.sina.com.cn/s/blog_6ff05 ...

  4. 收藏单词TOEFL备份托福英语

    TOEFL托福词汇串讲(文本) alchemy(chem-化学)n. 炼金术 chemistry 化学 alder 赤杨树 联想:older 老人坐在赤杨树下 sloth 树懒 algae n.海藻 ...

  5. CDN 访问控制的那些事

    网络已经成为生活中必不可少的一部分,无论是清早手机翻看的新闻八卦,还是公交地铁里刷的停不下来的短视频,又或是你闲逛的购物网站,热追的电视剧,都与 CDN 有着密不可分的联系.无论你在互联网上做什么,或 ...

  6. Locust性能测试-环境准备与基本使用 转自:悠悠

    前言 提到性能测试,大部分小伙伴想到的就是LR和jmeter这种工具,小编一直不太喜欢写这种工具类的东西,我的原则是能用代码解决的问题,尽量不去用工具. python里面也有一个性能测试框架Locus ...

  7. python_socket (套接字)

    socket是计算机网络通信的基本的技术之一.如今大多数基于网络的软件,如浏览器,即时通讯工具甚至是P2P下载都是基于Socket实现的. 网络上两个程序通过一个双向的通信连接实现数据的交换,这个连接 ...

  8. python_dict json读写文件

    命令汇总: json.dumps(obj) 将python数据转化为json Indent实现缩进,ensure_ascii 是否用ascii解析 json.loads(s) 将json数据转换为py ...

  9. java之mybatis之占位符

    1.mybatis中有两种占位符 #{}和 ${}. 2. #{} 占位符是为了获取值,获取的值用在 where 语句后,insert 语句后,update 语句. #{} 获取值,是根据值的名称取值 ...

  10. [Linux学习--用户管理]centos中添加一个新用户,并授权

    前言 有时候给root用户不太方便,新建一个用于并赋予权限这个做法相对好些 创建新用户 创建一个用户名为:cmj [root@localhost ~]# adduser cmj 为这个用户初始化密码, ...