背景
最近要用到这个定时任务,之前就简单使用注解的那种方式,需求一变化,就得重新修改。就想到了动态定时任务,连接数据库来动态选择,这样确实解决了问题。但是仍然有一个缺陷,就是没法设置任务的执行时间,无法做到像 QQ 发说说那样,给 xdm 祝福生日时,设定说说为晚上00:00发布。本文就以上三点用自己的思路写了一个小Demo,希望对大家有所帮助。
前言:
阅读完本文:
知晓 SpringBoot
用注解如何实现定时任务
明白 SpringBoot
如何实现一个动态定时任务 (与数据库相关联实现)
理解 SpringBoot
实现设置时间执行定时任务 (使用 ThreadPoolTaskScheduler
实现)
一、注解实现定时任务
用注解实现是真的简单,只要会 cron 表达式就行。?♂️
第一步: 主启动类上加上 @EnableScheduling
注解
@EnableScheduling@SpringBootApplicationpublicclassSpringBootScheduled{publicstaticvoidmain(String[]args){SpringApplication.run(SpringBootScheduled.class);}}
第二步:写一个类,注入到Spring,关键就是 @Scheduled
注解。 () 里就是 cron 表达式,用来说明这个方法的执行周期的。 ?
我常常也记不住,通常是在线生成的: Cron 表达式在线生成
/***定时任务静态定时任务**第一位,表示秒,取值0-59*第二位,表示分,取值0-59*第三位,表示小时,取值0-23*第四位,日期天/日,取值1-31*第五位,日期月份,取值1-12*第六位,星期,取值1-7,1表示星期天,2表示星期一*第七位,年份,可以留空,取值1970-2099*@authorcrush*@since1.0.0*@Date:2021-07-2721:13*/@ComponentpublicclassSchedulingTaskBasic{/***每五秒执行一次*/@Scheduled(cron="*/5****?")privatevoidprintNowDate(){longnowDateTime=System.currentTimeMillis();System.out.println("固定定时任务执行:--->"+nowDateTime+",此任务为每五秒执行一次");}}
执行效果:
源码在文末。?
二、动态定时任务
其实也非常的简单。
2.1、建数据表
第一步:建个数据库表。
CREATETABLE`tb_cron`(`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'动态定时任务时间表',`cron_expression`varchar(50)CHARACTERSETutf8COLLATEutf8_general_ciNOTNULLCOMMENT'定时任务表达式',`cron_describe`varchar(50)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULLCOMMENT'描述',PRIMARYKEY(`id`)USINGBTREE)ENGINE=InnoDBAUTO_INCREMENT=3CHARACTERSET=utf8COLLATE=utf8_general_ciROW_FORMAT=Dynamic;INSERTINTO`tb_cron`VALUES(1,'00/1***?','每分钟执行一次');
2.2、导入依赖,基础编码
第二步:导入数据库相关依赖,做到能从数据库查询数据。大家都会。?♂️
第三步: 编码
实体类:
@Data@TableName("tb_cron")publicclassCron{privateLongid;privateStringcronExpression;privateStringcronDescribe;}
mapper层:
@RepositorypublicinterfaceCronMapperextendsBaseMapper<Cron>{@Select("selectcron_expressionfromtb_cronwhereid=1")StringgetCron1();}
2.3、主要实现代码
第四步:写一个类 实现 SchedulingConfigurer
?
实现 void configureTasks(ScheduledTaskRegistrar taskRegistrar);
方法,此方法的作用就是根据给定的 ScheduledTaskRegistrar 注册 TaskScheduler 和特定的Task实例
@ComponentpublicclassCompleteScheduleConfigimplementsSchedulingConfigurer{@Autowired@SuppressWarnings("all")CronMappercronMapper;/***执行定时任务.*/@OverridepublicvoidconfigureTasks(ScheduledTaskRegistrartaskRegistrar){taskRegistrar.addTriggerTask(//1.添加任务内容(Runnable)()->System.out.println("执行动态定时任务1:"+LocalDateTime.now().toLocalTime()+",此任务执行周期由数据库中的cron表达式决定"),//2.设置执行周期(Trigger)triggerContext->{//2.1从数据库获取执行周期Stringcron=cronMapper.getCron1();//2.2合法性校验.if(cron!=null){//OmittedCode..}//2.3返回执行周期(Date)returnnewCronTrigger(cron).nextExecutionTime(triggerContext);});}}
2.4、效果
注意:当你修改了任务执行周期后,生效时间为执行完最近一次任务后。这一点是需要注意的,用生活中的例子理解就是我们取消电话卡的套餐也要下个月生效,含义是一样的。
源码同样在文末。
三、实现设置时间定时任务
通常业务场景是我前言中说的那样,是一次性的定时任务。如:我设置了我写的这篇文章的发布时间为今天下午的两点,执行完就删除没有了。一次性的。
实现主要依靠于 TaskScheduler
的ScheduledFuture<?> schedule(Runnable task, Trigger trigger);
方法来实现。其本质和动态定时任务的实现是一样的。
3.1、实现重点
代码中都含有注解,不多做阐述。
importcn.hutool.core.convert.ConverterRegistry;importcom.crush.scheduled.entity.Task;importlombok.extern.slf4j.Slf4j;importorg.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;importorg.springframework.stereotype.Component;importjava.time.LocalDateTime;importjava.util.*;importjava.util.concurrent.ConcurrentHashMap;importjava.util.concurrent.CopyOnWriteArrayList;importjava.util.concurrent.ScheduledFuture;/***@authorcrush*/@Component@Slf4jpublicclassDynamicTaskService{/***以下两个都是线程安全的集合类。*/publicMap<String,ScheduledFuture<?>>taskMap=newConcurrentHashMap<>();publicList<String>taskList=newCopyOnWriteArrayList<String>();privatefinalThreadPoolTaskSchedulersyncScheduler;publicDynamicTaskService(ThreadPoolTaskSchedulersyncScheduler){this.syncScheduler=syncScheduler;}/***查看已开启但还未执行的动态任务*@return*/publicList<String>getTaskList(){returntaskList;}/***添加一个动态任务**@paramtask*@return*/publicbooleanadd(Tasktask){//此处的逻辑是,如果当前已经有这个名字的任务存在,先删除之前的,再添加现在的。(即重复就覆盖)if(null!=taskMap.get(task.getName())){stop(task.getName());}//hutool工具包下的一个转换类型工具类好用的很ConverterRegistryconverterRegistry=ConverterRegistry.getInstance();DatestartTime=converterRegistry.convert(Date.class,task.getStart());//schedule:调度给定的Runnable,在指定的执行时间调用它。//一旦调度程序关闭或返回的ScheduledFuture被取消,执行将结束。//参数://任务–触发器触发时执行的Runnable//startTime–任务所需的执行时间(如果这是过去,则任务将立即执行,即尽快执行)ScheduledFuture<?>schedule=syncScheduler.schedule(getRunnable(task),startTime);taskMap.put(task.getName(),schedule);taskList.add(task.getName());returntrue;}/***运行任务**@paramtask*@return*/publicRunnablegetRunnable(Tasktask){return()->{log.info("---动态定时任务运行---");try{System.out.println("此时时间==>"+LocalDateTime.now());System.out.println("task中设定的时间==>"+task);Thread.sleep(10);}catch(InterruptedExceptione){e.printStackTrace();}log.info("---end--------");};}/***停止任务**@paramname*@return*/publicbooleanstop(Stringname){if(null==taskMap.get(name)){returnfalse;}ScheduledFuture<?>scheduledFuture=taskMap.get(name);scheduledFuture.cancel(true);taskMap.remove(name);taskList.remove(name);returntrue;}}
3.2、异步线程池的配置
/***异步线程池ThreadPoolExecutor配置类**@Author:crush*@Date:2021-07-2314:14*/@ConfigurationpublicclassThreadPoolTaskExecutorConfig{@BeanpublicThreadPoolTaskSchedulersyncScheduler(){ThreadPoolTaskSchedulersyncScheduler=newThreadPoolTaskScheduler();syncScheduler.setPoolSize(5);//这里给线程设置名字,主要是为了在项目能够更快速的定位错误。syncScheduler.setThreadGroupName("syncTg");syncScheduler.setThreadNamePrefix("syncThread-");syncScheduler.initialize();returnsyncScheduler;}}
3.3、业务代码
这里需要注意一个点,我给项目中的 LocalDateTime
做了类型转换。这里没贴出来(主要是复制以前的代码遗留下来的,源码中都有)
大家简单使用,可以直接用注解 标注在 LocalDateTime
属性上即可。
packagecom.crush.scheduled.controller;importcom.crush.scheduled.entity.Task;importcom.crush.scheduled.service.DynamicTaskService;importorg.springframework.web.bind.annotation.*;importjava.util.List;/***@Author:crush*@Date:2021-07-2915:26*version1.0*/@RestController@RequestMapping("/dynamicTask")publicclassDynamicTaskController{privatefinalDynamicTaskServicedynamicTask;publicDynamicTaskController(DynamicTaskServicedynamicTask){this.dynamicTask=dynamicTask;}/***查看已开启但还未执行的动态任务*@return*/@GetMappingpublicList<String>getStartingDynamicTask(){returndynamicTask.getTaskList();}/***开启一个动态任务*@paramtask*@return*/@PostMapping("/dynamic")publicStringstartDynamicTask(@RequestBodyTasktask){//将这个添加到动态定时任务中去dynamicTask.add(task);return"动态任务:"+task.getName()+"已开启";}/***根据名称停止一个动态任务*@paramname*@return*/@DeleteMapping("/{name}")publicStringstopDynamicTask(@PathVariable("name")Stringname){//将这个添加到动态定时任务中去if(!dynamicTask.stop(name)){return"停止失败,任务已在进行中.";}return"任务已停止";}}
简单封装的一个实体类:
/***@Author:crush*@Date:2021-07-2915:35*version1.0*/@Data@Accessors(chain=true)//方便链式编写习惯所然publicclassTask{/***动态任务名曾*/privateStringname;/***设定动态任务开始时间*/privateLocalDateTimestart;}
3.4、效果
??
开启一个动态任务:
查看开启还未执行的动态任务:
执行结果:
和我们代码中是一模一样的。
停止任务:
再去查看就是已经停止的拉
四、自言自语
源码
:springboot-scheduled
本文就是简单介绍了,具体使用时还需要根据具体情况具体分析啦。
你好,我是博主
宁在春,
希望本篇文章能让你感到有所收获!!!