Spring Boot定时器的使用
Posted noob-mengling
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Boot定时器的使用相关的知识,希望对你有一定的参考价值。
本章介绍Spring Boot中定时器的使用方法,总结个人对一些定时器用法的了解,如有错误欢迎指正。
定时器是什么?
定时器就是让程序定时执行某些任务,不需要人工手动执行。
为什么要使用定时器?
使用定时器,有很多好处。举个例子:在平台api对接中,请求通常需要携带token信息,而token有可能有固定时效。在token失效后,需要重新获取token信息。此时可以使用定时任务重新获取token。
怎么用定时器?
在Java中,有很多中方式处理定时任务。Timer,SpringBoot的@Scheduled注解,SpringBoot整合Quartz等方式。
整合Quartz
Spring Boot中使用Quartz是通过继承QuartzJobBean的方式,创建JobDetail和Trigger以达到定时效果。
1、创建SimpleScheduleExtendsQuartzJobTask任务类
package com.study.controller.schedule; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; import java.util.Date; public class SimpleScheduleExtendsQuartzJobTask extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("do schedule extends quartz job task " + new Date()); } }
2、创建JobDetail和Trigger。创建JobDetail时需要使用JobBuilder.newJob()方法添加任务类,withIdentity()方法指定key,build()方法进行构建。TriggerBuilder.forJob()方法添加JobDetail,withSchedule()方法添加schedule。schedule可以通过SimpleScheduleBuilder进行构建,withIntervalInSeconds()指明每次执行定时任务的间隔时间。
package com.study.config; import com.study.controller.schedule.SimpleScheduleExtendsQuartzJobTask; import org.quartz.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SimpleScheduleConfig { @Bean public JobDetail simpleJobBeanDetails() { return JobBuilder.newJob(SimpleScheduleExtendsQuartzJobTask.class).withIdentity("simpleJobBean").storeDurably().build(); } @Bean public Trigger simpleTrigger() { SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(15).repeatForever(); return TriggerBuilder.newTrigger().withIdentity("simpleTrigger").forJob(simpleJobBeanDetails()).withSchedule(scheduleBuilder).build(); } }
上述方法已经可以到达效果,但仍然有需要改进的地方。我们来思考几个问题:
1、有大量的定时任务时如何解决?
2、当某个任务的执行时间需要修改时如何解决?
3、如何控制任务的启动和停止?
我们是否可以将所有的任务的类名,执行方法, 参数,以及执行时间保存在数据库中,通过对数据库的修改和访问,实现任务调度。
我们约定所有的定时脚本,都通过http://localhost:8080/authenticate/runScript?type=DoSimpleJobTask&args=aa方式进行访问,其中type指定类名,设置统一的访问方法为runScript()方法,通过反射的方式处理不同的任务。创建AuthenticateController
package com.study.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; @RestController public class AuthenticateController { @RequestMapping("authenticate/runScript") public String runScript(HttpServletRequest request, HttpServletResponse response) { String type = request.getParameter("type"); if (null == type) { System.out.println("type is null"); return "ok"; } String argList = request.getParameter("args"); type = type.replace("/", "."); String className = "script." + type; String methodName = "runScript"; try { Class<?> customClass = Class.forName(className); Method method = customClass.getMethod(methodName, String.class); Object result = method.invoke(customClass.newInstance(), argList); if (null != result) { if ((Boolean) result) { } } } catch (Exception e) { e.printStackTrace(); } return "ok"; } }
因为配置信息保存在数据库中,所以添加maven依赖访问数据库
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version> </dependency>
application.properties中设置mysql的连接配置。此处做下说明,设置时区serverTimezone=UTC,useSSL=false,因为mysql驱动为6.0版本,driver也改成了com.mysql.cj.jdbc.Driver
#mysql spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useSSL=false spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
任务配置类
package com.study.orm; public class ScheduleTaskConfig { private Integer id; private String name; private String task; private String cron; public ScheduleTaskConfig() { } public ScheduleTaskConfig(Integer id, String name, String task, String cron) { this.id = id; this.name = name; this.task = task; this.cron = cron; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getTask() { return task; } public void setTask(String task) { this.task = task; } public String getCron() { return cron; } public void setCron(String cron) { this.cron = cron; } }
创建Mapper
package com.study.mapper; import com.study.orm.ScheduleTaskConfig; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface ScheduleTaskConfigMapper { @Select("select * from tb_task") List<ScheduleTaskConfig> fetchList(); }
service接口及实现
package com.study.service; import com.study.orm.ScheduleTaskConfig; import java.util.List; public interface ScheduleTaskConfigService { List<ScheduleTaskConfig> fetchList(); }
package com.study.service.impl; import com.study.mapper.ScheduleTaskConfigMapper; import com.study.orm.ScheduleTaskConfig; import com.study.service.ScheduleTaskConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class ScheduleTaskConfigServiceImpl implements ScheduleTaskConfigService { @Autowired private ScheduleTaskConfigMapper scheduleTaskConfigMapper; @Override public List<ScheduleTaskConfig> fetchList() { return scheduleTaskConfigMapper.fetchList(); } }
扫描mapper
package com; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @MapperScan("com.study.mapper") @SpringBootApplication @EnableScheduling public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }
考虑到需要控制任务的启动和停止,采用ThreadPoolTaskScheduler进行任务调度。ThreadPoolTaskScheduler的schedule方法可以创建ScheduledFuture,ScheduledFuture中可以通过cancel(true),取消任务。
package com.study.controller.schedule; import com.mchange.v1.util.MapUtils; import com.study.orm.ScheduleTaskConfig; import com.study.service.ScheduleTaskConfigService; import com.study.utils.ServiceResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.TriggerContext; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledFuture; @RequestMapping("schedule") @RestController public class ScheduleTaskManageController { @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; @Bean public ThreadPoolTaskScheduler getThreadPoolTaskScheduler() { return new ThreadPoolTaskScheduler(); } private Map<Integer, ScheduledFuture> scheduledFutureMap = new HashMap<>(); @Autowired private ScheduleTaskConfigService scheduleTaskConfigService; public void _init(){ System.out.println("++++++++++++init++++++++++++++++"); List<ScheduleTaskConfig> scheduleTaskConfigs = scheduleTaskConfigService.fetchList(); if (!CollectionUtils.isEmpty(scheduleTaskConfigs)) { for (ScheduleTaskConfig scheduleTaskConfig : scheduleTaskConfigs) { ScheduledFuture future = threadPoolTaskScheduler.schedule(new Runnable() { @Override public void run() { try {
//task的内容设置为 curl "http://localhost:8080/authenticate/runScript?type=DoSimpleJobTask&args=aa"
//请求通过authenticate/runScript映射,通过反射调用script中的任务 System.out.println(scheduleTaskConfig.getTask()); Process process = Runtime.getRuntime().exec(scheduleTaskConfig.getTask()); } catch (IOException e) { e.printStackTrace(); } } }, new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { return new CronTrigger(scheduleTaskConfig.getCron()).nextExecutionTime(triggerContext); } }); scheduledFutureMap.put(scheduleTaskConfig.getId(), future); } } } public void _destory(){ System.out.println("++++++++++++++++destory++++++++++++"); if (scheduledFutureMap != null && !scheduledFutureMap.isEmpty()) { for (Integer key : scheduledFutureMap.keySet()) { System.out.println("scheduledFutureMap :" + key); scheduledFutureMap.get(key).cancel(true); } } scheduledFutureMap.clear(); } @RequestMapping("start") public String startScheduleTask() { _init(); return "success"; } @RequestMapping("stop") public String stopScheduleTask() { _destory(); return "success"; } }
创建tb_task存放需要执行的任务
CREATE TABLE `tb_task` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(1000) DEFAULT NULL, `task` varchar(1000) DEFAULT NULL, `cron` varchar(1000) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
select * from tb_task
1 测试 curl "http://localhost:8080/authenticate/runScript?type=DoSimpleJobTask&args=aa" 0/35 * * * * ?
2 测试访问百度 curl "www.baidu.com" 0/35 * * * * ?
最后再script下添加不同的任务类
package script; public class DoSimpleJobTask { public Boolean runScript(String args) { System.out.println("this is test schedule task : " + args); return false; } }
附cron表达式说明:
0/35 * * * * ?
秒 分 时 天 年 ?
大概的思路就是这样,可以根据自己需要添加其它。如
- 添加event,在项目启动之后,自动执行_init方法进行初始化。
- 当重新添加或修改了task表中的数据时,可以先调用stop方法,取消所有的任务,然后再调用start方法添加。
- 或者在页面编辑数据表中数据时,对应新增的方法,可以添加到schedule中,对于修改的方法可以根据id值获取到scheduledFutureMap的值,先取消,然后重新添加一个等等。
package com.study.event; import com.study.controller.schedule.ScheduleTaskManageController; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; @Component public class ApplicationStartup implements ApplicationListener<ApplicationStartedEvent> { @Override public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) { System.out.println("++++++++++++++application started event+++++++++++++++"); ScheduleTaskManageController scheduleTaskManageController = applicationStartedEvent.getApplicationContext().getBean(ScheduleTaskManageController.class); scheduleTaskManageController._destory(); scheduleTaskManageController._init(); } }
如果有什么错误,或者有更好的处理方式,也可以留言告诉我
以上是关于Spring Boot定时器的使用的主要内容,如果未能解决你的问题,请参考以下文章
Spring boot实现定时任务二:使用注解@scheduled和@EnableScheduling