SpringBoot定时任务详解
记录
使用springboot创建定时任务非常简单,目前主要有以下三种方式创建:
一、基于注解(@Scheduled)
二、基于接口(SchedulingConfigurer)
三、基于注解设定多线程定时任务
一、静态:基于注解
基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响,通过在配置类注解@EnableScheduling来开启对定时任务的支持,然后在要执行计划任务的方法上注解@Scheduled,声明这是需执行的定时任务
- 创建定时器
使用SpringBoot基于注解来创建定时任务非常简单,只需几行代码就能完成
@Configuration//主要用于标记配置类,兼备Component的效果
@EnableScheduling//开启定时任务
public class StaticScheduleTask{
//添加定时任务
@Scheduled(cron="0/5 * * * * ?")
//或直接指定时间间隔,例如:5秒
//@Scheduled(fixedRate=5000)
private void configureTasks(){
System.err.println("执行静态定时任务时间:"+LocalDateTime.now());
}
}
Cron表达式参数分别表示:
秒(0~59)例如0/5表示每5秒
分(0~59)
时(0~23)
日(0~31)的某天,需计算
月(0~11)
周(可填1-7或SUN/MON/TUE/WED/THU/FRI/SAT)
@Scheduled:除了支持灵活的参数表达式cron之外,还支持简单的延时操作,例如fixedDelay,fixedRate填写相应的毫秒数即可
cron表达式:依据业务需求,可设定具体的任务执行时间,预定时间一到就会自动执行;
cron一共有7位,但是最后一位是年,可以留空,一般情况可以写6位:
* 第一位,表示秒,取值0-59
* 第二位,表示分,取值0-59
* 第三位,表示小时,取值0-23
* 第四位,日期天/日,取值1-31
* 第五位,日期月份,取值1-12
* 第六位,星期,取值1-7,星期一,星期二...,注:不是第1周,第二周的意思,另外:1表示星期天,2表示星期一。
* 第7为,年份,可以留空,取值1970-2099
cron中,还有一些特殊的符号,含义如下:
(*)星号:可以理解为每的意思,每秒,每分,每天,每月,每年...
(?)问号:问号只能出现在日期和星期这两个位置。
(-)减号:表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12
(,)逗号:表达一个列表值,如在星期字段中使用“1,2,4”,则表示星期一,星期二,星期四
(/)斜杠:如:x/y,x是开始值,y是步长,比如在第一位(秒) 0/15就是,从0秒开始,每15秒,最后就是0,15,30,45,60 另:*/y,等同于0/y
Cron表达式范例
每搁5秒执行一次:*/5 * * * * ?
每搁一分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月一号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点执行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
缺点:多个定时任务使用同一个调度线程,所以任务是阻塞执行,执行效率低;所有的任务都是在同一线程池中的同一个线程来完成
优点:使用与单个后台线程执行周期任务,并且保证按照预定时间执行
但是当我们调整了执行周期的时候,需要重启应用才能生效,这多少有些不方便,为了达到实时生效的效果,可以使用接口来完成定时任务
二、动态:基于接口
实现SchedulingConfigure接口,重写configureTasks方法,添加需执行的定时任务;
- 导入依赖包
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.0.4.RELEASE</version>
</parent>
<dependencies>
<dependency><!--添加Web依赖 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!--添加MySql依赖 -->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency><!--添加Mybatis依赖 配置mybatis的一些初始化的东西-->
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency><!-- 添加mybatis依赖 -->
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
<scope>compile</scope>
</dependency>
</dependencies>
- 添加数据库数据(随便打开查询窗口,执行操作脚本内容),然后在项目中的application.yml添加数据源
spring
datasource:
url:jdbc:mysql://localhost:3306/数据库名称
username:root
password:root
- 创建定时器
数据库准备好数据之后,我们编写定时任务,这里添加的是TriggerTask,目的是循环读取我们在数据库设置好的执行周期,以及执行相关定时任务的内容
@Configuration//主要用于标记配置类,兼备component的效果
@EnableScheduling//开启定时任务
public class DynamicScheduleTask implements SchedulingConfigurer{
@Mapper
public interface CronMapper{
@Select("select cron from cron limit 1")
public String getCron();
}
@Autowired//注入mapper
@SuppressWarnings("all")
CronMapper cronMapper;
//执行定时任务
@Override
public void configureTasks(ScheduledTask taskRegistrar){
taskRegistar.addTriggerTask{
//添加任务内容(Runnable)
//其中configureTasks()是需要执行的任务
()->System.out.println("执行动态定时任务:"+LocalDateTime.now().toLocalTime()),
//设置执行周期(Trigger)
triggerContext->{
//从数据库获取执行周期
String cron=cronMapper.getCron();
//合法性校验
//cron=0/5 * * * * ?
if(StringUtils.isEmpty(cron)){
}
//返回执行周期(Date)
return new CronTrigger(cron).nextExecutiontime(triggerContext);
}
}
}
}//启动了springboot之后就会发现每次执行的时间差是数据库中设定的定时任务
注意:如果在数据库修改时格式出现错误,则定时任务会停止,即使重新修改正确,此时只能重新启动项目才能恢复
三、多线程定时任务
添加@EnableAsync开启对异步的支持
添加@Async注解,表示该定时任务是异步执行的,如果没有配置线程池的话会默认使用SimpleAsyncTaskExecutor,这个异步执行器每次都会开启一个子线程执行,性能消耗比较大
//@Component注解用于对那些比较中立的类进行注释
//相对于在持久层、业务层和控制层分别采用@Repository,@Service和@Controller对分层中的类进行注释
@Component
@EnableScheduling//开启定时任务
@EnableAsync//开启多线程
public class MultithreadScheduleTask {
@Async
@Scheduled(fixedDelay = 1000)//间隔1秒
public void first() throws InterruptedException{
System.out.println("第一个定时任务开始:"+ LocalDateTime.now().toLocalTime()+"\r\n线程:"+Thread.currentThread());
System.out.println();
Thread.sleep(1000*10);
}
@Async
@Scheduled(fixedDelay = 2000)
public void second(){
System.out.println("第二个定时任务开始:"+LocalDateTime.now().toLocalTime()+"\r\n线程:"+Thread.currentThread());
}
}