springboot有俩种实现定时任务的方式,第一种就是spring自带的俩个定时任务注解@EnableScheduling(用于配置类上)与@Scheduled(用于定时任务方法上)具体用法如下:
package com.tycho.readserialport.schedule;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import lombok.extern.slf4j.Slf4j;
/**
*
* springboot自带的schedule定时任务配置方式
* ll 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
@Configuration
@EnableScheduling
@Slf4j
public class ScheduleTask {
@Scheduled(cron = "0 0 0 */1 * ?")
private void configureTasks() {
log.info("你你你你!!!1");
}
/**
* 检测获取设备状态
*/
@Scheduled(cron = "0 1/1 1/1 1/1 * ?")
private void detectDeviceStatus() {
log.info("你你你你!!!1");
}
}
org.springframework.boot
spring-boot-starter-quartz
package com.tycho.readserialport.schedule;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;
@Component
public class QuartzJobBeanFactory extends AdaptableJobFactory implements ApplicationContextAware {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) throws BeansException {
capableBeanFactory = context.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
//进行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
注入工厂对象及任务调度对象
package com.tycho.readserialport.config;
import org.quartz.Scheduler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import com.tycho.readserialport.schedule.QuartzJobBeanFactory;
@Configuration
public class QuartzConfig {
@Bean
public QuartzJobBeanFactory autoWiringSpringBeanJobFactory() {
return new QuartzJobBeanFactory();
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setJobFactory(autoWiringSpringBeanJobFactory());
return schedulerFactoryBean;
}
@Bean
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}
封装任务相关方法
package com.tycho.readserialport.schedule;
import java.util.Map;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
/**
1.
2. @Description 定时任务管理类 负责增加、删除、启动、暂停定时任务
*/
public class QuartzManager {
private static SchedulerFactory gSchedulerFactory = new StdSchedulerFactory();
private static String JOB_GROUP_NAME = "查询设备状态";
private static String TRIGGER_GROUP_NAME = "查询设备状态触发器";
/**
* @Description: 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名
* @param jobName 任务名
* @param cls 任务
* @param time 时间设置,参考quartz说明文档
* ll 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static void addJob(String jobName, Class cls, String time,Map<String,Object> params) {
try {
Scheduler sched = gSchedulerFactory.getScheduler();
JobDetail job = JobBuilder.newJob(cls)
.withIdentity(jobName, JOB_GROUP_NAME)
.build();
// 添加具体任务方法
job.getJobDataMap().putAll(params);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(time);
// 按新的cronExpression表达式构建一个新的trigger
Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(jobName, TRIGGER_GROUP_NAME)
.withSchedule(scheduleBuilder).build();
//交给scheduler去调度
sched.scheduleJob(job, trigger);
// 启动
if (!sched.isShutdown()) {
sched.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description: 添加一个定时任务
* @param jobName 任务名
* @param jobGroupName 任务组名
* @param triggerName 触发器名
* @param triggerGroupName 触发器组名
* @param jobClass 任务
* @param time 时间设置,参考quartz说明文档
* ll 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void addJob(String jobName, String jobGroupName,
String triggerName, String triggerGroupName, Class jobClass,
String time) {
try {
Scheduler sched = gSchedulerFactory.getScheduler();
JobDetail job = JobBuilder.newJob(jobClass)
.withIdentity(jobName, jobGroupName)
.build();
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(time);
// 按新的cronExpression表达式构建一个新的trigger
Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity(triggerName, triggerGroupName)
.withSchedule(scheduleBuilder).build();
sched.scheduleJob(job, trigger);
// 启动
if (!sched.isShutdown()) {
sched.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description: 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)
* @param jobName
* @param time
* ll 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
public static void modifyJobTime(String jobName, String time) {
TriggerKey triggerKey = TriggerKey.triggerKey(
jobName, TRIGGER_GROUP_NAME);
try {
Scheduler sched = gSchedulerFactory.getScheduler();
CronTrigger trigger =(CronTrigger) sched.getTrigger(triggerKey);
if (trigger == null) {
return;
}
String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(time)) {
CronScheduleBuilder scheduleBuilder =CronScheduleBuilder.cronSchedule(time);
//按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
.withSchedule(scheduleBuilder).build();
//按新的trigger重新设置job执行
sched.rescheduleJob(triggerKey, trigger);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description: 修改一个任务的触发时间
* @param triggerName
* @param triggerGroupName
* @param time
* @author ll
* @date ll 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
public static void modifyJobTime(String triggerName,
String triggerGroupName, String time) {
TriggerKey triggerKey = TriggerKey.triggerKey(
triggerName, triggerGroupName);
try {
Scheduler sched = gSchedulerFactory.getScheduler();
CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
if (trigger == null) {
return;
}
String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(time)) {
// trigger已存在,则更新相应的定时设置
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
.cronSchedule(time);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
.withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
sched.resumeTrigger(triggerKey);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description 移除一个任务(使用默认的任务组名,触发器名,触发器组名)
* @param jobName
* @author ll
* @date ll 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
public static void removeJob(String jobName) {
TriggerKey triggerKey = TriggerKey.triggerKey(
jobName, TRIGGER_GROUP_NAME);
JobKey jobKey = JobKey.jobKey(jobName, JOB_GROUP_NAME);
try {
Scheduler sched = gSchedulerFactory.getScheduler();
Trigger trigger = (Trigger) sched.getTrigger(triggerKey);
if (trigger == null) {
return;
}
sched.pauseTrigger(triggerKey);;// 停止触发器
sched.unscheduleJob(triggerKey);// 移除触发器
sched.deleteJob(jobKey);// 删除任务
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description: 移除一个任务
* @param jobName
* @param jobGroupName
* @param triggerName
* @param triggerGroupName
* @author ll
* @date ll 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
public static void removeJob(String jobName, String jobGroupName,
String triggerName, String triggerGroupName) {
TriggerKey triggerKey = TriggerKey.triggerKey(
jobName, triggerGroupName);
JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
try {
Scheduler sched = gSchedulerFactory.getScheduler();
sched.pauseTrigger(triggerKey);// 停止触发器
sched.unscheduleJob(triggerKey);// 移除触发器
sched.deleteJob(jobKey);// 删除任务
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description:暂停一个任务
* @param jobName
* @param jobGroupName
* ll 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
public static void pauseJob(String jobName, String jobGroupName) {
JobKey jobKey =JobKey.jobKey(jobName, jobName);
try {
Scheduler sched = gSchedulerFactory.getScheduler();
sched.pauseJob(jobKey);
} catch (SchedulerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* @Description:暂停一个任务(使用默认组名)
* @param jobName
* @param jobGroupName
* ll 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
public static void pauseJob(String jobName) {
JobKey jobKey =JobKey.jobKey(jobName, JOB_GROUP_NAME);
try {
Scheduler sched = gSchedulerFactory.getScheduler();
sched.pauseJob(jobKey);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
/**
* @Description:启动所有定时任务
* @author ll
* @date ll 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
public static void startJobs() {
try {
Scheduler sched = gSchedulerFactory.getScheduler();
sched.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description 关闭所有定时任务
* @author qgw
* @date ll 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
public static void shutdownJobs() {
try {
Scheduler sched = gSchedulerFactory.getScheduler();
if (!sched.isShutdown()) {
sched.shutdown();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
定时任务的增删停止,此处添加定时任务方法都是通过传入自己实现的Job类,然后反射创建对象,构造JobDetail(同一个Job构造的是不同的JobDetail),最后用schedule调度任务
3. 接下来,使用自己定义的任务方法
新增一个定时任务需要实现Job接口
package com.tycho.readserialport.schedule;
import java.util.Date;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import lombok.extern.slf4j.Slf4j;
/**
* 定时任务类
* 读站号状态任务
* @author ll
* 2020年6月08日 ✿(。◕ᴗ◕。)✿
*/
@Component
//保证同一个Jobdetail不会并发执行
@DisallowConcurrentExecution
@Slf4j
public class DetectDeviceStatusJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//解决spring bean无法自动注入 (@Autowired注入失效问题)
//解决不了就使用工具类获取IOC中的bean对象SpringContextUtils.getBean("");
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
//JobDataMap获取任务中传递的数据,实现了Map接口
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String cron = jobDataMap.getString("cron");
//获取开启任务时设置的withIdentity
//JobKey jobKey = context.getJobDetail().getKey();
log.info("启动读取站号状态任务-----当前时间:{},当前线程:{},当前cron:{}",new Date(),Thread.currentThread().getName(),cron);
try {
//ScheduleJob任务运行时具体参数,可自定义
}catch (Exception e) {
log.error("读取站号状态任务运行异常:{}",e);
}
}
}
@DisallowConcurrentExecution,因为定时任务job最后要通过JobDetail来运行,此注解用来保证同一个JobDetail对象不会同时运行(相同的任务可以同时运行),JobDataMap可以用来获取传给定时任务的参数,如果你的自定义Job注入的Ioc中对象,则需要SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);来保证注入成功(quartz整合产生的问题,具体自查),JobKey用来获取JobDetail中设置的 withIdentity
package com.tycho.readserialport;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;
import com.tycho.readserialport.schedule.DetectDeviceStatusJob;
import com.tycho.readserialport.schedule.QuartzManager;
import lombok.extern.slf4j.Slf4j;
/**
* 项目启动成功监听类
* @author ll
*
*/
@Slf4j
public class StartListener implements ApplicationListener<ApplicationStartedEvent>{
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
//项目启动,开启读站号状态定时任务
Environment env = event.getApplicationContext().getEnvironment();
String cron = env.getProperty("myconfig.job.cron");
log.info("程序启动" + Instant.now());
Map<String,Object> param = new HashMap<>();
param.put("cron", cron);
QuartzManager.addJob("读站号状态1", DetectDeviceStatusJob.class, cron,param);
QuartzManager.addJob("读站号状态2", DetectDeviceStatusJob.class, cron,param);
QuartzManager.addJob("读站号状态3", DetectDeviceStatusJob.class, cron,param);
QuartzManager.addJob("读站号状态4", DetectDeviceStatusJob.class, cron,param);
}
}
server:
port: 8081
myconfig:
serial:
#采集数据串口
data-port: COM1,COM2,COM3,COM4
#进线信号串口
incoming: COM5
#波特率
baud: 115200
job:
#配置定时任务执行周期
cron: 0 1/1 1/1 1/1 * ?
通过实现ApplicationListener接口,监听项目的启动,在重写的方法中开启定时任务,此处的Cron时间表达式可通过Yml属性配置文件配置,通过环境配置类Environment 获得属性配置,param可给定时任务传递参数,通过JobDataMap获取
5. 运行
关于Quartz线程池可添加配置文件quartz.properties自行定义,也可采用默认的线程池
总结Quartz与schedule异同
1. schedule比较简单,自动开机启动,quartz需要借助实现ApplicationListener监听项目启动来开启定时任务。
2. schedule与quartz的cron都可以通过配置文件来配置(上文schedule可用过@Value注解获取配置文件值)
3. schedule不能传参,quartz可以传入参数
4. schedule开机启动(动态开启需借助ThreadPoolTaskScheduler.schedule()实现),quartz可根据项目运行动态开启定时任务