当前位置: 首页 > 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…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

数据结构:递归的种类(Types of Recursion)

目录 尾递归&#xff08;Tail Recursion&#xff09; 什么是 Loop&#xff08;循环&#xff09;&#xff1f; 复杂度分析 头递归&#xff08;Head Recursion&#xff09; 树形递归&#xff08;Tree Recursion&#xff09; 线性递归&#xff08;Linear Recursion&#xff09;…...