专栏名称: Java专栏
一个Java、Python、数据库、中间件、业内资讯、面试、学习资源等干货的知识分享社区。
目录
相关文章推荐
51好读  ›  专栏  ›  Java专栏

SpringBoot定时任务(schedule、quartz)

Java专栏  · 公众号  ·  · 2021-01-12 12:20

正文



原文链接: https://www.cnblogs.com/jing99/p/11546559.html


Scheduled


只适合处理简单的计划任务,不能处理分布式计划任务。优势:是spring框架提供的计划任务,开发简单,执行效率比较高。且在计划任务数量太多的时候,可能出现阻塞,崩溃,延迟启动等问题。

Scheduled定时任务是spring3.0版本之后自带的一个定时任务。其所属Spring的资源包为:spring-context-support。所以需要使用Scheduled定时任务机制时,需要在工程中依赖对应资源,具体如下:



<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-context-supportartifactId>
dependency>


如果在spring应用中需要启用Scheduled定时任务,则需要在启动类上增加注解@EnableScheduling,代表启用Scheduled定时任务机制。具体如下:


@SpringBootApplication
@EnableScheduling
public class AppStarter {

  public static void main(String[] args) {
    SpringApplication.run(AppStarter.class, args);
  }
}


Scheduled定时任务的核心在于注解@Scheduled,这个注解的核心属性是cron,代表定时任务的触发计划表达式。这个表达式的格式为:


@Scheduled(cron="seconds minutes hours day month week")




@Scheduled(cron="seconds minutes hours day month week year")


推荐使用第一种表达式形式,因为在很多其他技术中都有不同的定时任务机制,其中用于设置触发计划的表达式都是第一种cron表达式。第二种表达式不能说是Spring Scheduled特有的,也是只有少数技术支持的。


cron表达式中,每个位置的约束如下:



星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如,*在分钟字段时,表示“每分钟”;


问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于占位符;


减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12;


逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;


斜杠(/):x/y表达一个等步长序列,x为起始值,y为增量步长值。如在秒数字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y;

L:该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号;如果L用在星期中,则表示星期六,等同于7。但是,如果L出现在星期字段里,而且在前面有一个数值X,则表示“这个月的最后X天”,例如,6L表示该月的最后星期五;

W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二。但必须注意关联的匹配日期不能够跨月,如你指定1W,如果1号是星期六,结果匹配的是3号星期一,而非上个月最后的那天。W字符串只能指定单一日期,而不能指定日期范围;


LW组合:在日期字段可以组合使用LW,它的意思是当月的最后一个工作日;

井号(#):该字符只能在星期字段中使用,表示当月某个工作日。如6#3表示当月的第三个星期五(6表示星期五,#3表示当前的第三个),而4#5表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发;

C:该字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。


Cron表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。

计划任务Scheduled是通过一个线程池实现的。是一个多线程的调度。SpringBoot会初始化一个线程池,线程池默认大小为1,专门用于执行计划任务。每个计划任务启动的时候,都从线程池中获取一个线程执行,如果发生异常,只是执行当前任务的线程发生异常,而不是计划任务调度线程发生异常。如果当前定时任务还未执行完成,当相同的定时任务又进入到执行周期时,不会触发新的定时任务。如:


@Scheduled(cron="* * * * * ?"




    
)
public void test1(){
    Random r = new Random();
    /*int i = r.nextInt(100);
    if(i % 3 == 0){
        throw new RuntimeException("error");
    }*/

    System.out.println(Thread.currentThread().getName() + " cron=* * * * * ? --- " + new Date());
    try{
        Thread.sleep(2000);
    }catch(Exception e){
        e.printStackTrace();
    }
}


如结果所示(每次的线程名称一致,由于前一个定时任务未执行完成,因此造成后一个任务的推迟,而不是1秒执行一次,而是3秒):


pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:20 CST 2019
pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:23 CST 2019
pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:26 CST 2019
pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:29 CST 2019


quartz


Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。


Quartz是一个完全由java编写的开源作业调度框架。不要让作业调度这个术语吓着你。尽管Quartz框架整合了许多额外功能, 但就其简易形式看,你会发现它易用得简直让人受不了!


在开发Quartz相关应用时,只要定义了Job(任务),Trigger(触发器)和Scheduler(调度器),即可实现一个定时调度能力。其中Scheduler是Quartz中的核心,Scheduler负责管理Quartz应用运行时环境,Scheduler不是靠自己完成所有的工作,是根据Trigger的触发标准,调用Job中的任务执行逻辑,来完成完整的定时任务调度。


Job - 定时任务内容是什么。
Trigger - 在什么时间上执行job。
Scheduler - 维护定时任务环境,并让触发器生效。


在SpringBoot中应用Quartz,需要依赖下述资源:



        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-context-supportartifactId>
        dependency>
        
        <dependency>
            <groupId>org.quartz-schedulergroupId>
            <artifactId>quartzartifactId>
            <version>2.2.1version>
            
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-apiartifactId>
                    <groupId>org.slf4jgroupId>
                exclusion>
            exclusions>
        dependency>
        
        <dependency >
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-txartifactId>
        dependency>
dependencies>


启动器添加注解@EnableScheduling:


/**
 * @EnableScheduling 必要
 * 开启定时任务机制。
 */

@SpringBootApplication
@EnableScheduling
public class AppStarter {

    public static void main(String[] args) {
        SpringApplication.run(AppStarter.class, args);
    }
}


定义JOB任务以及JOB任务调用的模拟业务对象:


public class SpringBootQuartzJobDemo implements Job {

    // 用于模拟任务中的业务对象。也可能是数据访问对象,或其他类型的对象。
    @Autowired
    private CommonsUtil4Quartz commonsUtil4Quartz;
    
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("SpringBootQuartzJobDemo : " + new Date());
        this.commonsUtil4Quartz.testMethod();
    }

}


@Component
public class CommonsUtil4Quartz {

    public void testMethod(){
        System.out.println("CommonsUtil4Quartz testMethod run...");
    }
}


创建Trigger以及JobDetail对象,并用Schedule配置定时任务:


/**
 * 初始化类
 * Quartz环境初始化。
 *
 */

@Configuration
public class QuartzConfiguration {

    /**
     * 创建Job对象。在Spring环境中,创建一个类型的对象的时候,很多情况下,都是通过FactoryBean来间接创建的。
     * 如果有多个Job对象,定义多次方法。
     *
     * 在JobDetailFactoryBean类型中,用于创建JobDetail对象的方法,其底层使用的逻辑是:Class.newInstance()
     * 也就是说,JobDetail对象不是通过Spring容器管理的。
     * 因为Spring容器不管理JobDetail对象,那么Job中需要自动装配的属性,就无法实现自动状态。如上JOB的第10行会报空指针异常。
     *
     * 解决方案是:将JobDetail加入到Spring容器中,让Spring容器管理JobDetail对象。
     * 需要重写Factory相关代码。实现Spring容器管理JobDetail。
     * @return
     */

    @Bean
    public JobDetailFactoryBean initJobDetailFactoryBean(){
        JobDetailFactoryBean factoryBean =
                new JobDetailFactoryBean();
        // 提供job类型。
        factoryBean.setJobClass(SpringBootQuartzJobDemo.class);
        
        return factoryBean;
    }
    
    /**
     * 管理Trigger对象
     * CronTrigger - 就是Trigger的一个实现类型。其中用于定义周期时间的是CronSchedulerBuilder
     * 实际上,CronTrigger是用于管理一个Cron表达式的类型。
     * @param jobDetailFactoryBean - 上一个方法初始化的JobDetailFactoryBean
     * @return
     */

    @Bean(name="cronTriggerFactoryBean1")
    public CronTriggerFactoryBean initCronTriggerFactoryBean(
            )
{
        CronTriggerFactoryBean factoryBean =
                new CronTriggerFactoryBean();
        
        JobDetailFactoryBean jobDetailFactoryBean = this.initJobDetailFactoryBean();
        
        factoryBean.setJobDetail(jobDetailFactoryBean.getObject());
        
        factoryBean.setCronExpression("0/3 * * * * ?");
        
        return factoryBean;
    }
    
    /**
     * 初始化Scheduler
     * @param cronTriggerFactoryBean - 上一个方法初始化的CronTriggerFactoryBean
     * @return
     */

    @Bean
    public SchedulerFactoryBean initSchedulerFactoryBean(
            CustomJobFactory customJobFactory,
            CronTriggerFactoryBean[] cronTriggerFactoryBean)
{
        SchedulerFactoryBean factoryBean =
                new SchedulerFactoryBean();
        CronTrigger[] triggers = new CronTrigger[cronTriggerFactoryBean.length];
        for(int i = 0; i < cronTriggerFactoryBean.length; i++){
            triggers[i] = cronTriggerFactoryBean[i].getObject();
        }
        // 注册触发器,一个Scheduler可以注册若干触发器。
        factoryBean.setTriggers(triggers);
        // 为Scheduler设置JobDetail的工厂。可以覆盖掉SpringBoot提供的默认工厂,保证JobDetail中的自动装配有效。
        factoryBean.setJobFactory(customJobFactory);
        
        return factoryBean;
    }
    
}


重写JobFactory:


/**
 * 重写的工厂对象。
 */

@Component
public class CustomJobFactory extends AdaptableJobFactory {

    /**
     * AutowireCapableBeanFactory : 简单理解为Spring容器,是Spring容器Context的一个Bean对象管理工程。
     * 可以实现自动装配逻辑,和对象创建逻辑。
     * 是SpringIoC容器的一个重要组成部件。
     */

    @Autowired
    private AutowireCapableBeanFactory autowireCapableBeanFactory;
    
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 通过父类型中的方法,创建JobDetail对象。
        Object obj = super






请到「今天看啥」查看全文