springboot定时任务主要有以下三种创建方式:
1、基于注解(@Scheduled)
@Configuration //标记配置类
@EnableScheduling // 开启定时任务
public class SaticScheduleTask {
//添加定时任务
//cron表达式:cron是一个字符串,之间通过空格隔开,分割成6,7个域,每个域代表一种含义
@Scheduled(cron = "")
//或直接指定时间间隔,例如:5秒
//@Scheduled(fixedRate=5000)
private void configureTasks() {
System.err.println("定时任务执行时间" + LocalDateTime.now());
}
}
2、基于接口(SchedulingConfigurer) 前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了。
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(
//1.添加任务内容(Runnable)
() -> System.out.println("执行定时任务: " + LocalDateTime.now().toLocalTime()),
//2.设置执行周期(Trigger)
triggerContext -> {
//2.1 从数据库获取执行周期
String cron = cronMapper.getCron();
//2.2 合法性校验.
if (StringUtils.isEmpty(cron)) {
// Omitted Code ..
}
//2.3 返回执行周期(Date)
return new CronTrigger(cron).nextExecutionTime(triggerContext);
}
);
}
3、基于注解设定多线程定时任务
//@Component注解用于对那些比较中立的类进行注释;
//相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释
@Component
@EnableScheduling // 1.开启定时任务
@EnableAsync // 2.开启多线程
public class MultithreadScheduleTask {
@Async
@Scheduled(fixedDelay = 1000) //间隔1秒
public void first() throws InterruptedException {
System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
Thread.sleep(1000 * 10);
}
@Async
@Scheduled(fixedDelay = 2000)
public void second() {
System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
}
}
什么是Quartz
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:
持久性作业 - 就是保持调度定时的状态;
作业管理 - 对调度作业进行有效的管理;
在我们实际的项目中,当Job过多的时候,肯定不能人工去操作,这时候就需要一个任务调度框架,帮我们自动去执行这些程序。
上面三个部分就是Quartz的基本组成部分:
Quartz框架使用
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
// 定义任务并将其绑定到我们的HelloJob类
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();
// 触发器定时每四十秒触发
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
Quartz框架具体API,Job和Trigger
Quartz API核心接口有:
说明:
Scheduler的生命期,从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger(即执行job)
SchedulerBuilder接口的各种实现类,可以定义不同类型的调度计划(schedule);
DateBuilder类包含很多方法,可以很方便地构造表示不同时间点的java.util.Date实例(如定义下一个小时为偶数的时间点,如果当前时间为9:43:27,则定义的时间为10:00:00)。
Job
package org.quartz;
public interface Job {
void execute(JobExecutionContext var1) throws JobExecutionException;
}
JobDetail jobDetail = newJob(HelloJob.class)
.withIdentity("myJob" + myJob.getJobId())
.usingJobData("myJob", JSONObject.toJSONString(myJob))
.build();
Trigger
Key
JobDetail
public class HelloJob implements Job {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
System.err.println("Hello! HelloJob is executing.");
}
}
可以看到,我们传给scheduler一个JobDetail实例,因为我们在创建JobDetail时,将要执行的job的类名传给了JobDetail,所以scheduler就知道了要执行何种类型的job;每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。
那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?
答案就是:JobDataMap,JobDetail对象的一部分。
JobDataMap
JobDetail jobDetail = newJob(HelloJob.class)
.withIdentity("myJob" + myJob.getJobId())
.usingJobData("myJob", JSONObject.toJSONString(myJob))
//可以写入多个数据
.usingJobData("jobSays", "Hello World!")
.build();
usingJobData方法
public JobBuilder usingJobData(String dataKey, String value) {
this.jobDataMap.put(dataKey, value);
return this;
}
在job的执行过程中,可以从JobDataMap中取出数据,如下示例:
public class HelloJob implements Job {
@Autowired
private ProjectService projectService;
@Autowired
private RegularService regularService;
@Autowired
private CompareService compareService;
@Autowired
private InterfaceService interfaceService;
@Override
public void execute(JobExecutionContext context) {
//获取trigger
Trigger trigger = context.getTrigger();
//获取数据
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String jobJsonString = jobDataMap.getString("myJob");
//fastJson 解析成具体实体类对象
cn.XXX.ict.entity.Job job = JSONObject.parseObject(jobJsonString, cn.XXX.ict.entity.Job.class);
//通过trigger获取job标识
JobKey jobKey = trigger.getJobKey();
//主要任务
......
}
}
注意
如果你使用的是持久化的存储机制,在决定JobDataMap中存放什么数据的时候需要小心,因为JobDataMap中存储的对象都会被序列化,因此很可能会导致类的版本不一致的问题;
如果你在job类中,为JobDataMap中存储的数据的key增加set方法那么Quartz的默认JobFactory实现在job被实例化的时候会自动调用这些set方法,这样你就不需要在execute()方法中显式地从map中取数据了。
在Job执行时,JobExecutionContext中的JobDataMap为我们提供了很多的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。
Job的其它特性
通过JobDetail对象,可以给job实例配置的其它属性有:
JobExecutionException
线程池配置
<!-- 定时框架线程池配置--!>
quartz:
properties:
org:
quartz:
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 50
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
如何为Quartz的Job自动装配Spring容器Bean
Quartz配置
@Configuration
public class QuartzConfig {
@Autowired
private MyJobFactory myJobFactory;
public QuartzConfig(MyJobFactory jobFactory){
this.myJobFactory = jobFactory;
}
/**
* 配置SchedulerFactoryBean
* 将一个方法产生为Bean并交给Spring容器管理
* @return
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
// Spring提供SchedulerFactoryBean为Scheduler提供配置信息,并被Spring容器管理其生命周期
SchedulerFactoryBean factory = new SchedulerFactoryBean();
// 设置自定义Job Factory,用于Spring管理Job bean
factory.setJobFactory(myJobFactory);
return factory;
}
@Bean(name = "scheduler")
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}
MyJobFactory
@Component
public class MyJobFactory extends AdaptableJobFactory {
//这个对象Spring会帮我们自动注入进来,也属于Spring技术范畴.
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
//进行注入,这属于Spring的技术,不清楚的可以查看Spring的API.
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
依赖:
<dependency>
<groupId>org.quartz-schedulergroupId>
<artifactId>quartzartifactId>
<version>2.2.3version>
dependency>
<dependency>
<groupId>org.quartz-schedulergroupId>
<artifactId>quartz-jobsartifactId>
<version>2.2.3version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>5.1.5.RELEASEversion>
dependency>
Controller层调用
@CrossOrigin
@RestController
@RequestMapping("/job")
@Api(tags = "4", description = "定时器管理")
public class JobController {
@Autowired
private RegularService regularService;
@Autowired
private JobService jobService;
@Autowired
private ProjectService projectService;
@Autowired
private JobTask jobTask;
@PostMapping("/create")
@ApiOperation(value = "创建定时任务", notes = "创建定时任务")
@Transactional
public Result create(String projectName, String startTime, String finishTime, String frequency, String srcVersion) {
//实体类
MyJob myJob = new MyJob ();
String jobId = UUID.randomUUID().toString();
myJob .setJobId(jobId);
myJob .setProjectId(projectId);
myJob .setStartTime(DateUtil.parse(startTime, DateUtil.YCHAR_06));
myJob .setFinishTime(DateUtil.parse(finishTime, DateUtil.YCHAR_06));
myJob .setFrequency(frequency);
myJob .setSrcVersion(srcVersion);
jobService.save(myJob );
//执行定时任务
jobTask.addJob(myJob);
return Result.success();
}
}
JobTask
@Component
@Slf4j
public class JobTask {
@Autowired
private JobService jobService;
@Autowired
private Scheduler scheduler;
public void updateJob(Job myJob) {
removeJob(myJob);
addJob(myJob);
}
public void removeJob(MyJob myJob) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey("myTrigger" + myJob.getJobId(), "group1");
scheduler.pauseTrigger(triggerKey);
scheduler.unscheduleJob(triggerKey);
} catch (SchedulerException e) {
log.error("error", e);
}
}
public void addJob(MyJob myJob) {
try {
JobDetail jobDetail = newJob(HelloJob.class)
.withIdentity("myJob" + myJob.getJobId())
.usingJobData("myJob", JSONObject.toJSONString(myJob))
.build();
Date startTime = myJob.getStartTime();
Date endTime = myJob.getFinishTime();
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger" + myJob.getJobId(), "group1")
.startAt(startTime)
.endAt(endTime)
.withSchedule(CronScheduleBuilder.cronSchedule(myJob.getFrequency()))
.build();
//将trigger和jobDetail加入这个调度
scheduler.scheduleJob(jobDetail, trigger);
//启动scheduler
scheduler.start();
} catch (Exception e) {
log.error("error", e);
}
}
}
HelloJob
public class HelloJob implements Job {
@Autowired
private ProjectService projectService;
@Autowired
private RegularService regularService;
@Autowired
private CompareService compareService;
@Autowired
private InterfaceService interfaceService;
@Override
public void execute(JobExecutionContext context) {
//获取trigger
Trigger trigger = context.getTrigger();
//获取数据
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String jobJsonString = jobDataMap.getString("myJob");
//fastJson 解析成具体实体类对象
cn.XXX.ict.entity.Job job = JSONObject.parseObject(jobJsonString, cn.XXX.ict.entity.Job.class);
//通过trigger获取job标识
JobKey jobKey = trigger.getJobKey();
//主要任务
......
}
}