【spring-boot】 springboot整合quartz实现定时任务

在做项目时有时候会有定时器任务的功能,比如某某时间应该做什么,多少秒应该怎么样之类的。

spring 支持多种定时任务的实现。我们来介绍下使用 spring 的定时器和使用 quartz 定时器

  1. 我们使用 spring-boot 作为基础框架,其理念为零配置文件,所有的配置都是基于注解和暴露 bean 的方式。

  2. 使用 spring 的定时器:

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

@Component
@Configurable
@EnableScheduling
public class ScheduledTasks{
@Scheduled(fixedRate </span>= 1000 * 30<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> reportCurrentTime(){
    System.out.println (</span>"Scheduling Tasks Examples: The time is now " + dateFormat ().format (<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date ()));
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">每1分钟执行一次</span>
@Scheduled(cron = "0 */1 *  * * * "<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> reportCurrentByCron(){
    System.out.println (</span>"Scheduling Tasks Examples By Cron: The time is now " + dateFormat ().format (<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date ()));
}

</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> SimpleDateFormat dateFormat(){
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> SimpleDateFormat ("HH🇲🇲ss"<span style="color: rgba(0, 0, 0, 1)">);
}

}

          没了,没错,使用 spring 的定时任务就这么简单,其中有几个比较重要的注解:

   @EnableScheduling:标注启动定时任务。

          @Scheduled(fixedRate = 1000 * 30)  定义某个定时任务。

  3. 使用 quartz 实现定时任务。
    Quartz 设计者做了一个设计选择来从调度分离开作业。Quartz 中的触发器用来告诉调度程序作业什么时候触发。框架提供了一把触发器类型,但两个最常用的是 SimpleTrigger 和 CronTrigger。SimpleTrigger 为需要简单打火调度而设计。典型地,如果你需要在给定的时间和重复次数或者两次打火之间等待的秒数打火一个作业,那么 SimpleTrigger 适合你。另一方面,如果你有许多复杂的作业调度,那么或许需要 CronTrigger。
    CronTrigger 是基于 Calendar-like 调度的。当你需要在除星期六和星期天外的每天上午 10 点半执行作业时,那么应该使用 CronTrigger。正如它的名字所暗示的那样,CronTrigger 是基于 Unix 克隆表达式的。

    使用 quartz 说使用的 maven 依赖。

    

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>1.8.4</version>
</dependency>

    由于我们使用的是 spring-boot 框架,其目的是做到零配置文件,所以我们不使用 xml 文件的配置文件来定义一个定时器,而是使用向 spring 容器暴露 bean 的方式。

    向 spring 容器暴露所必须的 bean

    

@Configuration
public class SchedledConfiguration {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配置中设定了
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ① targetMethod: 指定需要定时执行scheduleInfoAction中的simpleJobTest()方法
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ② concurrent:对于相同的JobDetail,当指定多个Trigger时, 很可能第一个job完成之前,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第二个job就开始了。指定concurrent设为false,多个job不会并发运行,第二个job将不会在第一个job完成之前开始。
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ③ cronExpression:0/10 * * * * ?表示每10秒执行一次,具体可参考附表。
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ④ triggers:通过再添加其他的ref元素可在list中放置多个触发器。 scheduleInfoAction中的simpleJobTest()方法</span>
@Bean(name = "detailFactoryBean"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MethodInvokingJobDetailFactoryBean detailFactoryBean(ScheduledTasks scheduledTasks){
    MethodInvokingJobDetailFactoryBean bean </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MethodInvokingJobDetailFactoryBean ();
    bean.setTargetObject (scheduledTasks);
    bean.setTargetMethod (</span>"reportCurrentByCron"<span style="color: rgba(0, 0, 0, 1)">);
    bean.setConcurrent (</span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> bean;
}

@Bean(name </span>= "cronTriggerBean"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> CronTriggerBean cronTriggerBean(MethodInvokingJobDetailFactoryBean detailFactoryBean){
    CronTriggerBean tigger </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> CronTriggerBean ();
    tigger.setJobDetail (detailFactoryBean.getObject ());
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
        tigger.setCronExpression (</span>"0/5 * * * * ? "<span style="color: rgba(0, 0, 0, 1)">);//每5秒执行一次
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (ParseException e) {
        e.printStackTrace ();
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> tigger;

}

@Bean
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> SchedulerFactoryBean schedulerFactory(CronTriggerBean[] cronTriggerBean){
    SchedulerFactoryBean bean </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SchedulerFactoryBean ();
    System.err.println (cronTriggerBean[</span>0<span style="color: rgba(0, 0, 0, 1)">]);
    bean.setTriggers (cronTriggerBean);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> bean;
}<br>}<br></span></pre>

    MethodInvokingJobDetailFactoryBean:此工厂主要用来制作一个 jobDetail,即制作一个任务。由于我们所做的定时任务根本上讲其实就是执行一个方法。所以用这个工厂比较方便。

      注意:其setTargetObject所设置的是一个对象而不是一个类。

    CronTriggerBean:定义一个触发器。

      注意:setCronExpression:是一个表达式,如果此表达式不合规范,即会抛出异常。

    SchedulerFactoryBean:主要的管理的工厂,这是最主要的一个 bean。quartz 通过这个工厂来进行对各触发器的管理。

  4. 对 quartz 的封装

    由上面代码可以看出来,此处我们设置的是一个固定的 cronExpression,那么,做为项目中使用的话,我们一般是需要其动态设置比如从数据库中取出来。

    其实做法也很简单,我们只需要定义一个Trigger来继承CronTriggerBean。顶用其setCronExpression方法即可。

    那么另外一个问题,如果我们要定义两个定时任务则会比较麻烦,需要先注入一个任务工厂,在注入一个触发器。

       为了减少这样的配置,我们定义了一个抽象的超类来继承CronTriggerBean

    具体代码如下:

    

public abstract class BaseCronTrigger extends CronTriggerBean implements Serializable {
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">long</span> serialVersionUID = 1L<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> init(){
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 得到任务</span>
    JobDetail jobdetail = <span style="color: rgba(0, 0, 255, 1)">new</span> JobDetail (<span style="color: rgba(0, 0, 255, 1)">this</span>.getClass ().getSimpleName (),<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.getMyTargetObject ().getClass ());
    </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.setJobDetail (jobdetail);
    </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.setJobName (jobdetail.getName ());
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setName (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.getClass ().getSimpleName ());
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
        </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setCronExpression (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.getMyCronExpression ());
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (java.text.ParseException e) {
        e.printStackTrace ();
    }

}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">abstract</span><span style="color: rgba(0, 0, 0, 1)"> String getMyCronExpression();

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">abstract</span><span style="color: rgba(0, 0, 0, 1)"> Job getMyTargetObject();

}

    其 init()方法,来为这个触发器绑定任务。其任务为一个 Job 类型的,也就是说其执行的任务为实现了 Job 接口的类,这个任务会有一个 execute() 方法,来执行任务题。

    

public class ScheduledTasks implements Job {
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> execute(JobExecutionContext context) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> JobExecutionException{
    System.out.println (</span>"Scheduling Tasks Examples By Cron: The time is now " + dateFormat ().format (<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date ()));
}

private SimpleDateFormat dateFormat(){
return new SimpleDateFormat ("HH🇲🇲ss");
}
}

    为了给触发器添加任务,我们需要在子类中调用 init() 方法,由于 spring 容器注入时是使用的空参的构造函数,所以我们在此构造函数中调用 init()方法。

    

@Component
public class InitializingCronTrigger extends BaseCronTrigger implements Serializable {
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">long</span>    serialVersionUID = 1L<span style="color: rgba(0, 0, 0, 1)">;

@Autowired
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> SchedulerFactoryBean schedulerFactoryBean;

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> InitializingCronTrigger() {
    init ();
}

@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getMyCronExpression(){
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> "0/5 * * * * ?"<span style="color: rgba(0, 0, 0, 1)">;
}

@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Job getMyTargetObject(){
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ScheduledTasks ();
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> parse(){
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
        schedulerFactoryBean.getObject ().pauseAll ();
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (SchedulerException e) {
        e.printStackTrace ();
    }
}

}

    此时我们只需要在配置类中加入一个配置就可以了。

    

  @Bean
    public SchedulerFactoryBean schedulerFactory(CronTriggerBean[] cronTriggerBean){
        SchedulerFactoryBean bean = new SchedulerFactoryBean ();
        System.err.println (cronTriggerBean[0]);
        bean.setTriggers (cronTriggerBean);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> bean;
}</span></pre>

 

  4. 介绍一个 cronExpression表达式。

    这一部分是摘抄的:

      

字段 允许值 允许的特殊字符
  0-59   , - * /
  0-59   , - * /
小时   0-23   , - * /
日期   1-31   , - *   / L W C
月份   1-12 或者 JAN-DEC   , - * /
星期   1-7 或者 SUN-SAT   , - *   / L C #
年(可选)   留空, 1970-2099   , - * /

  
如上面的表达式所示: 
“*”字符被用来指定所有的值。如:”*“在分钟的字段域里表示“每分钟”。 
“-”字符被用来指定一个范围。如:“10-12”在小时域意味着“10 点、11 点、12 点”。
 
“,”字符被用来指定另外的值。如:“MON,WED,FRI”在星期域里表示”星期一、星期三、星期五”. 
“?”字符只在日期域和星期域中使用。它被用来指定“非明确的值”。当你需要通过在这两个域中的一个来指定一些东西的时候,它是有用的。看下面的例子你就会明白。 
“L”字符指定在月或者星期中的某天(最后一天)。即“Last ”的缩写。但是在星期和月中“L”表示不同的意思,如:在月子段中“L”指月份的最后一天 -1 月 31 日,2 月 28 日,如果在星期字段中则简单的表示为“7”或者“SAT”。如果在星期字段中在某个 value 值得后面,则表示“某月的最后一个星期 value”, 如“6L”表示某月的最后一个星期五。
“W”字符只能用在月份字段中,该字段指定了离指定日期最近的那个星期日。
“#”字符只能用在星期字段,该字段指定了第几个星期 value 在某月中

每一个元素都可以显式地规定一个值(如 6),一个区间(如 9-12),一个列表(如 9,11,13)或一个通配符(如 *)。“月份中的日期”和“星期中的日期”这两个元素是互斥的,因此应该通过设置一个问号(?)来表明你不想设置的那个字段。表 7.1 中显示了一些 cron 表达式的例子和它们的意义:

表达式

 意义
"0 0 12 * * ?"   每天中午 12 点触发
"0 15 10 ? * *"   每天上午 10:15 触发
"0 15 10 * * ?"   每天上午 10:15 触发
"0 15 10 * * ? *"   每天上午 10:15 触发
"0 15 10 * * ? 2005"   2005年的每天上午10:15触发
"0 * 14 * * ?"   在每天下午 2 点到下午 2:59 期间的每 1 分钟触发
"0 0/5 14 * * ?"   在每天下午2点到下午2:55期间的每5分钟触发
"0 0/5 14,18 * * ?"   在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
"0 0-5 14 * * ?"   在每天下午 2 点到下午 2:05 期间的每 1 分钟触发
"0 10,44 14 ? 3 WED"   每年三月的星期三的下午 2:10 和 2:44 触发
"0 15 10 ? * MON-FRI"   周一至周五的上午 10:15 触发
"0 15 10 15 * ?"   每月 15 日上午 10:15 触发
"0 15 10 L * ?"   每月最后一日的上午 10:15 触发
"0 15 10 ? * 6L"   每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6L 2002-2005"   2002 年至 2005 年的每月的最后一个星期五上午 10:15 触发
"0 15 10 ? * 6#3"   每月的第三个星期五上午10:15触发

            每天早上 6 点 0 6 * * * 

            每两个小时 0 */2 * * * 

            晚上 11 点到早上 8 点之间每两个小时,早上八点 0 23-7/2,8 * * * 

                                                     每个月的 4 号和每个礼拜的礼拜一到礼拜三的早上 11 点 0 11 4 * 1-3 

            1 月 1 日早上 4 点 0 4 1 1 *