SpringBoot使用@Scheduled注解实现定时任务
某些场景需要我们在某个特定的时间做某件事情
业务场景:
- 进行数据备份
- 每天定时进行缓存预热
- 博客平台定时发送文章
- 电商平台超过 15 分钟就结束订单
。。。。。。。。。。。。。。。。。。
- 定时任务:在指定时间点执行特定的任务,比如每天半夜缓存预热,更新缓存;数据备份;清理日志等等
- 延时任务:在一定的延时时间后执行特定的任务,例如半小时后删除某样数据等
二者共同点均是在某个未来的时间点来进行某项系统操作。
使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:
- 1、基于注解(@Scheduled)
- 2、基于接口(SchedulingConfigurer) 前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了。
- 3、基于注解设定多线程定时任务
基于注解(@Scheduled、@EnableScheduling)
注解 @Scheduled 默认为单线程,任务的执行时机会受上一个任务执行时间的影响。
@EnableScheduling :在需要配置的定时任务的类上使用,springboot 项目直接加在启动类上
@Scheduled:声明这是一个定时任务的方法,在需要设置定时任务的方法上使用
示例
-
springboot 项目在启动类上加上注解 @EnableScheduling,开启定时任务管理
package com.pb.usercenterbackend; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @MapperScan("com.pb.usercenterbackend.mapper") @EnableScheduling public class UserCenterBackendApplication { public static void main(String[] args) { SpringApplication.run(UserCenterBackendApplication.class, args); } }
-
创建定时任务,在需要执行的任务方法上加上注解 @Scheduled,这里是进行在凌晨 5点进行 Redis 缓存预热任务。
package com.pb.usercenterbackend.job; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pb.usercenterbackend.model.domain.User; import com.pb.usercenterbackend.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; /** * 缓存预热 */ @Slf4j @Component public class PreCacheJob { @Resource private UserService userService; //重点用户,可以在数据库进行标识,每次都查询重点用户即可 private List<Long> mainUserList = Arrays.asList(1L); @Resource private RedisTemplate redisTemplate; //每天执行,缓存预热用户 @Scheduled( cron = "0 0 5 * * *") public void doCacheRecommendUsers(){ Page<User> userPage = userService.page(new Page<>(1, 20)); for (Long userId : mainUserList) { String redisKey = String.format("zhihe:user:recommed:%s",userId); try { redisTemplate.opsForValue().set(redisKey,userPage,100000, TimeUnit.MILLISECONDS); } catch (Exception e) { log.error("redis set error",e); } } } }
参数
注解源码
package org.springframework.scheduling.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
String CRON_DISABLED = "-";
String cron() default "";
String zone() default "";
long fixedDelay() default -1L;
String fixedDelayString() default "";
long fixedRate() default -1L;
String fixedRateString() default "";
long initialDelay() default -1L;
String initialDelayString() default "";
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
1. cron
接收一个 cron tab表达式,设置定时任务的时间,可直接在网页自动生成,不做赘述。
参考网址:crontab执行时间计算 - 在线工具 (tool.lu)
2. zone
时区,接收一个 java.util.TimeZone#ID。cron 表达式会基于该时区进行解析,不设置默认是一个空字符串,取当前服务器所在时区。比如一般使用的 Asia/Shanghai。
3. fixedDelay
上一次执行完毕时间点之后多长时间执行
4. fixedDelayString
和 3 相同,只是将 数字形式变为字符串形式,唯一不同是支持占位符
使用
//pom 配置
time:
fixedDelay: 5000
//定时任务
@Component
public class Task
{
@Scheduled(fixedDelayString = "${time.fixedDelay}")
void testFixedDelayString()
{
System.out.println("欢迎访问博客 " + System.currentTimeMillis());
}
}
5. fixedRate
上一次开始执行时间点之后多长时间再执行。
@Scheduled(fixedRate = 5000) //上一次开始执行时间点之后5秒再执行
6. fixedRateString
与 5 相同
7. initialDelay
第一次延迟多长时间后再执行。
@Scheduled(initialDelay=1000, fixedRate=5000) //第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次
8. initialDelayString
与 8 相同。
基于注解设定多线程定时任务
注解 @Scheduled 默认为单线程任务,所以要开启多线程任务时
-
基于上述单线程注解的配置下,在需要设置定时任务的类上使用,springboot项目在启动类上添加注解 @EnableAsync
-
在需要设置多线程定时任务的每个方法上,在基于单线程基础上添加注解 @Async
@Async @Scheduled(fixedDelay = 1000) //间隔1秒 public void first() throws InterruptedException { System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName()); System.out.println(); Thread.sleep(1000 * 10); } @Async @Scheduled(fixedDelay = 2000) public void second() { System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName()); System.out.println(); }
注意:开启了多线程之后,需要注意可能会出现重复操作,导致数据异常,需要做其他处理。