单体项目中定时任务的实现-详细教程

news2024/11/25 10:23:43

单体项目中定时任务的实现

在企业开发中,遇到的项目无非就两种,单体项目和分布式项目

单体项目中实现定时任务有以下几种方式

1. 使用Timer实现定时任务(不常用)
1.1、JDK1.3推出的定时任务实现工具类java.util.Timer
1.2、API
    Timer timer = new Timer();
    timer.schedule(任务,延迟时间/执行时间);
    timer.schedule(任务,延迟时间,重复频率);
    timer.schedule(任务,首次执行时间,重复频率);

示例:
public class MyTimer {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer(); //创建一个定时器对象
        String str = "2024-09-23 14:30:00";
        Date startDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // 定时任务要执行的功能
                System.out.println("hello Timer!");
            }
        }, startDate);
    }
}

因为定时任务类TimerTask是个抽象类,底层实现了Runnable接口,所以在实际开发中,我们会将要定时执行的业务逻辑单独定义在一个类作为定时任务类,让这个类继承TimerTask。

// 定时任务类
public class MyTimerTask extends TimerTask{
    @Override
    public void run() {
        // 定时任务要执行的功能
        System.out.println("hello Timer!");
    }
}

// 里面的逻辑在实际开发中一般写在service或controller的方法中
public class MyTimer {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer();
        String str = "2024-09-23 14:30:00";
        Date startDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str);
        MyTimerTask task = new MyTimerTask();
        timer.schedule(task, startDate);
    }
}

使用Timer实现定时任务的缺点

  1. 单线程执行所有任务,效率低;
  2. 当定义了很多个定时任务时,如果其中一个定时任务发生异常,其他所有定时任务都会终止;

因此,阿里的开发规范中不允许使用这种方式实现定时任务。


2. Spring Task实现定时任务(常用)
2.1、Spring 3.0开始推出定时任务功能,也就是Spring Task,因为Spring Task集成在SpringContext中,所以使用时无需引入依赖。
2.2、@EnableScheduling:SpringBoot启动类上需添加此依赖,表示开启定时任务功能
2.3、@Scheduled(cron = “cron表达式”):定时任务执行的业务逻辑方法上需添加此注解,让项目识别到要执行的逻辑是哪个方法;cron表达式定义的是定时任务开始执行的时间和频率等信息,可用网上的生成工具一键生成,如https://cron.qqe2.com/
cron表达式

2.4、运行定时任务不需要编码调用API,直接运行启动类即可

示例:
@SpringBootApplication
@EnableScheduling // 开启定时任务功能
public class MultiFileApp {
    public static void main(String[] args) {
        SpringApplication.run(MultiFileApp.class, args);
    }
}

// 定时任务类
@Component
public class MyTask {
    @Scheduled(cron = "0/2 * * * * ? ")
    public void task1() {
        // 定时任务要执行的功能
        System.out.println("hello Timer1!");
    }
    
    @Scheduled(cron = "* 0/5 * * * ? *")
    public void task2() {
        // 定时任务要执行的功能
        System.out.println("hello Timer2!");
    }
}

Spring Task默认是以单线程的方式执行所有的定时任务,可以通过配置改成以多线程的方式执行定时任务,提高效率

// 多线程配置
spring:
  task:
    scheduling:
      thread-name-prefix: mytask_  //配置线程名前缀
      pool:
        size: 5 //线程池里的线程数
      shutdown:
        await-termination: false //线程关闭前是否等待所有任务执行完毕,默认就是false
        await-termination-period: 10s //线程关闭前的最大等待时间(防止执行某个任务时线程一直不结束)

Spring Task实现定时任务的缺点

  1. 不支持持久化,也就意味着需要手动持久化数据
  2. 默认也是单线程执行所有任务

这些缺点都可以手动通过配置或编码弥补,所以这种方式还是比较常用的,Spring Task可以当作是轻量级的Quartz。

3. SpringBoot整合Quartz实现定时任务(常用)
3.1、quartz支持持久化,默认以多线程的方式执行;
3.2、概念:
    Job:任务。定时执行的具体任务内容;
    JobDetail:任务详情。即与任务相关的其他配置信息;
    Trigger:触发器。定义定时任务执行的时间规则;
    Scheduler:调度器。将Job和Trigger绑定;
示意图

3.3、具体实现:

  1. 引入依赖:spring-boot-starter-quartz
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  1. 创建Job:
    2.1. 定义定时任务类,实现Job接口;
    2.2. @PersistJobDataAfterExecution,加了此注解的job称为有状态的job:多次执行的定时任务共享一个jobDataMap,真正的实现数据共享。
    2.3. @DisallowConcurrentExecution,禁止并发访问同一个job:当任务处理时间超过任务间隔时间时,任务的间隔执行时间将变为任务处理时间,保证数据的准确性。
示例:
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 定时任务执行的功能
        // context-定时任务执行的环境(上下文,可以理解为调度器:执行定时任务)
        
        // 业务举例
        // 获取jobDetail对象
        JobDetail jobDetail = context.getJobDetail();
        System.out.println("任务名字" + jobDetail.getKey().getName());
        System.out.println("任务分组名字" + jobDetail.getKey().getGroup());
        System.out.println("任务类名字" + jobDetail.getJobClass().getName());
        System.out.println("本次执行时间" + context.getFireTime());
        System.out.println("下次执行时间" + context.getNextFireTime());
        // 记录任务执行次数
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        Integer count = (Integer)jobDataMap.get("count");
        System.out.println("第" + count + "次执行");
        jobDataMap.put("count", ++count); // 更新共享数据
    }
}
  1. 创建JobDetail:
    3.1. 定义配置类QuartzConfig;
    3.2. @Bean public JobDetail jobDetail() {}
    3.3. JobBuilder.newJob(绑定具体Job的class)
            .storeDurably()
            .withIdentity(“任务名”,“任务组名”)
            .usingJobData(“count”,1)
            .build();
示例:
@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail jobDetail() {
        return JobBuilder.newJob(MyJob.class)
                .storeDurably() // 持久化(此JobDetail没有与Trigger绑定也不会被清理掉)
                .withIdentity("job1", "group1") // 唯一标识
                .usingJobData("count",1) // (多次执行的任务之间可共享的数据)数据初始化
                .build();
    }
}
  1. 创建Trigger
    4.1. 配置类QuartzConfig;
    4.2. @Bean public Trigger trigger() {}
    4.3 TriggerBuilder.newTrigger()
            .forJob(绑定具体JobDetail的实例名)
            .withSchedule(CronScheduleBuilder.cronSchedule(cron表达式))
            .withIdentity(“触发器名”,“任务组名”)
            .build();
示例:
@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail jobDetail() {
        return JobBuilder.newJob(MyJob.class)
                .storeDurably() // 持久化(此JobDetail没有与Trigger绑定也不会被清理掉)
                .withIdentity("job1", "group1") // 唯一标识
                .usingJobData("count",1) // (共享数据)数据初始化
                .build();
    }

    @Bean
    public Trigger trigger() {
        String cron = "0/2 * * * * ? *"; // 每隔2秒执行一次
        return TriggerBuilder.newTrigger()
                .forJob(jobDetail()) // 关联jobDetail
                .withIdentity("trigger1", "group1") // 唯一标识
                .withSchedule(CronScheduleBuilder.cronSchedule(cron)) // 定时规则
                .build();
    }
}
  1. 运行启动类即可

Quartz实现定时任务的动态调度
上面的例子虽已使用Quartz实现了定时任务功能,但定时任务是有系统调度的,也就是说定时任务会不断执行,直到项目结束运行;而在实际业务场景中,往往是需要根据需求自由的进行定时任务的生成、暂停、恢复、删除和更新操作的,所以,就需要动态调度定时任务。
Quartz本身没有提供动态调度的功能,需要自己根据相关的API开发。

动态定时任务的使用场景:

  1. 订单成功后自动发送短信通知
  2. 每日推送的功能引发用户不满,不再定时推送
  3. 每日下班前30分钟发送工作报表,遇节假日暂停发送
    … …

实现动态调度定时任务,前面配置的QuartzConfig类其实就不需要了,然后自己去编写调度器。
Scheduler调度器常用的API:
  scheduler.scheduleJob(jobDetail, trigger); 生成一个新的定时任务;
  scheduler.pauseJob(jobKey); 暂停一个定时任务,参数jobKey代表该任务的唯一标识;
  scheduler.resumeJob(jobKey); 恢复一个定时任务,参数jobKey代表该任务的唯一标识;
  scheduler.deleteJob(jobKey); 删除一个定时任务,参数jobKey代表该任务的唯一标识;
  scheduler.rescheduleJob(triggerKey, trigger); 修改一个定时任务;
  scheduler.triggerJob(jobKey); 执行一次定时任务;

动态调度定时任务的具体实现:

  1. 定义任务类,实现Job接口,重写execute方法
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 定时任务执行的功能
        // context-定时任务执行的环境(上下文,可以理解为调度器:执行定时任务)
        
    }
}
  1. 任务Bean(创建的调度器通过JobBean来识别操作哪个Job)
    JobBean
       String jobName
       String jobClass
       String cronExpression
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JobBean {
    /**
    * 任务名字
    */
    private String jobName;

    /**
    * 任务类名
    */
    private String jobClass;

    /**
    * cron表达式
    */
    private String cronExpression;
}
  1. 操作任务的工具栏(重点)
    JobUtils
       static void createJob(Scheduler scheduler, JobBean jobBean)
       static void pauseJob(Scheduler scheduler, String jobName)
       static void resumeJob(Scheduler scheduler, String jobName)
       static void deleteJob(Scheduler scheduler, String jobName)
       static void runJobOnce(Scheduler scheduler, String jobName)
       static void modifyJob(Scheduler scheduler, JobBean jobBean)
public class JobUtils {
    /**
    * 生成/创建一个定时任务
    * @param scheduler 调度器
    * @param jobBean 任务bean
    */
    public static void createJob(Scheduler scheduler, JobBean jobBean) {
        Class<? extends Job> jobClass = null;
        JobDetail jobDetail = null;
        Trigger trigger = null;
        String cron = jobBean.getCronExpression();
        try {
            jobClass = (Class<? extends Job>) Class.forName(jobBean.getJobClass());
            jobDetail = JobBuilder.newJob(jobClass)
                    .storeDurably()
                    .withIdentity(jobBean.getJobName())
                    .usingJobData("count",1)
                    .build();
            trigger = TriggerBuilder.newTrigger()
                    .forJob(jobDetial)
                    .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                    .withIdentity(jobBean.getJobName() + "_trigger")
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
    * 暂停定时任务
    * @param scheduler 调度器
    * @param jobName 任务名字
    */
    public static void pauseJob(Scheduler scheduler, String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName);
        try {
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
    * 恢复定时任务
    * @param scheduler 调度器
    * @param jobName 任务名字
    */
    public static void resumeJob(Scheduler scheduler, String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName);
        try {
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
    * 删除定时任务
    * @param scheduler 调度器
    * @param jobName 任务名字
    */
    public static void deleteJob(Scheduler scheduler, String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName);
        try {
            scheduler.deleteJob(jobKey);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
    * 执行一次定时任务(立即执行一次)
    * @param scheduler 调度器
    * @param jobName 任务名字
    */
    public static void runJobOnce(Scheduler scheduler, String jobName) {
        JobKey jobKey = JobKey.jobKey(jobName);
        try {
            scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }

    /**
    * 修改定时任务
    * @param scheduler 调度器
    * @param jobBean 任务bean
    */
    public static void modifyJob(Scheduler scheduler, JobBean jobBean) {
        // 获取任务触发器的唯一标识
        TriggerKey triggerKey = TriggerKey.triggerKey(jobBean.getJobName() + "_trigger");
        try {
            // 通过触发器的唯一标识获取触发器对象
            CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            // 使用新的cron表达式创建新的触发器
            String newCron = jobBean.getCronExpression();
            CronTrigger newTrigger =  oldTrigger.getTriggerBuilder()
                    .withSchedule(CronScheduleBuilder.cronSchedule(newCron).withMisfireHandlingInstructionDoNothing()) //后面的.withMisfireHandlingInstructionDoNothing()是为了让调度器忽略所有错过的任务,按正常调度正常执行
                    .bulid();
            // 调度器更新任务的触发器
            scheduler.rescheduleJob(triggerKey, newTrigger);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
}
  1. 编写控制器操作任务
    QuartzController
       String createJob()
       String pauseJob()
       String resumeJob()
       String deleteJob()
       String modifyJob()
@RestController
@RequestMapping("/quartz")
public class QuartzController {
    @Autowired
    Scheduler scheduler;

    private String jobName = "myjob";
    
    @GetMapping("/create")
    public String createJob() {
        JobBean jobBean = new JobBean(jobName, MyJob.Class.getName, "0/2 * * * * ?");
        JobUtils.createJob(scheduler, jobBean);
        return "定时任务创建成功";
    }

    @GetMapping("/pause")
    public String pauseJob() {
        JobUtils.pauseJob(scheduler, jobName);
        return "定时任务暂停成功";
    }

    @GetMapping("/resume")
    public String resumeJob() {
        JobUtils.resumeJob(scheduler, jobName);
        return "定时任务恢复成功";
    }

    @GetMapping("/delete")
    public String deleteJob() {
        JobUtils.deleteJob(scheduler, jobName);
        return "定时任务删除成功";
    }

    @GetMapping("/once")
    public String runOnceJob() {
        JobUtils.runJobOnce(scheduler, jobName);
        return "定时任务执行一次成功";
    }

    @GetMapping("/modify")
    public String modifyJob() {
        JobBean jobBean = new JobBean(jobName, MyJob.Class.getName, "0/5 * * * * ?");
        JobUtils.modifyJob(scheduler, jobBean);
        return "定时任务修改成功";
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2164836.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习MRI处理过程中搜到的宝藏网站

今天浏览网页查到了一些宝藏网站&#xff0c;正好记录一下&#xff0c;后面搜到好东东再接着填充&#xff0c;方便查阅~ &#xff08;1&#xff09;牛人网站 这个网站是在搜集seed关键词时发现的&#xff0c;用pdf文档记录&#xff0c;可下载查阅&#xff0c;条理清晰&#xf…

Python | Leetcode Python题解之第433题最小基因变化

题目&#xff1a; 题解&#xff1a; class Solution:def minMutation(self, start: str, end: str, bank: List[str]) -> int:if start end:return 0def diffOne(s: str, t: str) -> bool:return sum(x ! y for x, y in zip(s, t)) 1m len(bank)adj [[] for _ in ra…

基于Node.js+Express+MySQL+VUE实现的在线电影视频点播网站管理系统的设计与实现

1. 引言 随着互联网技术的快速发展和普及&#xff0c;人们获取信息的方式发生了巨大变化&#xff0c;其中在线视频点播服务因其便捷性和多样性而受到广泛欢迎。在线电影视频点播网站作为这一领域的代表&#xff0c;不仅需要满足用户观看需求&#xff0c;同时也需为管理员提供高…

架构设计读后有感——设计流程

架构也是有套路的 绝大部分的公司中&#xff0c;架构师都是技术人员的终极方向&#xff0c;是技术金字塔的顶端&#xff0c;那么普通人员要想走上这条路&#xff0c;需要掌握适当的方法&#xff0c;逐步完善架构 &#x1f3f9;1 有的放矢——识别复杂度 分析复杂性是设计架构的…

vscode 代码格式setting设置

{"editor.tabSize": 2,"eslint.validate": ["javascript", // 用eslint的规则检测js文件"vue","html","typescript","typescriptreact"],"editor.codeActionsOnSave": {"source.fixAll…

【NLP】daydayup 循环神经网络基本结构,pytorch实现

RNN 循环神经网络 循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;是一种神经网络结构&#xff0c;专门用于处理序列数据。 RNN结构原理 RNN架构中&#xff0c;网络通过循环把信息从一个处理步骤传递到下一个&#xff0c;这个循环结构被称为隐…

10个强大的AI驱动售后服务,助力成功

目录 10个AI驱动售后服务 1. 预测性维护以减少停机时间2. 自动化客户支持3. 个性化产品推荐4. 情感分析以改进反馈管理5. AI驱动自助服务平台6. 主动客户沟通7. 智能保修管理8. AI增强服务团队培训9. 高级分析服务优化10. AI驱动忠诚度计划 利用AI提升售后服务体验 在当今竞争…

探索OpenAI的全新里程碑:o1模型

近期&#xff0c;人工智能领域迎来了一项重要突破——OpenAI发布了其最新的语言模型o1。作为一款专为解决复杂问题设计的新一代大语言模型&#xff08;LLM&#xff09;&#xff0c;o1标志着该公司在智能推理能力方面迈出了重要的一步。尽管这个新系统仍处于初步阶段&#xff0c…

分析二极管的交流响应(1)——直流分析,Q点的计算

二极管的直流电路分析我们可以用理想模型&#xff0c;恒压降模型和折线模型去近似分析&#xff0c;但是这些模型仅限于我们的信号是直流的情况。如果遇到交流信号&#xff0c;我们该如何去分析呢&#xff1f; 首先我们来理解Q点的概念&#xff1a; 看这个Q点里的“Q”是个什么…

【C++】C++中如何处理多返回值

十四、C中如何处理多返回值 本部分也是碎碎念&#xff0c;因为这些点都是很小的点&#xff0c;构不成一篇文章&#xff0c;所以本篇就是想到哪个点就写哪个点。 1、C中如何处理多个返回值 写过python的同学都知道&#xff0c;当你写一个函数的返回时&#xff0c;那是你想返回…

【Javascript】原生实现deep watch,使用proxy逐层建立数据监听

原理 使用 proxy对象处理数据&#xff0c;添加监听&#xff0c;然后递归再次添加直到全部添加完毕 代码 /*** 给对象递归建立数据监听&#xff0c;可以监测每一层的每个键的变化* * param {*} obj // 目标对象 * param {*} callback //回调。通过key处理对应的变化* param {…

机器学习EDA探查工具Pandas profiling

在最初的数据探查的时候&#xff0c;可以通过pandas的函数&#xff0c;以及matplotlib做图像绘图&#xff0c;这个工作比较重复和低效&#xff0c;所以pandas针对常用的数据列统计和展示&#xff0c;做了EDA工具profiling&#xff0c;可以自动帮助数据分析。 问题1&#xff1a…

java核心基础

文章目录 1. Java开发基础1.1 DOS常用命令:&#xff08;以MAC常用命令比较&#xff09;1.2 JVM、JRE、JDK之间的关系1.3 Java开发环境的搭建1.4 Java的注释&#xff0c;标识符、标识符的命名规范1.5 变量和常量的定义及初始化1.6 Java的运算符1.7 三大语句1.8 常用的类1.8.1 ja…

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-21

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-21 1. AIvril: AI-Driven RTL Generation With Verification In-The-Loop Authors: Mubashir ul Islam, Humza Sami, Pierre-Emmanuel Gaillardon, and Valerio Tenace AIVRIL: 人工智能驱动的RTL生成与验证内…

OpenAPI鉴权(二)jwt鉴权

一、思路 前端调用后端可以使用jwt鉴权&#xff1b;调用三方接口也可以使用jwt鉴权。对接多个三方则与每个third parth都约定一套token规则&#xff0c;因为如果使用同一套token&#xff0c;token串用可能造成权限越界问题&#xff0c;且payload交叉业务不够清晰。下面的demo包…

springBoot --> 学习笔记

文章目录 认识 SpringBoot第一个 SpringBoot 程序彩蛋 banner &#xff08;emmmmm&#xff0c;哈哈哈哈哈哈&#xff0c;牛逼&#xff01;&#xff09;SpringBoot 配置配置文件第一个 yaml 配置 成功案例yaml 存在 松散绑定 JSR 303 数据校验多环境配置以及文件位置访问静态资源…

教你制作一个二维码就能查分的系统

学生和家长对于成绩查询的需求日益增长。为了满足这一需求&#xff0c;很多学校和老师开始使用二维码查询系统&#xff0c;以提高效率和保护隐私。以下内容就是如何制作一个简单易用的成绩查询二维码系统的步骤&#xff1a; 1. 准备电子表格 老师需要准备一个包含学生成绩的电…

(已解决)vscode如何传入argparse参数来调试/运行python程序

文章目录 前言调试传入参数运行传入参数延申 前言 以前&#xff0c;我都是用Pycharm专业版的&#xff0c;由于其好像在外网的时候&#xff0c;不能够通过VPN来连接内网服务器&#xff0c;我就改用了vscode。改用了之后&#xff0c;遇到一个问题&#xff0c;调试或者运行python…

基于Qt5.12.2开发 MQTT客户端调试助手

项目介绍 该项目是一个基于 Qt 框架开发的桌面应用程序&#xff0c;主要用于与 MQTT 服务器进行连接和通信。通过该应用&#xff0c;用户可以连接到 MQTT 服务器&#xff0c;订阅主题、发布消息并处理接收到的消息。项目使用 QMqttClient 类来实现 MQTT 协议的客户端功能&…

第128集《大佛顶首楞严经》

《大佛顶如来密因修正了义诸菩萨万行首楞严经》。监院法师慈悲&#xff0c;诸位法师&#xff0c;诸位同学&#xff0c;阿弥陀佛&#xff01; 请大家打开讲义296面。 庚一、总示阴相&#xff08;分四&#xff1a;辛一、结前行阴尽相。辛二、正明识阴区宇。辛三、悬示识阴尽相。…