首页 > 其他分享 >单体项目中定时任务的实现-详细教程

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

时间:2024-09-26 21:50:45浏览次数:3  
标签:教程 String 单体 jobName 任务 scheduler 定时 public

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

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

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

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 "定时任务修改成功";
    }
}

标签:教程,String,单体,jobName,任务,scheduler,定时,public
From: https://blog.csdn.net/weixin_61769871/article/details/142455860

相关文章

  • Docker 教程:在 Mac M2 上运行 PostgreSQL
    前提条件  -已安装[DockerDesktop] 创建Docker卷  首先,创建一个名为`pgdata`的Docker卷,以便用于PostgreSQL数据存储。  dockervolumecreatepgdata运行PostgreSQL容器  使用以下命令启动PostgreSQL容器:  dockerrun--namepostgres -ePOS......
  • 【实用教程】如何使用kernelbase.dll修复工具解决常见问题?利用KernelBase.dll修复工具
    引言:在使用Windows操作系统时,有时会遇到因KernelBase.dll文件损坏或缺失而导致的系统崩溃、程序无响应等问题。KernelBase.dll是Windows核心库文件之一,负责提供许多基本的系统级功能。当这个文件出现问题时,系统的稳定性和性能会受到影响。幸运的是,通过使用KernelBase.dll修复......
  • 宝塔面板WordPress建站教程:海外服务器选择与详细安装步骤
     一、什么是宝塔面板?宝塔面板(BTPanel)是一款简单易用的服务器管理工具,适合那些不熟悉命令行操作的用户。它允许你通过一个图形化界面轻松管理服务器和网站,尤其适合新手用户快速搭建像WordPress这样的网站。二、准备工作选择服务器与域名搭建网站的第一步是选择合适的服务器......
  • Adobe Bridge简体中文版百度云下载与安装(附教程)
    如大家所熟悉的,AdobeBridge常常简称为BR,是一款数字资产管理软件,可以帮助用户浏览、组织、搜索和管理各种类型的媒体文件,如照片、音频、视频等。Bridge发展至今有许多个版本,目前来说常用的版本有Bridge2018、2020及最新的2024版本,我们可根据自己的电脑配置及需求去选择合适的......
  • 传奇微端架设教程-GEE引擎
    登录器和网站配置好后,我们进入游戏后会发现是黑屏的,更新不了地图和NPC这些,因为还没有做微端,会黑屏也是正常的。有些GEE版本有配套的微端程序,有些版本没有,需要自己去gee官网下载更新时间比引擎还靠后的引擎包,引擎包里有微端程序,没有的微端服务器私信我或者添加下方卡片获取......
  • 传奇外网架设教程带图文解说—Gee引擎
    架设前准备工作:①通过百度网盘下载版本、补丁、客户端和DBC2000。版本解压到D盘,客户端解压到D盘或是E盘,补丁先不解压②安装和配置DBC2000,有些版本不一定用的是DBC2000数据库,看引擎默认的数据库是哪个DBC数据库的安装与配置不会的看我前面的教程点击查看在架设前确认一下版......
  • Java单体服务和集群分布式SpringCloud微服务的理解
    单体应用存在的问题1.随着业务的发展开发变得越来越复杂。2.修改或者新增,需要对整个系统进行测试、重新部署。3.一个模块出现问题,很可能导致整个系统崩溃。4.多个开发团队同时对数据进行管理,容易产生安全漏洞。5.各个模块使用同一种技术进行开发,各个模块很难根据实际情况......
  • 「滚雪球学SpringCloud」教程导航帖(已完结)
    写在前面我是bug菌,CSDN|掘金|InfoQ|51CTO|华为云|阿里云|腾讯云等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计30w+;硬核微信公众号「猿圈奇妙屋」,欢迎你的......
  • python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
    一、python线程死锁与递归锁死锁现象所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程代码示例:fromthreadingimport......
  • 基于springboot的企业人事管理系统的设计与实现 (含源码+sql+视频导入教程+论文)
    ......