项目开发中经常需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息。Spring为我们提供了异步执行任务调度的方式,提供TaskExecutor 、TaskScheduler 接口。
SpringBoot中使用两个注解:@EnableScheduling、@Scheduled来简单实现定时任务。
【1】@Scheduled注解
按照惯例,先看javadoc源码:
- 使一个方法定时被执行的注解。其属性cron/fixedDelay/fixedRate必须有一个被指定
- 该注解标记的方法没有参数,也没有返回值。即使写了返回值,也会被忽略。
- 该注解被ScheduledAnnotationBeanPostProcessor处理器解析。
- 可以手动注册该bean,当然更方便的是使用
<task:annotation-driven/>
配置或者@EnableScheduling注解。
这个注解可以用来作为一个meta-annotation
通过属性覆盖去创建自定义的组合注解。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(Schedules.class) public @interface Scheduled { // cron表达式 String cron() default ""; //corn表达式的时区,默认为空,使用服务器的local time zone。 String zone() default ""; // 周期为在最后一次调用结束和下一次调用开始,以毫秒为单位,固定周期地执行注释的方法。 long fixedDelay() default -1; //周期间隔为在最后一次调用结束和下一次调用开始,以毫秒为单位,固定周期地执行注释的方法。 String fixedDelayString() default ""; //固定周期地执行注解方法,周期为调用间隔,单位为毫秒 long fixedRate() default -1; String fixedRateString() default ""; // fixedRate() 或fixedDelay()第一次执行前延迟的毫秒数 long initialDelay() default -1; // fixedRate() 或fixedDelay()第一次执行前延迟的毫秒数 String initialDelayString() default ""; }
【2】@EnableScheduling 注解
启动Spring的定时任务执行能力,通常用在配置类上面,类似于xml片段<task:*>
。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(SchedulingConfiguration.class) @Documented public @interface EnableScheduling { }
该注解会使Spring 容器中所有bean中的@Scheduled注解起作用。如下所示,给定一个类MyTask :
package com.myco.tasks; public class MyTask { // 每1000ms 执行一次该方法 @Scheduled(fixedRate=1000) public void work() { // task execution logic } }
如下配置确保MyTask.work()每1000ms
被调用一次。
@Configuration @EnableScheduling public class AppConfig { @Bean public MyTask task() { return new MyTask(); } }
如果MyTask使用了注解@Component
,下面的配置将会确保其@Scheduled标注的方法会被以期望的间隔进行调用。
@Configuration @EnableScheduling @ComponentScan(basePackages="com.myco.tasks") public class AppConfig { }
甚至可以直接在配置类中写@Scheduled注解标注的方法。
@Configuration @EnableScheduling public class AppConfig { * @Scheduled(fixedRate=1000) public void work() { // task execution logic } }
默认,将会寻找一个相关联的scheduler ,可能是上下文中一个唯一的TaskScheduler或者一个bean id为taskScheduler。ScheduledExecutorService也将执行同样的查找。如果上面两个定时任务处理器都没有被找到,一个本地单线程默认scheduler 将会被创建并被注册器使用。
如果想获得更多的控制,配置类应该实现SchedulingConfigurer
接口。这将可以获取底层的ScheduledTaskRegistrar
实例。
下面的例子演示了如何自定义Executor去执行定时任务。
@Configuration @EnableScheduling public class AppConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskExecutor()); } @Bean(destroyMethod="shutdown") public Executor taskExecutor() { return Executors.newScheduledThreadPool(100); } }
注意上面例子中@Bean(destroyMethod="shutdown")的使用。这将确保线程任务执行器在Spring容器被销毁时正确的关闭。实现SchedulingConfigurer还可以通过ScheduledTaskRegistrar对任务注册实细粒度
@Configuration @EnableScheduling public class AppConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskScheduler()); taskRegistrar.addTriggerTask( new Runnable() { public void run() { myTask().work(); } }, new CustomTrigger() ); } @Bean(destroyMethod="shutdown") public Executor taskScheduler() { return Executors.newScheduledThreadPool(42); } @Bean public MyTask myTask() { return new MyTask(); } }
作为参考,可以将上面的示例与下面的Spring XML配置进行比较
<beans> <task:annotation-driven scheduler="taskScheduler"/> <task:scheduler id="taskScheduler" pool-size="42"/> <task:scheduled-tasks scheduler="taskScheduler"> <task:scheduled ref="myTask" method="work" fixed-rate="1000"/> </task:scheduled-tasks> <bean id="myTask" class="com.foo.MyTask"/> </beans>
更多参考Scheduled、SchedulingConfiguration、SchedulingConfigurer、ScheduledTaskRegistrar、Trigger以及ScheduledAnnotationBeanPostProcessor。
【3】代码实例
Service如下(这里使用cron表达式):
@Service public class ScheduledService { // 每天的整分调用一次该方法 @Scheduled(cron = "0 * * * * 0-7") public void hello(){ System.out.println("hello..."); } }
在主程序上添加@EnableScheduling
注解如下:
@SpringBootApplication @EnableScheduling public class SpringBoot01CacheApplication { public static void main(String[] args) { SpringApplication.run(SpringBoot01CacheApplication.class, args); } }
fixedRate ,fixedDelay 和initialDelay
代码示例如下:
//上一次 项目启动时间点之后 5 秒执行一次 @Scheduled(fixedRate = 5000) public void fixedRate(){ System.out.println("fixedRate: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } //上一次 结束时间点之后 每50秒执行一次 @Scheduled(fixedDelay = 50000) public void fixedDelay(){ for (int i = 0; i < 10; i++){ System.out.println("fixedDelay" + i + new SimpleDateFormat("HH:mm:ss").format(new Date())); } } // initialDelay :fixedRate() 或fixedDelay()第一次执行前延迟的毫秒数 //第一次延迟50秒执行,之后按照fixedRate的规则每6秒执行一次 @Scheduled(initialDelay = 50000,fixedRate = 6000) public void initialDelayFixedRate (){ for (int i = 0; i < 10; i++){ System.out.println("initialDelayFixedRate :" + i + new SimpleDateFormat("HH:mm:ss").format(new Date()) ); } }
【4】cron表达式
cron表达式属性如下:
second(秒), minute(分), hour(时),day of month(日),month(月),day of week(周几)
字段与值表格如下:
特殊符号释义如下:
关于L的说明
L是"last"的缩写,只能在 “日”和“周”中使用。在“日”中设置,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年), 在“周”上表示周六,相当于”7”或”SAT”。如果在”L”前加上数字,则表示该数据的最后一个。例如在“周”上设置”7L”这样的格式,则表示“本月最后一个周六”。
关于W的说明
表示离指定日期的最近那个工作日(周一至周五)触发,只能在 “日” 中使用且只能用在具体的数字之后。若在“日”上置 15W,表示离每月15号最近的那个工作日触发。假如15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发,如果15号正好在工作日(周一至周五),则就在该天触发。如果是 1W 就只能往本月的下一个最近的工作日推不能跨月往上一个月推。
关于#的说明
用于指定某个月份的第几个周几,只能作用于 “周” 上。例如,3#1
表示该月份的第一个星期二,而 5#2
表示该月份的第二个星期四。
示例如下:
0 0 5-22/2 * * ? --- 每天5点到22点,每两小时执行一次 0 0 0 * * * -- 每天零时执行一次 0 0/15 14,18 * * ? -- 每天14点整和18点整,每隔15分钟执行一次 0 15 10 ? * 1-6 -- 每个月的周一到周六 10:15分执行一次 0 0 2 ? * 6L -- 每个月的最后一个周六凌晨2点执行一次 0 0 2 LW * ? -- 每个月的最后一个工作日凌晨2点执行一次 0 0 2-4 ? * 1#1 -- 每个月的第一个周一凌晨2点到4点期间,每个整点都执行一次
实现定时任务的方式很多,其他的有JDK 自带的 TimerTask or Timer、使用开源作业调度框架 Quartz、线程池 ScheduleExecutorService
和其实现类ScheduledThreadPoolExecutor
。