当前位置: 首页 > news >正文

如何动态修改spring中定时任务的调度策略(1)

在我们日常开发中经常会调度工具来处理一下需要定时执行的任务,比如定时导出报表数据给业务方发送邮件。你在工作中是如何这种定时调度?

如何实现调度任务

使用java技术栈的老铁来说,现成定时调度的解决方案应该有很多,总结来说有三大类:

1.开源的定时调度框架,如xxlJob。

2.使用java中的定时任务api自己写,比如Timer.

3.使用spring提供的@Scheduled注解,简单方便。

上述三种方案中,开源调度框架是最好用的,而且有可视化管理面板。但是这种开源框架通常需要单独部署和维护,成本较高,如果业务中调度任务不多的话,没有必要大费周折去部署和维护这么一套系统,得不偿失。

使用jdk自带的Timer,或者 ScheduledThreadPoolExecutor来自己手动实现,这种方式灵活度高,可更具业务进行自定义开发,但是jdk自带的api相对较为底层,不能很好的使用 cron表达式,如果想要使用的话,还需要实现一套cron表达式解析器的开发,开发成本较高。

使用spring提供的@Scheduled注解,通过简单配置一下cron表达式即可,再加上现在java应用开发,基本上都在使用spring全家桶,引用@Scheduled的成本不高。

Sping中调度任务是如何实现的

在使用spring的项目中 ,在一个方法上添加一个 @Scheduled 注解

然后根据业务需要添加需要的cron表达式(其实@Scheduled不仅仅支持cron表达式,还支持其他很多调度策略,详细可以看@Scheduled注解的定义)

最后在启动类上添加 @EnableScheduling 即可。

是不是很简单,完整代码如下:


@SpringBootApplication
@EnableScheduling
public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}
}@Component
public class JobScheduler {@Scheduled(cron="0/1 * * * * ?")public void schedule(){System.out.println("scheduler" + new SimpleDateFormat("YYYY-MM-dd HH-mm:ss").format(new Date()) + "  :  "+Thread.currentThread().getName());}
}

这里可能就有老铁好奇了,为什么只添加了 @Scheduled 和 @EnableScheduling 两个注解,就可以实现调度的能力呢?

我们可以简单的看下 spring源码,探索一下它是如何实现的。

@EnableScheduling

当你在配置类上使用@EnableScheduling注解时,Spring会创建一个名为scheduledAnnotationProcessor的bean。这个过程是通过SchedulingConfiguration类实现的。SchedulingConfiguration类是@EnableScheduling注解导入的配置类,它负责创建ScheduledAnnotationBeanPostProcessor实例。

具体代码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {}@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();}}

在SchedulingConfiguration类中,定义了一个名为scheduledAnnotationProcessor的bean。这个bean是通过scheduledAnnotationProcessor()方法创建的,该方法返回一个新的ScheduledAnnotationBeanPostProcessor实例。

同时,@Role(BeanDefinition.ROLE_INFRASTRUCTURE)注解表示这个bean是基础设施bean,通常不会被应用程序代码直接使用。

ScheduledAnnotationBeanPostProcessor类主要负责解析@Scheduled注解并创建定时任务。

以下是解析@Scheduled注解并创建定时任务的详细过程(以cron任务做介绍,其他任务流程类似)。

1.处理bean

当Spring创建bean时,ScheduledAnnotationBeanPostProcessor的postProcessAfterInitialization方法会被调用。这个方法会检查bean中的所有方法,寻找带有@Scheduled注解的方法。

	public Object postProcessAfterInitialization(Object bean, String beanName) {if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||bean instanceof ScheduledExecutorService) {// Ignore AOP infrastructure such as scoped proxies.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>> 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);if (logger.isTraceEnabled()) {logger.trace("No @Scheduled annotations found on bean class: " + targetClass);}}else {// Non-empty set of methodsannotatedMethods.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;}

2.创建定时任务

对于每个解析到的@Scheduled注解,ScheduledAnnotationBeanPostProcessor会创建一个定时任务。

它首先获取一个TaskScheduler实例(默认为 ThreadPoolTaskScheduler ),然后使用该实例为带有@Scheduled注解的方法创建一个定时任务,任务的执行策略根据注解中的属性确定。

这一步是在processScheduled方法的另一个重载版本中完成的。

	protected void processScheduled(Scheduled scheduled, Method method, Object bean) {try {Runnable runnable = createRunnable(bean, method);boolean processedSchedule = false;String errorMessage ="Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";Set<ScheduledTask> tasks = new LinkedHashSet<>(4);// Determine initial delaylong initialDelay = convertToMillis(scheduled.initialDelay(), scheduled.timeUnit());String initialDelayString = scheduled.initialDelayString();if (StringUtils.hasText(initialDelayString)) {Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");if (this.embeddedValueResolver != null) {initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);}if (StringUtils.hasLength(initialDelayString)) {try {initialDelay = convertToMillis(initialDelayString, scheduled.timeUnit());}catch (RuntimeException ex) {throw new IllegalArgumentException("Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");}}}// Check cron expression  // cron表达式处理String cron = scheduled.cron();if (StringUtils.hasText(cron)) {String zone = scheduled.zone();if (this.embeddedValueResolver != null) {cron = this.embeddedValueResolver.resolveStringValue(cron);zone = this.embeddedValueResolver.resolveStringValue(zone);}if (StringUtils.hasLength(cron)) {Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");processedSchedule = true;if (!Scheduled.CRON_DISABLED.equals(cron)) {TimeZone timeZone;if (StringUtils.hasText(zone)) {timeZone = StringUtils.parseTimeZoneString(zone);}else {timeZone = TimeZone.getDefault();}tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));}}}// At this point we don't need to differentiate between initial delay set or not anymoreif (initialDelay < 0) {initialDelay = 0;}// Check fixed delay// 固定延迟......// Check fixed rate// 固定频次......// Check whether we had any attribute setAssert.isTrue(processedSchedule, errorMessage);// Finally register the scheduled taskssynchronized (this.scheduledTasks) {Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));regTasks.addAll(tasks);}}catch (IllegalArgumentException ex) {throw new IllegalStateException("Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());}}

3.创建Runnable
createRunnable方法用于创建一个Runnable实例,它封装了带有@Scheduled注解的方法的调用。这个Runnable实例将被传递给任务调度器。

	protected Runnable createRunnable(Object target, Method method) {Assert.isTrue(method.getParameterCount() == 0, "Only no-arg methods may be annotated with @Scheduled");Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());return new ScheduledMethodRunnable(target, invocableMethod);}

4.创建Trigger
createTrigger方法根据@Scheduled注解的属性创建一个Trigger实例。Trigger实例决定了任务的执行策略,如固定速率、固定延迟或Cron表达式。

	public CronTrigger(String expression, ZoneId zoneId) {Assert.hasLength(expression, "Expression must not be empty");Assert.notNull(zoneId, "ZoneId must not be null");this.expression = CronExpression.parse(expression);this.zoneId = zoneId;}

5.配置调度任务

根据cron表达式对业务逻辑进行调度

public ScheduledTask scheduleCronTask(CronTask task) {ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);boolean newTask = false;if (scheduledTask == null) {scheduledTask = new ScheduledTask(task);newTask = true;}if (this.taskScheduler != null) {scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());}else {addCronTask(task);this.unresolvedTasks.put(task, scheduledTask);}return (newTask ? scheduledTask : null);}

从整个流程看下来,还是很复杂的,只不过这个复杂的流程,交给了spring自身来处理,只暴露给用户一个@Scheduled注解,简单的配置一下就可以。

到这里呢,spring的对@Scheduled 注解的解析和我们在日常开发中使用 @Scheduled的方式,想必你已经很清楚了,不过通过走读源码,大概也会发现一些问题:

1.cron表达式不能动态修改,如果想要调整调度策略的话,需要修改cron表达式,并重启项目才可以。

2.调度任务使用的 调度线程池是spring中默认的,线程池的大小,队里长度,拒绝策略都是什么?如何自定义个线程池提供给spring使用?

这两个问题,我们发到下一篇文中,进一步说明。

相关文章:

如何动态修改spring中定时任务的调度策略(1)

在我们日常开发中经常会调度工具来处理一下需要定时执行的任务&#xff0c;比如定时导出报表数据给业务方发送邮件。你在工作中是如何这种定时调度&#xff1f; 如何实现调度任务 使用java技术栈的老铁来说&#xff0c;现成定时调度的解决方案应该有很多&#xff0c;总结来说…...

idea import的maven类报红

idea 报红/显示红色的原因 一般报红&#xff0c;显示红色&#xff0c;是因为 idea 在此路径下&#xff0c;找不到这个类。 找到是哪个 jar 包的类导致 idea 报红 点击报红的路径的上一层&#xff0c;进入jar 包。比如&#xff1a; import com.aaa.bbb.ccc.DddDto;这个 impo…...

React——class组件中setState修改state

class组件中通过state去存储当前组件的数据&#xff0c;那怎么对其进行修改呢&#xff1f;就是方法this.setState({ 要修改的部分数据 }) setState() 作用&#xff1a;1 、修改 state 内容&#xff1b;2 、更新 UI 特别注意&#xff1a;react的核心其实是虚拟dom&#xff08;数…...

搭建基于 Snowflake 的 CI/CD 最佳实践!

Snowflake 提供了可扩展的计算和存储资源&#xff0c;和基于 SQL 的界面 Snowsight&#xff0c;方便用户进行数据操作和分析。然而&#xff0c;如果用户想将自己的 CI/CD 流程与 Snowflake 集成时&#xff0c;会发现一些不便之处&#xff08;尤其相比其 SnowSight 优秀的查询能…...

数据结构(五)——树的基本概念

五、树 5.1 树的基本概念 5.1.1 树的定义 树是n(n>0)个结点的有限集合&#xff0c;结点数为0的树称为空树 非空树的特性 有且仅有一个根节点没有后继的结点称为“叶子结点”&#xff08;或终端结点&#xff09;有后继的结点称为“分支结点”&#xff08;或非终端结点&a…...

2.28CACHE,虚拟存储器

主存储器,简称主存。CPU可以直接随机地对其进行访问&#xff0c;也可以和高速缓存器及辅助存储器交换数据。 2> 辅助存储器,简称辅存&#xff0c;不能与CPU直接相连&#xff0c;用来存放当前暂时不用的程序和数据 3> 高速缓冲存储器,位于主存和CPU之间&#xff0c;用来…...

深入理解栈和队列(一):栈

个人主页&#xff1a;17_Kevin-CSDN博客 专栏&#xff1a;《数据结构》 一、栈的概念 栈&#xff08;Stack&#xff09;是一种特殊的线性表&#xff0c;它遵循后进先出&#xff08;Last-In-First-Out&#xff0c;LIFO&#xff09;的原则。栈可以被看作是一个只能在一端进行操作…...

electron-builder 打包问题,下载慢解决方案

目录 问题说明设置下载源 &#xff1f;解决方案思路下载Electron下载winCodeSign下载nsis下载nsis-resources 总结 问题说明 项目使用了Electron&#xff0c;在第一次打包时会遇见下载慢&#xff0c;导致打包进度几乎停滞不前&#xff0c;甚至可能直接报错 其实这是因为Electr…...

(简单成功)Mac:命令设置别名

案例&#xff1a;给"ls -l"命令&#xff0c;设置别名通过”ll“快速访问 1、在项目根目录底下查看有无.bash_profile文件&#xff0c;注意这个是个隐藏文件&#xff0c;需要使用ls -a命令查看&#xff1a; 没有.bash_profile新建一个文件&#xff0c; 在最后添加一行…...

Grok-1:参数量最大的开源大语言模型

Grok-1&#xff1a;参数量最大的开源大语言模型 项目简介 由马斯克领衔的大型模型企业 xAI 正式公布了一项重要动作&#xff1a;开源了一个拥有 3140 亿参数的混合专家模型&#xff08;MoE&#xff09;「Grok-1」&#xff0c;连同其模型权重和网络架构一并公开。 此举将 Gro…...

Python 自然语言处理库之stanza使用详解

概要 在自然语言处理(NLP)领域,Python Stanza 库是一个备受推崇的工具,它提供了强大的功能和易用的接口,帮助开发者处理文本数据、进行语言分析和构建NLP应用。本文将深入探讨 Stanza 库的特性、用法,并通过丰富的示例代码展示其在实际项目中的应用。 Stanza 简介 Stan…...

计算机网络:数据交换方式

计算机网络&#xff1a;数据交换方式 电路交换分组交换报文交换传输对比 本博客介绍计算机之间数据交换的三种方式&#xff0c;分别是电路交换、分组交换以及报文交换。 电路交换 我们首先来看电路交换&#xff0c;在电话问世后不久&#xff0c;人们就发现要让所有的电话机都…...

万用表革新升级,WT588F02BP-14S语音芯片助力智能测量新体验v

万能表功能&#xff1a; 万能表是一款集多功能于一体的电子测量工具&#xff0c;能够精准测量电压、电流、电阻等参数&#xff0c;广泛应用于电气、电子、通信等领域。其操作简便、测量准确&#xff0c;是工程师们进行电路调试、故障排查的得力助手&#xff0c;为提升工作效率…...

Day61:WEB攻防-PHP反序列化原生类TIPSCVE绕过漏洞属性类型特征

知识点&#xff1a; 1、PHP-反序列化-属性类型&显示特征 2、PHP-反序列化-CVE绕过&字符串逃逸 3、PHP-反序列化-原生类生成&利用&配合 补充&#xff1a;如果在 PHP 类中没有实现某个魔术方法&#xff0c;那么该魔术方法在相应的情况下不会被自动触发。PHP 的魔…...

【开源】SpringBoot框架开发不良邮件过滤系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统用户模块2.2 收件箱模块2.3 发件箱模块2.4 垃圾箱模块2.5 回收站模块2.6 邮箱过滤设置模块 三、实体类设计3.1 系统用户3.2 邮件3.3 其他实体 四、系统展示五、核心代码5.1 查询收件箱档案5.2 查询回收站档案5.3 新…...

详细教---用Django封装写好的模型

本次我们要用自己写好的热销词条爬虫代码来演示如何用Django把我们写好的模型封装。 第一步&#xff1a;代码准备 热搜词条搜集代码&#xff1a; import requests from lxml import etreeurl "https://tophub.today/n/KqndgxeLl9" headers{User-Agent: Mozilla/5.…...

设计模式 抽象工厂

01.人类接口 public interface Human { //首先定义什么是人类//人是愉快的&#xff0c;会笑的&#xff0c;本来是想用smile表示&#xff0c;想了一下laugh更合适&#xff0c;好长时间没有大笑了&#xff1b; public void laugh(); //人类还会哭&#xff0c;代表痛苦 public v…...

OPTIONS请求(跨域预检查)

目录 一、什么是OPTIONS请求&#xff1f;二、简单请求、复杂请求三、特定的请求头、响应头 一、什么是OPTIONS请求&#xff1f; OPTIONS 请求方式是 HTTP 协议中的一种&#xff0c;主要用于 从响应头中获取服务器支持的HTTP请求方式。 OPTIONS 请求方式是 浏览级行为&#xf…...

游戏反云手机检测方案

游戏风险环境&#xff0c;是指独立于原有设备或破坏设备原有系统的环境。常见的游戏风险环境有&#xff1a;云手机、虚拟机、虚拟框架、iOS越狱、安卓设备root等。 这类风险环境可以为游戏外挂、破解提供所需的高级别设备权限&#xff0c;当游戏处于这些风险环境下&#xff0c…...

HarmonyOS NEXT应用开发之动态路由

介绍 本示例将介绍如何使用动态路由跳转到模块中的页面&#xff0c;以及如何使用动态import的方式加载模块 使用说明 通过动态import的方式&#xff0c;在需要进入页面时加载对应的模块。配置动态路由&#xff0c;通过WrapBuilder接口&#xff0c;动态创建页面并跳转。动态i…...

告别Samba和FTP:用Java NFS-Client 1.0.3实现跨平台文件操作,SpringBoot项目实战

告别Samba和FTP&#xff1a;用Java NFS-Client 1.0.3实现跨平台文件操作&#xff0c;SpringBoot项目实战 在分布式系统和云原生架构日益普及的今天&#xff0c;传统的文件共享方案如Samba和FTP逐渐暴露出性能瓶颈和兼容性问题。本文将带你探索一种更现代、更高效的替代方案——…...

CANoe测试模块怎么选?XML vs CAPL Test Module,我用700人投票结果告诉你

CANoe测试模块选择指南&#xff1a;XML与CAPL的深度对比与实战建议 在汽车电子测试领域&#xff0c;CANoe作为行业标杆工具&#xff0c;其测试模块的选择往往让新手工程师感到困惑。最近一项针对700名工程师的调研显示&#xff0c;70%的受访者倾向于使用XML Test Module&#x…...

5分钟轻松解锁B站缓存视频:m4s转MP4一键解决方案

5分钟轻松解锁B站缓存视频&#xff1a;m4s转MP4一键解决方案 【免费下载链接】m4s-converter 一个跨平台小工具&#xff0c;将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾经遇到过这样的情况&#x…...

Navicat Premium试用期重置终极指南:简单三步恢复14天完整试用

Navicat Premium试用期重置终极指南&#xff1a;简单三步恢复14天完整试用 【免费下载链接】navicat-premium-reset-trial Reset macOS Navicat Premium 15/16/17 app remaining trial days 项目地址: https://gitcode.com/gh_mirrors/na/navicat-premium-reset-trial 你…...

3步搭建智能媒体库:MoviePilot让NAS影视管理变简单

3步搭建智能媒体库&#xff1a;MoviePilot让NAS影视管理变简单 【免费下载链接】MoviePilot NAS媒体库自动化管理工具 项目地址: https://gitcode.com/gh_mirrors/mo/MoviePilot MoviePilot是一个专注于NAS媒体库自动化管理的开源工具&#xff0c;通过智能化的设计和简洁…...

Android Profiler 内存分析实战:从卡顿溯源到泄漏定位

1. Android Profiler内存分析器入门指南 第一次打开Android Studio的Profiler面板时&#xff0c;很多开发者都会被那些跳动的曲线和复杂的数据搞得一头雾水。记得我刚接触内存分析时&#xff0c;盯着那些上上下下的折线图看了半天&#xff0c;完全不知道从何下手。其实Android …...

番茄小说下载器:5分钟打造个人离线图书馆的终极指南

番茄小说下载器&#xff1a;5分钟打造个人离线图书馆的终极指南 【免费下载链接】Tomato-Novel-Downloader 番茄小说下载器不精简版 项目地址: https://gitcode.com/gh_mirrors/to/Tomato-Novel-Downloader 你是否曾在通勤地铁上、旅行途中或网络信号不佳的地方&#xf…...

微信支付V3批量转账接口踩坑实录:从签名验签到结果回调的完整避坑指南

微信支付V3批量转账接口深度排雷指南&#xff1a;从签名验签到异步回调的实战全解析 第一次对接微信支付V3批量转账接口时&#xff0c;我盯着控制台里那行FREQUENCY_LIMITED错误码发了半小时呆。这不是简单的频率限制提示&#xff0c;而是新版API给开发者设下的第一道"思…...

终极指南:如何通过智能鼠标宏配置解锁PUBG精准射击的完整潜力

终极指南&#xff1a;如何通过智能鼠标宏配置解锁PUBG精准射击的完整潜力 【免费下载链接】logitech-pubg PUBG no recoil script for Logitech gaming mouse / 绝地求生 罗技 鼠标宏 项目地址: https://gitcode.com/gh_mirrors/lo/logitech-pubg 你是否在《绝地求生》的…...

Phi-3.5-mini-instruct免配置优势:系统重启后自动恢复,无须人工干预

Phi-3.5-mini-instruct免配置优势&#xff1a;系统重启后自动恢复&#xff0c;无须人工干预 1. 开箱即用的轻量级文本助手 Phi-3.5-mini-instruct是一款专为中文场景优化的轻量级文本生成模型&#xff0c;它已经完成了网页封装&#xff0c;用户无需任何技术背景即可直接使用。…...