SpringBoot中实现定时任务的两种方式
以前的笔记用OneNote写的,传到了博客园很不方便换成了Markdown
在Spring+SpringMVC环境中要实现定时任务一共有两种解决方案:
- 使用Spring自带的定时任务处理
@Scheduled
注解 - 使用第三方框架Quartz
@Schedule
- 创建SpringBoot项目并导入
Web依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 在SpringBoot的启动类里加上
@EnableScheduling
注解表示开启一个定时任务
@SpringBootApplication
@EnableScheduling
public class ScheduleApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduleApplication.class, args);
}
}
- 配置你需要的定时任务
@Service
public class HelloService {
@Scheduled(fixedDelay = 2000)
public void fixedDelay(){
System.out.println("fixedDelay>>"+new Date());
}
@Scheduled(fixedRate = 2000)
public void fixedRate() {
System.out.println("fixedRate>>" + new Date());
}
//表示每五秒执行一次
@Scheduled(cron = "0/5 * * * * ?")
public void cron(){
System.out.println("cron>>" + new Date());
}
}
关于上述代码的简单叙述:
-
fixedDelay
表示前一次任务结束
到后一次任务结束
的间隔2秒
-
fixedRate
表示两次任务开始时间的间隔
,即每两秒执行一次任务,有可能前一次任务还没运行完,下一次任务就已经要开始执行了 -
initialDelay
表示首次任务启动的延迟时间
。 -
单位都是毫秒
-
cron
表达式格式:* * * * * * *
[秒][分][时][日][月][周][年]
序号 | 说明 | 是否必填 | 允许填写的值 | 允许的通配符 |
---|---|---|---|---|
1 | 秒 | 是 | 0~59 | - * / |
2 | 分 | 是 | 0~59 | - * / |
3 | 时 | 是 | 0~23 | - * / |
4 | 日 | 是 | 1~31 | - * ? / L W |
5 | 月 | 是 | 1-12 or JAN-DEC | - * / |
6 | 周 | 是 | 1-7 or SUN-SAT | - * ? / L # |
7 | 年 | 否 | 1970-2099 | - * / |
注:值得注意的是,**月份中的日期和星期可能会起冲突,因此在配置时这两个得有一个是
?`**
?
表示不指定值,即不关心某个字段的取值使用
。需要注意的是,月份中的日期和星期可能会起冲突,因此在配置时这两个得有一个是
?`*
表示所有值
,例如:在秒的字段上设置*
,表示每一秒都会触发,
用来区分多个值,例如在周字段上设置MON,WED,FRI
表示周一,周三和周五触发-
表示区间
,例如在秒字段上设置10-12
,则表示10,11,12秒都会触发/
表示递增触发
,如在秒字段上设置5/15
表示从5秒开始,每15秒触发(5,20,35)#
表示每个月的第几个周几
,例如在周字段上设置6#3
表示在每月的第三个周六,(用在父亲节和母亲节再合适不过了)- 周字段的设置,若是使用英文是不区分大小写的,即MON与mon效果相同
L
表示最后的意思
。在日字段上设置,表示当月的最后一天
(依据当前月份,如果是二月还会自动判断是否是闰年),在周字段上表示星期六
,相等于7
或SAT(
注意周日算是第一天)。如果在
L前加上数字,则表示该数据的最后一个。例如:在周字段上设置
6L这样的格式,则表示
本月最后一个星期五`W
表示离指定日期的最近工作日(周一至周五)
,例如在日字段上设置15W
,表示离每月15号最近的工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发,如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,”W”前只能设置具体的数字,不允许区间”-“)L
和W
可以一组合使用。如果在日字段上设置”LW”,则表示在本月的最后一个工作日触发(一般指发工资 )
Quartz
一般在项目中,除非定时任务涉及到的业务实在是太简单,使用 @Scheduled 注解来解决定时任务,否则大部分情况可能都是使用 Quartz 来做定时任务。在 Spring Boot 中使用 Quartz ,只需要在创建项目时,添加 Quartz 依赖即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 在启动类加上
@EnableScheduling
注解表示开启定时任务
@SpringBootApplication
@EnableScheduling
public class QuartzApplication {
public static void main(String[] args) {
SpringApplication.run(QuartzApplication.class, args);
}
}
-
Quartz 在使用过程中,有两个关键概念,
一个是JobDetail(要做的事情)
,另一个是触发器(什么时候做)
;要定义 JobDetail,需要先定义 Job。Job 的定义有两种方式:
2.1. 第一种直接定义一个bean
@Component public class MyFirstJob { public void firstJob(){ System.out.println("my first job say hello:" + new Date()); } }
关于上述代码的两点叙述:
2.1.1.
@Component
表示把这个类注册到Spring容器中去 2.1.2. 第一种方法无法接受参数
2.2. 第二种定义方式,则是继承QuartzJobBean并实现默认方法
@Component public class MySecondJob extends QuartzJobBean { private String name; @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { System.out.println("my second job say hello" + name + ":" + new Date()); } }
注:第二种方式相较于第一种方式,优点在于定时任务可以传参。其参数可以是任务类型
-
有了
Job类(具体要做的事情)
之后就需要JobDetail(要做的事情)
和Trigger触发器(什么时候做)
3.1.
无参定时任务的JobDetail和Trigger触发器
/** * 无参的JobDetail * @return */ @Bean MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean(){ MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean(); //定时任务的bean的名字,这是之所以可以写成“myFirstJob”是因为我们已经将它注册到Spring容器中去了,可以写成首字母小写的形式 bean.setTargetBeanName("myFirstJob"); //定时任务的方法 bean.setTargetMethod("firstJob"); return bean; } /** * 无参的Trigger触发器 * @return */ @Bean SimpleTriggerFactoryBean simpleTriggerFactoryBean(){ SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean(); bean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject()); //开始的时间,立马开始 bean.setStartTime(new Date()); //2秒重复一次 bean.setRepeatInterval(2000); //重复3次,如果不给参数代表一直重复下去 bean.setRepeatCount(3); return bean; } @Bean SchedulerFactoryBean schedulerFactoryBean(){ SchedulerFactoryBean bean = new SchedulerFactoryBean(); //将Trigger触发器传入用于执行,可以传入一个或多个参数 bean.setTriggers(simpleTriggerFactoryBean().getObject()); return bean; }
3.2.
带参定时任务的JobDetail和Trigger触发器
/** * 带参的JobDetail * @return */ @Bean JobDetailFactoryBean jobDetailFactoryBean(){ JobDetailFactoryBean bean = new JobDetailFactoryBean(); HashMap<String, String> map = new HashMap<>(); map.put("name", "fern"); //定时任务所需的参数 bean.setJobDataAsMap(map); //bean bean.setJobClass(MySecondJob.class); return bean; } /** * 带参的Trigger触发器 * @return */ @Bean CronTriggerFactoryBean cronTriggerFactoryBean(){ CronTriggerFactoryBean bean = new CronTriggerFactoryBean(); bean.setJobDetail(jobDetailFactoryBean().getObject()); //每十秒触发一次 bean.setCronExpression("0/10 * * * * ?"); return bean; } @Bean SchedulerFactoryBean schedulerFactoryBean(){ SchedulerFactoryBean bean = new SchedulerFactoryBean(); //将Trigger触发器传入用于执行,可以传入一个或多个参数 bean.setTriggers(cronTriggerFactoryBean().getObject()); return bean; }
关于这个配置总结如下几点:
- JobDetail 的配置有
两种方式
:MethodInvokingJobDetailFactoryBean
和JobDetailFactoryBean
。 - 使用
MethodInvokingJobDetailFactoryBean 可以配置目标 Bean 的名字和目标方法的名字
,这种方式不支持传参
。 - 使用
JobDetailFactoryBean 可以配置 JobDetail
,任务类继承自 QuartzJobBean ,这种方式支持传参,将参数封装在 JobDataMap 中进行传递。
- Trigger 是指触发器,Quartz 中定义了多个触发器,这里向大家展示其中两种的用法,
SimpleTrigger
和CronTrigger
。 - SimpleTrigger 有点类似于前面说的 @Scheduled 的基本用法。
- CronTrigger 则有点类似于 @Scheduled 中 cron 表达式的用法。
效果图: