文章目录
在项目中我们可能会遇到这样一个场景,把一个@Schedule注解的定时任务关闭。以下整理一下如何设置@Schedule注解的定时任务不启用。以下内容基于 org.springframework:spring-context:5.3.12.jar进行讲解
以下为测试代码:
package com.demo.schedule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@EnableScheduling
@Component
@Slf4j
public class ScheduleTest {
//每5秒执行一次
@Scheduled(cron = "${schedule.test1Cron}")
public void test_1() {
log.info("测试任务1: " + System.currentTimeMillis());
}
//每5秒执行一次
@Scheduled(cron = "${schedule.test2Cron}")
public void test_2() {
log.info("测试任务2: " + System.currentTimeMillis());
}
}
application.properties配置文件:
#定时任务1
schedule.test1Cron= 0/5 * * * * *
#定时任务2
schedule.test2Cron= 0/5 * * * * *
或者application.yml配置文件:
schedule:
#定时任务1
test1Cron: "0/5 * * * * *"
#定时任务2
test2Cron: "0/5 * * * * *"
注意: properties中不要用双引号否则会报错(因为加双引号读取时会原文输出即把双引号也带上);yml中可加双引号。
执行结果:
2024-09-24 10:03:55.003 INFO 6788 --- [scheduling-1] com.demo.schedule.ScheduleTest: 测试任务1: 1727143435002
2024-09-24 10:03:55.003 INFO 6788 --- [scheduling-1] com.demo.schedule.ScheduleTest: 测试任务2: 1727143435003
2024-09-24 10:04:00.007 INFO 6788 --- [scheduling-1] com.demo.schedule.ScheduleTest: 测试任务1: 1727143440007
2024-09-24 10:04:00.009 INFO 6788 --- [scheduling-1] com.demo.schedule.ScheduleTest: 测试任务2: 1727143440009
2024-09-24 10:04:05.013 INFO 6788 --- [scheduling-1] com.demo.schedule.ScheduleTest: 测试任务2: 1727143445013
2024-09-24 10:04:05.013 INFO 6788 --- [scheduling-1] com.demo.schedule.ScheduleTest: 测试任务1: 1727143445013
2024-09-24 10:04:10.012 INFO 6788 --- [scheduling-1] com.demo.schedule.ScheduleTest: 测试任务1: 1727143450012
2024-09-24 10:04:10.013 INFO 6788 --- [scheduling-1] com.demo.schedule.ScheduleTest: 测试任务2: 1727143450013
一、关闭定时任务
1.1、方法一:注释@EnableScheduling注解
@EnableScheduling注解的意义是开启定时任务,若@EnableScheduling注解被注释,则整个项目中所有@Scheduled注解的定时任务都将关闭。
优点:
- 可以一次性关闭项目中所有的定时任务
缺点:
- 实际项目中可能只是想关闭其中一个定时任务不执行。直接注释@EnableScheduling注解会导致整个项目所有@Scheduled注解的定时任务都将关闭。
- 需要手动注释代码,生产上更多的是想配置化的,方便修改。
package com.demo.schedule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
//注释@EnableScheduling注解,关闭整个项目所有的定时任务
//@EnableScheduling
@Component
@Slf4j
public class ScheduleTest {
//每5秒执行一次
@Scheduled(cron = "${schedule.test1Cron}")
public void test_1() {
log.info("测试任务1: " + System.currentTimeMillis());
}
//每5秒执行一次
@Scheduled(cron = "0/5 * * * * *")
public void test_2() {
log.info("测试任务2: " + System.currentTimeMillis());
}
}
1.1.1、原理
eg1.正常情况下,只要注释掉@EnableScheduling注解即可,原理就是:
- 注释掉@EnableScheduling就不会@Import(SchedulingConfiguration.class)
- 就不会注入ScheduledAnnotationBeanPostProcessor这个后置处理器
- 这个后置处理器就是用来注册执行@Scheduled定时任务的
eg2.注释掉@EnableScheduling无效,还会执行@Scheduled定时任务?
- 检查是否项目除启动类还有其它配置类用了@EnableScheduling注解
- 检查有没有引入spring-session-data-redis依赖,RedisHttpSessionConfiguration内部bean使用了@EnableScheduling
- 据说spring-boot-starter-actuator依赖也会有,但是我没找到
1.2、方法二:不加载ScheduledAnnotationBeanPostProcessor类
此方法与方法一效果一致,关闭所有@Scheduled的定时任务,只是可以通过配置的方式关闭。
其实 @Scheduled 注解,是被一个叫做 ScheduledAnnotationBeanPostProcessor 的类所拦截的,所以我们可以根据配置,决定是否创建这个 bean,如果没有这个 bean,@Scheduled 就不会被拦截,那么定时任务肯定不会执行了,有了这个思路,实现起来就很简单了。需要注意的是:这种方式,启动类上面的 @EnableScheduling 需要去掉。
- 注释所有@EnableScheduling注解
- application.properties配置文件中添加以下配置
# 关闭所有scheduled定时任务
scheduled.enable = false
#定时任务1
schedule.test1Cron= 0/5 * * * * *
#定时任务2
schedule.test2Cron= 0/5 * * * * *
- 创建一个 ScheduledCondtion 类,内容如下:
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class ScheduledCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println("配置属性:"+Boolean.valueOf(context.getEnvironment().getProperty("scheduled.enable")));
//读取配置中的属性
return Boolean.valueOf(context.getEnvironment().getProperty("scheduled.enable"));
}
}
这个类的功能很简单,就是去读取配置,然后返回一个 boolean 值。
- 创建一个配置类 ScheduledConfig ,内容如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
@Configuration
public class ScheduledConfig {
@Conditional(ScheduledCondition.class)
@Bean
public ScheduledAnnotationBeanPostProcessor processor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
这个配置就是以 ScheduledCondtion 为条件,决定是否创建 bean。然后,启动项目,定时任务就会执行,如果我们将配置修改为 false,则不会执行。
1.3、方法三:注释@Scheduled注解
@Scheduled注解的意义是指定当前方法为定时任务,若@Scheduled注解被注释,则当前这个方法的定时任务将关闭。
优点:
- 通过注释@Scheduled注解可以实现单个定时任务的关闭,
缺点:
- 需要手动注释代码,对于生产环境来说需要提交代码并重新打包,并不友好。
package com.demo.schedule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@EnableScheduling
@Component
@Slf4j
public class ScheduleTest {
//每5秒执行一次
//注释@Scheduled注解,关闭当前这个定时任务
//@Scheduled(cron = "${schedule.test1Cron}")
public void test_1() {
log.info("测试任务1: " + System.currentTimeMillis());
}
//每5秒执行一次
@Scheduled(cron = "${schedule.test2Cron}")
public void test_2() {
log.info("测试任务2: " + System.currentTimeMillis());
}
}
1.4、方法四:设置@Scheduled注解cron时间不开启(推荐)
@Scheduled注解源码中说明当cron表达式配置为
-
时,不执行该定时任务。所以我们可以通过在application.properties文件中设置定时任务时间为-
。
cron表达式设置为-
时在项目启动时就不会把该定时任务添加到任务池中。原理见下方原理分析。
优点:
- 可配置化,可以在application.properties配置文件中配置时间,不用修改代码。
- 支持开启/关闭指定的任务
@Scheduled注解源码如下:
/**
* 标记要调度的方法的注释。 必须准确指定 cron、fixedDelay 或 fixedRate 属性之一。带注释的方法必须没有参数。
* 它通常有一个 void 返回类型; 如果不是,则在通过调度程序调用时将忽略返回值。@Scheduled 注解的处理是通过注册
* 一个 ScheduledAnnotationBeanPostProcessor 来执行的。 这可以手动完成,或者更方便的是,通过
* <task:annotation-driven/> 元素或 @EnableScheduling 注释。
* 此注释可用作元注释以创建具有属性覆盖的自定义组合注释。
* @since 3.0
* @see EnableScheduling
* @see ScheduledAnnotationBeanPostProcessor
* @see Schedules
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
/**
* 指示禁用触发器的特殊 cron 表达式值:“-”。
* 这主要用于 ${...} 占位符,允许外部禁用相应的计划方法。
*
* @since 5.1
* @see ScheduledTaskRegistrar#CRON_DISABLED
*/
String CRON_DISABLED = ScheduledTaskRegistrar.CRON_DISABLED;
/**
* 一个类似 cron 的表达式,扩展了通常的 UN*X 定义以包括秒、分、小时、月中的某一天、月份和星期几的触发器。
* 例如,“0 * * * * MON-FRI”表示工作日每分钟一次(在分钟的顶部 - 第 0 秒)。
*
* 从左到右读取的字段解释如下:
* <ul>
* <li>second</li>
* <li>minute</li>
* <li>hour</li>
* <li>day of month</li>
* <li>month</li>
* <li>day of week</li>
* </ul>
* 特殊值“-”表示禁用的 cron 触发器,主要用于由 ${...} 占位符解析的外部指定值。
* @return 返回一个可以解析为 cron 计划的表达式
* @see org.springframework.scheduling.support.CronSequenceGenerator
*/
String cron() default "";
/**
* 将解析 cron 表达式的时区。 默认:""(即使用服务器的本地时区)。
*
* @return java.util.TimeZone.getTimeZone(String) 接受的区域 ID,或指示服务器默认时区的空字符串
* @since 4.0
* @see org.springframework.scheduling.support.CronTrigger#CronTrigger(String, java.util.TimeZone)
* @see java.util.TimeZone
*/
String zone() default "";
/**
* 在上次调用结束和下一次调用开始之间的固定时间段,单位:毫秒,默认:-1(不延迟)
*
* @return 延迟时长,单位:毫秒
*/
long fixedDelay() default -1;
/**
* 上次调用的结束和下一次调用的开始之间固定时间间隔字符串,单位:毫秒。
*
* @return 延迟值字符串,单位:毫秒,例如占位符或
* {@link java.time.Duration#parse java.time.Duration} 兼容值
* @since 3.2.2
*/
String fixedDelayString() default "";
/**
* 在调用之间的固定时间段,单位:毫秒。
*
* @return 以毫秒为单位的周期
*/
long fixedRate() default -1;
/**
* 在调用之间的固定时间段字符串,单位:毫秒。
*
* @return 延迟值字符串,单位:毫秒,例如占位符或
* {@link java.time.Duration#parse java.time.Duration} 兼容值
* @since 3.2.2
*/
String fixedRateString() default "";
/**
* 在第一次执行 fixedRate 或 fixedDelay 任务之前延迟的毫秒数。
*
* @return 初始延迟值,单位:毫秒
* @since 3.2
*/
long initialDelay() default -1;
/**
* 在第一次执行 fixedRate 或 fixedDelay 任务之前延迟的毫秒数字符串。
* @return 初始延迟值字符串,单位:毫秒,例如占位符或符合 java.time.Duration 的值
* @since 3.2.2
*/
String initialDelayString() default "";
}
package com.demo.schedule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@EnableScheduling
@Component
@Slf4j
public class ScheduleTest {
//每5秒执行一次
//在配置文件中将cron时间配置为- 即可关闭当前这个定时任务
@Scheduled(cron = "${schedule.test1Cron}")
public void test_1() {
log.info("测试任务1: " + System.currentTimeMillis());
}
//每5秒执行一次
@Scheduled(cron = "${schedule.test2Cron}")
public void test_2() {
log.info("测试任务2: " + System.currentTimeMillis());
}
}
application.propertites配置文件:
#定时任务1
schedule.test1Cron= -
#定时任务2
schedule.test2Cron= -
1.4.1、原理
@Scheduled注解中有个常量参数CRON_DISABLED
值为-
,当配置cron的值为-
时,则不加入任务池中,即实现了关闭定时任务。
源码分析见下图:
位置:org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#processScheduled