Spring定时任务的秘密
Spring定时任务的秘密
在 Spring 框架中,定时任务主要通过 @Scheduled
注解或 TaskScheduler
接口实现。
1.基本使用
在 Spring Boot 项目中,通过 @EnableScheduling
注解启用定时任务功能:
@SpringBootApplication
@MapperScan("com.feng.tackle.dao")
@EnableScheduling
public class DateApplication {
public static void main(String[] args) {
SpringApplication.run(DateApplication.class, args);
}
}
然后在spring的组件中,使用 @Scheduled
注解标记任务方法
@Component
public class MyScheduler {
// 固定频率(每隔 5 秒执行一次)
@Scheduled(fixedRate = 5000)
public void task1() {
// 业务逻辑
}
// 固定延迟(任务结束后延迟 3 秒再执行)
@Scheduled(fixedDelay = 3000)
public void task2() {
// 业务逻辑
}
// Cron 表达式(每天 12 点执行)
@Scheduled(cron = "0 0 12 * * ?")
public void task3() {
// 业务逻辑
}
}
其实用起来特别地简单。
2. 原理解析
核心部分:
@EnableScheduling
入口注解,触发SchedulingConfiguration
配置类,注册核心后置处理器ScheduledAnnotationBeanPostProcessor
。ScheduledAnnotationBeanPostProcessor
负责扫描 Bean 中的@Scheduled
注解方法,解析并注册定时任务。TaskScheduler
任务调度接口,默认实现为ThreadPoolTaskScheduler
(基于ScheduledExecutorService
)。ScheduledTaskRegistrar
任务注册中心,管理所有定时任务的注册和执行。
一、构建任务
@EnableScheduling
,在启动类上面加了这么一个注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class) //============
@Documented
public @interface EnableScheduling {
}
看过SpringBoot原理分析的都知道,@Import(SchedulingConfiguration.class)
这个就是突破口了嘛。
继续往下
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor(); // 往容器里面放进了这样一个bean
}
}
想必那个类就是核心了,ScheduledAnnotationBeanPostProcessor源码中最开头的英文的翻译如下
Bean 后处理器,它注册带有 @Scheduled 注释的方法,以便由TaskScheduler根据通过 Comments 提供的“fixedRate”、“fixedDelay”或“cron”表达式调用。这个后处理器由 Spring 的 <task:annotation-driven> XML 元素以及 @EnableScheduling 注释自动注册。自动检测容器中的任何 SchedulingConfigurer 实例,允许自定义要使用的调度器或对任务注册进行精细控制(例如,注册 Trigger 任务)
既然是xxxBeanPostProcessor了,那么肯定是实现了BeanPostProcessor接口,重写了postProcessAfterInitialization方法
。
下面来解析这个实现类的具体方法
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 忽略 AOP 基础设施类(如 ScopedProxy)和任务调度器自身
if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
bean instanceof ScheduledExecutorService) {
return bean;
}
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass) &&
AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
// 返回 Map<Method, Set<Scheduled>>,键为方法对象,值为该方法上的所有 @Scheduled 注解集合。
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);
});
// 如果没有
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(targetClass); //将不包含 @Scheduled 注解的类加入缓存,后续跳过扫描以提高性能。
if (logger.isTraceEnabled()) {
logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
}
}
else { // 如果有-------------------------------
annotatedMethods.forEach((method, scheduledAnnotations) ->
scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isTraceEnabled()) {
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
经过上面的大致分析,发现else里面,才是我们想看的,各位也可以打断点调试。
annotatedMethods.forEach(
(method, scheduledAnnotations) ->
scheduledAnnotations.forEach(
scheduled -> processScheduled(scheduled, method, bean)
)
);
processScheduled()
方法,构建任务。
private final ScheduledTaskRegistrar registrar;
private final Map<Object, Set<ScheduledTask>> scheduledTasks = new IdentityHashMap<>(16);
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
....
// 可以看出来了把,任务其实就是个runnable
Runnable runnable = createRunnable(bean, method);
.....
Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
// 检查注解的延迟属性
long initialDelay = convertToMillis(scheduled.initialDelay(), scheduled.timeUnit());
.....
//cron表达式
String cron = scheduled.cron();
.....
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
....
// 检查周期属性
long fixedDelay = convertToMillis(scheduled.fixedDelay(), scheduled.timeUnit());
...
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
...
String fixedDelayString = scheduled.fixedDelayString();
..........
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
.........
// Finally register the scheduled tasks
synchronized (this.scheduledTasks) {
Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
regTasks.addAll(tasks); // 全加进去咯
}
.....
}
二、自动配置
熟悉springboot自动配置原理的都知道,spring-boot-autoconfigure里面官方定义了超级多的场景。我们去找找看。
一下子就找到了 task目录下面的TaskSchedulingAutoConfiguration
. 【默认是单线程的】
@ConditionalOnClass(ThreadPoolTaskScheduler.class)
@AutoConfiguration(after = TaskExecutionAutoConfiguration.class)
@EnableConfigurationProperties(TaskSchedulingProperties.class)
public class TaskSchedulingAutoConfiguration {
@Bean
@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
// 如果容器中有我们自定义的这种bean,这个就失效了,@ConditionalOnMissingBean注解的作用
@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })
public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) { // 下面构建的bean,拿来这里用了
return builder.build();
}
@Bean
@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
public static LazyInitializationExcludeFilter scheduledBeanLazyInitializationExcludeFilter() {
return new ScheduledBeanLazyInitializationExcludeFilter();
}
@Bean
// 如果容器中有我们自定义的这种bean,这个就失效了,@ConditionalOnMissingBean注解的作用
@ConditionalOnMissingBean
public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties, // 从properties读取的
ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
builder = builder.poolSize(properties.getPool().getSize()); // 发现这里是 size = 1, 默认是单线程的!!!!!
Shutdown shutdown = properties.getShutdown();
builder = builder.awaitTermination(shutdown.isAwaitTermination());
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
builder = builder.customizers(taskSchedulerCustomizers);
return builder;
}
}
@ConfigurationProperties("spring.task.scheduling") // 说明我们可以根据这个配置来设置线程数那些参数
public class TaskSchedulingProperties {
}
三、任务调度
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler {
@Nullable
private ScheduledExecutorService scheduledExecutor; // 内部持有一个线程池!!!! initializeExecutor()方法会初始化它
.........
// 主要看这个方法
@Override
@Nullable
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
ScheduledExecutorService executor = getScheduledExecutor(); // 先得到executor
try {
ErrorHandler errorHandler = this.errorHandler;
if (errorHandler == null) {
errorHandler = TaskUtils.getDefaultErrorHandler(true);
}
// 执行这一个
return new ReschedulingRunnable(task, trigger, this.clock, executor, errorHandler).schedule();
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
..........
}
3.案例分析
①: 什么都不配置,单线程
@Slf4j
@Component
public class TestJob {
@Scheduled(cron = "1-59 * * * * ?")
public void hello() {
try {
log.info("hello---任务开始");
TimeUnit.SECONDS.sleep(5);
log.info("hello---任务------------结束hello");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Scheduled(cron = "1-59 * * * * ?")
public void world() {
try {
log.info("world---任务开始");
TimeUnit.SECONDS.sleep(2);
log.info("world---任务--------------结束world");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
运行结果
world任务,两个之间间隔了7秒钟。!!! 同理,两个hello()任务也间隔了七秒钟。也就是说,任务之间互相影响了。【因为是单线程的嘛】
②:配置调度线程是多线程的
@Configuration
public class SchedulerConfig {
// 配置多线程的调度器, 默认是单线程
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 设置调度线程池大小
scheduler.setThreadNamePrefix("ScheduledTask-");
return scheduler;
}
}
然后任务如下
@Scheduled(cron = "20/40 * * * * ? ") // 从每分钟的20秒开始,每40秒执行一次,---- xx:xx:20 xx:xx+1:20...
public void hello() {
try {
log.info("hello---任务开始");
TimeUnit.SECONDS.sleep(20);
log.info("hello---任务结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Scheduled(cron = "30/20 * * * * ? ") // 从每分钟的30开始,每20秒执行一次,---- xx:xx:30 xx:xx:50 xx:xx+1:30 xx:xx+1:50...
public void world() {
try {
log.info("world---任务开始");
TimeUnit.SECONDS.sleep(10);
log.info("world---任务结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
每一分钟内来看,应该是hello()在第20秒先执行,耗时20秒,在40秒结束, world()在第三十秒执行,耗时10秒,在40秒结束。 如果被阻塞的话,world()任务将会在第40秒执行。
但是看执行效果,发现并没有被阻塞,二者是按规定正常执行的,并没有互相影响。故我们的配置生效了,现在定时任务调度是多线程的。【图中也可以看到 执行的线程名字不一样】
Spring定时任务的秘密的更多相关文章
- 摆脱Spring 定时任务的@Scheduled cron表达式的困扰
一.背景 最近因为需要,需要适用Spring的task定时任务进行跑定时任务,以前也接触过,但是因为懒没有好好地理解@Scheduled的cron表达式,这次便对它做了一个全方位的了解和任务,记录下来 ...
- spring 定时任务配置
1.(易)如何在spring中配置定时任务? spring的定时任务配置分为三个步骤: 1.定义任务 2.任务执行策略配置 3.启动任务 (程序中一般我们都是到过写的,直观些) 1.定义任务 < ...
- Spring 定时任务2
转载自http://www.cnblogs.com/nick-huang/p/4864737.html > 版本说明 <dependencies> <dependency> ...
- 关于Spring定时任务(定时器)用法
Spring定时任务的几种实现 Spring定时任务的几种实现 一.分类 从实现的技术上来分类,目前主要有三种技术(或者说有三种产品): 从作业类的继承方式来讲,可以分为两类: 从任务调度的触发时机来 ...
- Cron和Spring定时任务
1.Java Spring spring定时任务cronExpression的值(配置定时时间)格式说明: 一个cronExpression表达式有至少6个(也可能是7个)由空格分隔的时间元素.从左至 ...
- spring 定时任务的 执行时间设置规则(转)
spring 定时任务的 执行时间设置规则 单纯针对时间的设置规则org.springframework.scheduling.quartz.CronTriggerBean允许你更精确地控制任务的运 ...
- (3)Spring定时任务的几种实现
Spring定时任务的几种实现 近日项目开发中需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息,借此机会整理了一下定时任务的几种实现方式,由于项目采用spring框架,所以我都将 ...
- Spring定时任务,Spring4整合quartz2.2,quartz-scheduler定时任务
Spring4整合quartz2.2,quartz-scheduler定时任务,Spring定时任务 >>>>>>>>>>>>& ...
- spring计划任务,springMvc计划任务,Spring@Scheduled,spring定时任务
spring计划任务,springMvc计划任务,Spring@Scheduled,spring定时任务 >>>>>>>>>>>> ...
- spring定时任务的几种实现方式
Spring定时任务的几种实现 近日项目开发中需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息,借此机会整理了一下定时任务的几种实现方式,由于项目采用spring框架,所以我都将 ...
随机推荐
- 基于开源IM即时通讯框架MobileIMSDK:RainbowChat v11.5版已发布
关于MobileIMSDK MobileIMSDK 是一套专门为移动端开发的开源IM即时通讯框架,超轻量级.高度提炼,一套API优雅支持UDP .TCP .WebSocket 三种协议,支持iOS.A ...
- 【狂神说Java】Java零基础学习笔记-Java数组
[狂神说Java]Java零基础学习笔记-Java数组 Java数组01:数组的定义 数组是相同类型数据的有序集合. 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成. 其中,每一个数 ...
- 注册中心如何选型?Eureka、Zookeeper、Nacos怎么选
这是小卷对分布式系统架构学习的第9篇文章,第8篇时只回答了注册中心的工作原理的内容,面试官的第二个问题还没回答,今天再来讲讲各个注册中心的原理,以及区别,最后如何进行选型 上一篇文章:如何设计一个注册 ...
- C#数据结构与算法入门实战指南
前言 在编程领域,数据结构与算法是构建高效.可靠和可扩展软件系统的基石.它们对于提升程序性能.优化资源利用以及解决复杂问题具有至关重要的作用.今天大姚分享一些非常不错的C#数据结构与算法实战教程,希望 ...
- Superset 用户集成完整方案(iframe方式)
本次集成方案经过个人测试,根据前面2个集成方案的资料,撰写,相关说明由于个人知识水平有限不一定理解准确,有错误的地方环境评论区评论: 1.用户集成方式: A系统用户,通过A的某个界面,iframe嵌入 ...
- Billyboss pg walkthough Intermediate window
nmap ┌──(root㉿kali)-[/home/ftpuserr/nc.exe] └─# nmap -p- -A -sS 192.168.219.61 Starting Nmap 7.94SVN ...
- 喜讯!天翼云斩获NLP国际顶会比赛两项荣誉
近日,NLP国际顶会ACL(The Association for Computational Linguistics)进行的国际赛事WASSA 2023(13th Workshop on Compu ...
- 文本处理命令head tail more less tr cut paste wc
文本处理命令 命令**head tail more less tr cut paste wc** 磁盘分区利用率 df|tr -s ' ' :|cut -d : -f5 df|tr -s ' ' :| ...
- linux mint安装Scala
Scala由java编写,需要前期安装jdk 面向函数式编程 1.下载 Scala 二进制包2.11.8 http://www.scala-lang.org/downloads 解压到/usr/loc ...
- Luogu P7077 CSP-S2020 函数调用 题解 [ 蓝 ] [ 拓扑排序 ] [ 动态规划 ] [ 数学 ]
函数调用:个人非常喜欢的一道拓扑题. 转化 这题一共有三种操作,不太好搞.而第一个函数看起来就比较可做,第三个函数显然就是让你拓扑转移,于是我们考虑第二个操作怎么处理. 当我们进行一个操作一后,假设当 ...