资讯专栏INFORMATION COLUMN

Spring 定时任务

justCoding / 1755人阅读

摘要:在定时器接口的方法中我们可以发现一个方法接受接口,而也是一个接口,抽象了触发任务执行的触发器。更常用的一个触发器是,它使用表达式指定何时执行任务。配置定时任务首先看看配置。配置提供了命名空间,让配置定时任务非常简单。

本文参考自Spring官方文档 34. Task Execution and Scheduling。

在程序中常常有定时任务的需求,例如每隔一周生成一次报表、每个月月末清空用户积分等等。Spring也提供了相应的支持,我们可以非常方便的按时执行任务。

项目准备

这里我使用Gradle来建立项目,然后在build.gradle中添加下面一行。springVersion的值是目前最新的Spring版本"4.3.7.RELEASE"。使用Maven的话也添加相应的行。spring-context会自动引入spring-core等几个最基本的依赖。

compile group: "org.springframework", name: "spring-context", version: springVersion

定时任务属于Spring的核心支持部分,所以我们不需要再添加其他的依赖了。所以定时任务功能既可以在命令行程序中使用,也可以在Java Web程序中使用。当然后者可能使用的更广泛一些(毕竟Web程序需要一直运行的嘛)。

这里我们定义两个任务,后面会让它们可以定时执行。

public interface IService {
    void doService();
}

public class SimpleService implements IService {
    @Override
    public void doService() {
        LocalTime time = LocalTime.now();
        System.out.println("This is a simple service:" + time);
    }
}

public class ExpensiveTaskService implements IService {
    @Override
    public void doService() {
        try {
            Thread.sleep(TimeUnit.SECONDS.toMillis(1));
            LocalTime time = LocalTime.now();
            System.out.println("This is an expensive task:" + time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
Spring的任务抽象 TaskExecutor

TaskExecutor接口是任务执行接口,类似于java.util.concurrent.Executor ,该接口只有一个方法execute(Runnable task),用于执行任务。

Spring提供了一组TaskExecutor的实现,详细列表可以看这里34.2.1. TaskExecutor types。要使用它们也很简单,直接注册为Spring Bean,然后注入到程序中即可使用。

TaskScheduler

TaskScheduler接口是定时器的抽象,它的源代码如下。可以看到,该接口包含了一组方法用于指定任务执行的时间。

public interface TaskScheduler {

    ScheduledFuture schedule(Runnable task, Trigger trigger);

    ScheduledFuture schedule(Runnable task, Date startTime);

    ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);

    ScheduledFuture scheduleAtFixedRate(Runnable task, long period);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);

}

Spring提供了两个实现,一是TimerManagerTaskScheduler,会将任务代理到CommonJ TimerManager实例。第二个是ThreadPoolTaskScheduler,当我们不需要管理线程的时候就可以使用该类。而且它还同时实现了TaskExecutor接口,所以一个ThreadPoolTaskScheduler实例即可同时用于执行定时任务。

Trigger

在定时器接口的方法中我们可以发现一个方法接受Trigger接口, 而Trigger也是一个接口,抽象了触发任务执行的触发器。

Trigger接口有两个实现,先说说比较简单的一个PeriodicTrigger。它直接按照给定的时间间隔触发任务执行。更常用的一个触发器是CronTrigger,它使用Cron表达式指定何时执行任务。下面是Spring官方的一个例子。

scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));

关于Cron表达式的信息可以参考这篇博客QuartZ Cron表达式。另外还有一个可以在线生成Cron表达式的网站:CroMaker,不过好像需要XX才能访问。而且好像Spring不支持第二个星期一这样的定时器设置,所以如果有这样的需求,需要使用Quartz。

配置任务

任务配置既可以使用Java配置,也可以使用XML配置。不管使用哪种方法,首先需要将要执行的方法所在的类配置为Spring Bean。例如下面就用XML配置注册了两个要执行的任务。

    
    
Java配置 定时任务

首先看看Java配置。我们需要在配置类上添加@EnableScheduling,如果需要异步的定时任务,还需要添加@Async。

@Configuration
@EnableAsync
@EnableScheduling
public class TaskConfiguration {
}

然后在要执行的方法上添加@Scheduled注解。@Scheduled注解有几个参数,任务会在相应参数的时间下执行。cron参数指定Cron表达式;fixedDelay指定任务执行的间隔,单位是毫秒;initialDelay指定当程序启动后多长时间开始执行第一次任务,单位是毫秒;zone指定任务执行时间所在的时区。下面的例子简单的指定了每隔一秒重复执行一次任务。

public class SimpleService implements IService {
    @Scheduled(fixedDelay = 1000)
    @Override
    public void doService() {
        LocalTime time = LocalTime.now();
        System.out.println("This is a simple service:" + time);
    }
}
异步任务

然后是异步任务,如果任务执行时间比较长的话,我们可以考虑使用异步的任务。当调用异步任务的时候,异步方法直接返回,异步任务会交由相应的任务执行器来执行。在Spring中标记异步方法很简单,直接在方法上使用@Async注解。如果需要指定异步方法使用的执行器,可以向注解传递执行器的名称。异步方法可以返回空值。

@Async("otherExecutor")
void doSomething(String s) {
    // this will be executed asynchronously by "otherExecutor"
}

但是如果异步方法想返回其他值的话,就必须使用Future。不过不仅是java.util.concurrent.Future,异步方法还可以返回Spring的org.springframework.util.concurrent.ListenableFuture和JDK8的java.util.concurrent.CompletableFuture类型。

@Async
Future returnSomething(int i) {
    // this will be executed asynchronously
}

异步方法不仅可以用于定时任务中,在Spring的其他地方也可以使用。例如Spring Data JPA可以使用@Async编写异步的查询方法。

需要注意,异步方法没有对应的XML配置,如果我们想让方法是异步的,只能使用注解。当然也不是完全不行,不过就比较麻烦了,你需要使用AsyncExecutionInterceptor和AOP配合才能达到类似的效果。

如果需要处理异步方法的异常,我们需要实现一个AsyncUncaughtExceptionHandler。下面的异步异常处理器简单的打印异常信息。

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        ex.printStackTrace();
    }
}

然后通过实现AsyncConfigurer接口(Java配置方式)或者task:annotation-driven(XML配置方式)的exception-handler元素来配置。

XML配置

Spring提供了task命名空间,让配置定时任务非常简单。

定时器

task:scheduler会注册一个ThreadPoolTaskScheduler 定时器,它只有一个属性线程池大小。默认是1,我们需要根据任务的数量指定一个合适的大小。

    
执行器

task:executor会注册一个ThreadPoolTaskExecutor执行器,我们可以使用它的相关属性来配置该执行器。默认情况下执行队列是无限的,可能会导致JVM使用完所有内存。因此我们最好指定一个确定的数值。还有一个rejection-policy属性,指定执行器队列满时的执行策略:默认是AbortPolicy,直接抛出异常;如果当系统忙时丢弃某些任务是可接受的,可以使用DiscardPolicyDiscardOldestPolicy策略;当系统负载较重时还可以使用CallerRunsPolicy,它不会将任务交给执行器线程,而是让调用者线程来执行该任务。最后一个就是keep-alive属性,也就是超出线程池数量 线程完成任务之后的存活时间,单位是秒。

    
执行任务

执行任务很简单,使用指定要执行的Bean和方法即可。

    
        
        
    

要设置定时的话,只需要指定相应的属性即可。


    
    
    


Quartz集成

Quartz是一个定时任务的库。Spring也提供了它的支持。Quartz的使用方法请查阅相应文档。这里只简单介绍一下。

Spring的Quartz集成在spring-context-support包中,它还需要Spring事务的支持。因此我们需要下面这样的依赖声明。

    compile group: "org.springframework", name: "spring-tx", version: springVersion
    compile group: "org.springframework", name: "spring-context-support", version: springVersion
    compile group: "org.quartz-scheduler", name: "quartz", version: "2.2.3"
定义任务

Quartz的任务需要继承Quartz的Job接口。所以一个典型的任务可以写成这样。

public class QuartzService implements IService, Job {
    @Override
    public void doService() {
        System.out.println("This is a quartz service");
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("Do something in execute method of quartz");
    }
}
JobDetailFactoryBean

JobDetailFactoryBean用来定义实现了Job接口的任务。如果需要添加更多信息,可以使用jobDataAsMap属性设置。


    
    
        
            
        
    
MethodInvokingJobDetailFactoryBean

如果任务没有实现Job接口,也可以执行,这时候需要使用MethodInvokingJobDetailFactoryBean。如果存在任务对象,使用targetObject属性,如果有任务类,使用targetClass属性。


    
    
    
触发器

有了任务,就可以定义触发器了。触发器有两个:SimpleTriggerFactoryBean,以指定的间隔重复执行任务;CronTriggerFactoryBean,以给定的Cron表达式执行任务。Quartz的Cron表达式比Spring 的强大,它支持第几个星期几这样的Cron表达式。


    
    
    


    
    
执行任务

有了触发器,我们就可以执行任务了。注册一个SchedulerFactoryBean,然后将触发器的Bean引用传入即可。


    
        
            
            
        
    

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66830.html

相关文章

  • Java 定时任务系列(2)-Spring 定时任务的几种实现

    本文来自网络一些博客的整理(包括gong1208的博客 dary1715的博客) 1、简介 这个系列介绍Spring框架实现定时任务的两种方式以及一些高级的用法,包括: 1、使用Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂,稍后会详细介绍。 2、Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且...

    roadtogeek 评论0 收藏0
  • Spring Boot(四)Spring Boot 中的定时任务

    摘要:对于定时任务类如果不定义线程池,控制台输出如下,可以看到不同的定时任务是同一个线程在执行在配置类或者启动类中增加定时任务的线程池控制台输出本节示例代码已上传到 一.SpringBoot中开启定时任务 在spirngboot中使用定时任务非常简单,只需要在启动类上增加一个@EnableScheduling注解即可。 @SpringBootApplication @EnableSchedu...

    firim 评论0 收藏0
  • Spring定时任务高级使用篇

    摘要:定时任务高级使用篇前面一篇博文之定时任务基本使用篇介绍了环境下,定时任务的简单使用姿势,也留了一些问题,这一篇则希望能针对这些问题给个答案定时任务进阶篇问题小结前面一篇博文,抛出了下面的几个问题,接下来则围绕问题进行分析一个项目中有多个定时 showImg(https://segmentfault.com/img/remote/1460000015880327); Spring定时任务...

    dcr309duan 评论0 收藏0
  • 分布式定时任务组件

    摘要:基于的分布式任务调度组件,非常小巧,使用简单,只需要引入包。单个任务节点故障时自动转移到其他任务节点继续执行。和都是一整套的定时任务框架,没有必要强行将集成进来,专注做的分布式以及动态任务的封装。是之后自主开发的定时任务工具。 基于Spring Task + Zookeeper的分布式任务调度组件,非常小巧,使用简单,只需要引入jar包。不需要单独部署服务端。确保所有任务在集群中不重...

    Mertens 评论0 收藏0
  • spring-boot | 多线程并发定时任务

    摘要:多线程并发定时任务刚刚看了下实现定时任务的文章,感觉还不错。存在问题但是后来发现个问题,通过同时测试几个任务发现,所有的任务都是在同一个线程池中的同一个线程来完成的。 spring-boot | 多线程并发定时任务 刚刚看了下Spring Boot实现定时任务的文章,感觉还不错。Spring Boot 使用Spring自带的Schedule来实现定时任务变得非常简单和方便。在这里个大家...

    silenceboy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<