本文参考自Spring官方文档 34. Task Execution and Scheduling。html
在程序中经常有定时任务的需求,例如每隔一周生成一次报表、每月月末清空用户积分等等。Spring也提供了相应的支持,咱们能够很是方便的按时执行任务。java
这里我使用Gradle来创建项目,而后在build.gradle
中添加下面一行。springVersion的值是目前最新的Spring版本'4.3.7.RELEASE'
。使用Maven的话也添加相应的行。spring-context会自动引入spring-core等几个最基本的依赖。spring
compile group: 'org.springframework', name: 'spring-context', version: springVersion
定时任务属于Spring的核心支持部分,因此咱们不须要再添加其余的依赖了。因此定时任务功能既能够在命令行程序中使用,也能够在Java Web程序中使用。固然后者可能使用的更普遍一些(毕竟Web程序须要一直运行的嘛)。异步
这里咱们定义两个任务,后面会让它们能够定时执行。async
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(); } } }
TaskExecutor
接口是任务执行接口,相似于java.util.concurrent.Executor
,该接口只有一个方法execute(Runnable task)
,用于执行任务。ide
Spring提供了一组TaskExecutor的实现,详细列表能够看这里34.2.1. TaskExecutor types。要使用它们也很简单,直接注册为Spring Bean,而后注入到程序中便可使用。gradle
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
实例便可同时用于执行定时任务。ui
在定时器接口的方法中咱们能够发现一个方法接受Trigger接口, 而Trigger也是一个接口,抽象了触发任务执行的触发器。this
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配置注册了两个要执行的任务。
<bean id="simpleService" class="yitian.study.service.SimpleService"/> <bean id="expensiveTaskService" class="yitian.study.service.ExpensiveTaskService"/>
首先看看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<String> 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
元素来配置。
Spring提供了task命名空间,让配置定时任务很是简单。
task:scheduler
会注册一个ThreadPoolTaskScheduler
定时器,它只有一个属性线程池大小。默认是1,咱们须要根据任务的数量指定一个合适的大小。
<task:scheduler id="threadPoolTaskScheduler" pool-size="10"/>
task:executor
会注册一个ThreadPoolTaskExecutor
执行器,咱们可使用它的相关属性来配置该执行器。默认状况下执行队列是无限的,可能会致使JVM使用完全部内存。所以咱们最好指定一个肯定的数值。还有一个rejection-policy
属性,指定执行器队列满时的执行策略:默认是AbortPolicy
,直接抛出异常;若是当系统忙时丢弃某些任务是可接受的,可使用DiscardPolicy
或DiscardOldestPolicy
策略;当系统负载较重时还可使用CallerRunsPolicy
,它不会将任务交给执行器线程,而是让调用者线程来执行该任务。最后一个就是keep-alive
属性,也就是超出线程池数量 线程完成任务以后的存活时间,单位是秒。
<task:executor id="threadPoolTaskExecutor" pool-size="10" queue-capacity="10"/>
执行任务很简单,使用<task:scheduled-tasks>
指定要执行的Bean和方法便可。
<task:scheduled-tasks> <task:scheduled ref="simpleService" method="doService" cron="*/1 * * * * *"/> <task:scheduled ref="expensiveTaskService" method="doService" cron="*/2 * * * * *"/> </task:scheduled-tasks>
要设置定时的话,只须要指定相应的属性便可。
<task:scheduled-tasks scheduler="myScheduler"> <task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/> <task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/> <task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/> </task:scheduled-tasks> <task:scheduler id="myScheduler" pool-size="10"/>
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用来定义实现了Job接口的任务。若是须要添加更多信息,可使用jobDataAsMap
属性设置。
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass" value="yitian.study.service.QuartzService"/> <property name="jobDataAsMap"> <map> <entry key="timeout" value="10"/> </map> </property> </bean>
若是任务没有实现Job接口,也能够执行,这时候须要使用MethodInvokingJobDetailFactoryBean。若是存在任务对象,使用targetObject
属性,若是有任务类,使用targetClass
属性。
<bean id="methodJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="quartzService"/> <property name="targetMethod" value="doService"/> <property name="concurrent" value="true"/> </bean>
有了任务,就能够定义触发器了。触发器有两个:SimpleTriggerFactoryBean
,以指定的间隔重复执行任务;CronTriggerFactoryBean
,以给定的Cron表达式执行任务。Quartz的Cron表达式比Spring 的强大,它支持第几个星期几这样的Cron表达式。
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"> <property name="jobDetail" ref="jobDetail"/> <property name="startDelay" value="0"/> <property name="repeatInterval" value="1000"/> </bean> <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="methodJobDetail"/> <property name="cronExpression" value="*/2 * * * * ?"/> </bean>
有了触发器,咱们就能够执行任务了。注册一个SchedulerFactoryBean
,而后将触发器的Bean引用传入便可。
<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger"/> <ref bean="simpleTrigger"/> </list> </property> </bean>