定时调度是指在无人值守的时候,系统可以在某一时刻执行某些特定的功能而采用的一种机制。本文总结三种实现任务调度的方法
1.使用@Scheduled 实现调度
本方法主要通过@EnableScheduling和?@Scheduled两个注解实现调度, 两个注解并不是springboot增加的注解,而是springframwork的注解,由spring-context包提供
实现方式如下:
- 启动主类不需要做其他特殊处理直接使用@SpringBootApplication即可,并不需要其他注解
- 调度类ScheduledTaskService 首先是一个bean,然后在方法上使用@EnableScheduling开启任务调度
- 使用@Scheduled标注配置调度任务方法,其中支持三个属性fixedRate? 、fixedDelay 、cron?
@SpringBootApplication
public class SchedulerApplication {
public static void main(String[] args) {
SpringApplication.run(SchedulerApplication.class, args);
}
}
1.fixedRate是按照一定的速率执行
? ? ? ?此调度是从上一次方法执行开始的时间算起,如果上一次方法阻塞住了,下一次也是不会执行,但是在阻塞这段时间内累计应该执行的次数,当不再阻塞时,一下子把这些全部执行掉,而后再按照固定速率继续执行
@Component
@EnableScheduling
public class ScheduledTaskService {
private static final SimpleDateFormat dataFormat = new SimpleDateFormat("HH:mm:ss");
@Scheduled(fixedRate = 5000)
//@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS) //使用timeUnit设置时间单位
public void reportCurrentTime() throws InterruptedException {
System.out.println("间隔5秒执行:" + dataFormat.format(new Date()));
Thread.sleep(6000);
System.out.println("间隔5秒结束:" + dataFormat.format(new Date()));
}
}
启动SpringBoot查看执行结果:可见先前的任务结束后不会再等间隔
间隔5秒执行:19:55:00
间隔5秒结束:19:55:06
间隔5秒执行:19:55:06
间隔5秒结束:19:55:12
间隔5秒执行:19:55:12
间隔5秒结束:19:55:18
2.fixedDelay控制方法执行的间隔时间
? ? ? 此调度方式是以上一次方法执行完开始算起,如上一次方法执行阻塞住了,那么直到上一次执行完,并间隔给定的时间后,执行下一次
@Component
@EnableScheduling
public class ScheduledTaskService {
private static final SimpleDateFormat dataFormat = new SimpleDateFormat("HH:mm:ss");
//@Scheduled(fixedDelay = 5000)
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) //可以使用timeUnit设置时间单位
public void reportCurrentTime2() throws InterruptedException {
System.out.println("间隔5秒执行:" + dataFormat.format(new Date()));
Thread.sleep(6000);
System.out.println("间隔5秒结束:" + dataFormat.format(new Date()));
}
}
启动SpringBoot查看执行结果:上一次执行5秒后才会执行
间隔5秒执行:20:00:55
间隔5秒结束:20:01:01
间隔5秒执行:20:01:06
间隔5秒结束:20:01:12
间隔5秒执行:20:01:17
间隔5秒结束:20:01:23
3. 使用cron表达式定制任务
执行的方式是与fixedDelay相近的,也是会按照上一次方法结束时间开始算起, 调度代码如下
@Component
@EnableScheduling
public class ScheduledTaskService {
@Scheduled(cron = "0/3 * * * * ?")
public void fixTimeExecution() {
System.out.println("指定时间执行:" + dataFormat.format(new Date()));
}
}
启动SpringBoot查看执行结果:执行间隔是每3秒打印一次
指定时间执行:20:06:30
指定时间执行:20:06:33
指定时间执行:20:06:36
指定时间执行:20:06:39
指定时间执行:20:06:42
指定时间执行:20:06:45
其中 cron表达式,如* * * * * * 由六个空格分隔的时间和日期字段组成,每个字段都有自己的有效值范围:
┌───────────── second (0-59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
│ │ │ │ │ ┌───────────── day of the week (0 - 7)
│ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN)
│ │ │ │ │ │
* * * * * *
- @Scheduled cron只支持6位,分别是:秒、分、时、日、月、周
- @Scheduled cron不支持第7位“年”,即不支持配置这样的格式,例如“0/1 * * * * ? 2099”?
- @Scheduled(cron = "-")? ?当配置为“-” 时,表示不启动不启动任务,从corn的属性标注可以看到此信息
- @Scheduled(cron = "${myjob.cron}")? 方式从配置文件中获取表达式配置,从corn的属性标注可以看到此信息
* <p>The special value {@link #CRON_DISABLED "-"} indicates a disabled cron
* trigger, primarily meant for externally specified values resolved by a
* <code>${...}</code> placeholder.
* @return an expression that can be parsed to a cron schedule
* @see org.springframework.scheduling.support.CronExpression#parse(String)
*/
String cron() default "";
2.使用XML 实现Scheduler任务
实现方式如下:
- 使用在XML文件中定义JobDetailFactoryBean等信息,
- 使用@ImportResource注解将XML配置的Bean加载到容器中,
? ? ? 一般低版本的Spring项目中调度都配置到XML中,所以当将spring项目迁移成springboot项目的时候使用此方法是比较省事比较快捷的方式,
@ImportResource(locations= {"classpath:spring-job.xml"})
@SpringBootApplication
public class SchedulerApplication {
public static void main(String[] args) {
SpringApplication.run(SchedulerApplication.class, args);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" default-lazy-init="false">
<bean id="autoCancelProduct" class="com.boot.basic.scheduled.AutoCancelProduct">
</bean>
<bean id="autoCancelProductDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="name" value="autoCancelProductDetail"/>
<property name="group" value="Order_Trigger_Group"/>
<property name="targetObject" ref="autoCancelProduct"/>
<property name="targetMethod" value="executeJob"/>
<property name="concurrent" value="false"/>
</bean>
<bean id="autoCancelProductTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="name" value="autoCancelProductTrigger"/>
<property name="group" value="Order_Trigger_Group"/>
<property name="jobDetail" ref="autoCancelProductDetail"/>
<property name="cronExpression" value="* 0/2 * * * ? 2099"/>
</bean>
<bean id="cancelSchedue" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="autoCancelProductTrigger"/>
</list>
</property>
</bean>
</beans>
3.使用@Configuration实现Scheduler
实现方式
- Spring3.0 通过定义@Configuration来替换xml ?所以job, jobdetail,jobtrigger,schedule等四个对象都可以通过java类实现定义,参考源码如下
@Configuration
public class AutoOrderProductConfig {
@Bean("autoOrderPorduct")
public AutoOrderProduct getAutoOrderProduct() {
return new AutoOrderProduct();
}
@Bean("jobDetail")
public MethodInvokingJobDetailFactoryBean getMethodInvokingJobDetailFactoryBean(AutoOrderProduct autoOrderProduct) {
MethodInvokingJobDetailFactoryBean factoryBean = new MethodInvokingJobDetailFactoryBean();
factoryBean.setTargetObject(autoOrderProduct);
factoryBean.setTargetMethod("executeJob");
factoryBean.setConcurrent(false);
return factoryBean;
}
@Bean("jobTrigger")
public CronTriggerFactoryBean cronTriggerFactoryBean() {
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setName("jobTrigger");
cronTriggerFactoryBean.setGroup("Order_Trigger_Group");
cronTriggerFactoryBean.setCronExpression("0/2 * * * * ?");
cronTriggerFactoryBean.setJobDetail(SpringUtil.getBean("jobDetail"));
return cronTriggerFactoryBean;
}
@Bean("myScheduler")
public SchedulerFactoryBean getSchedulerFactoryBean() {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setTriggers((CronTriggerImpl)SpringUtil.getBean("jobTrigger"));
return schedulerFactoryBean;
}
}
4.参考测试代码
源码地址:springboot/basic at master · PNZBEIJINGL/springboot · GitHub
5.常见问题?
1.CronTriggerImpl cannot be cast to class [Lorg.quartz.Trigger
一般是由于范型转换引起的,详细查看下面文章
链接:ClassCastException: XXX are in unnamed module of loader ‘app‘异常分析
2.@Scheduled注解不起作用
- spring的注解@Scheduled 需要写在实现方法上任务方法不能有返回值(如果有返回值,spring初始化的时候会告诉你有个错误、需要设定一个proxytargetclass的某个值为true),不能指向任何的参数;
- 如果该方法需要与应用程序上下文的其他对象进行交互,通常是通过依赖注入来实现;
- 实现类上要有组件的注解@Component。
3.@Scheduled配置cron报错IllegalStateException
Caused by: java.lang.IllegalStateException: Encountered invalid @Scheduled method 'doexec': Cron expression must consist of 6 fields (found 7 in "1 0 * * * ? 2022")
at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.processScheduled(ScheduledAnnotationBeanPostProcessor.java:462)
at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.postProcessAfterInitialization(ScheduledAnnotationBeanPostProcessor.java:332)
从报错信息看@Scheduled初始化的时候表达式1 0 * * * ? 2022不正确, 原因是@Scheduled中cron配置只支持6位, 不支持第7位年配置, 所以不能限制某年调度。如果想暂时不适用调度可以配置“-”
? ?
? ?上一篇:SpringBoot 06 集成Nacos配置中心以及常见问题
|