几个月前才从Spring MVC+Dubbo转到Spring Boot,于是新的定时任务需要用Spring Boot和现在的其他项目进行整合,介于对Spring Boot了解不深,这个整合也花了一天多的时间去了解。最近把工作完成了,功能也稍微完善了,于是写来记录一下。
Spring Boot最大有点就是不再使用繁重的XML配置文件,使用几个注解就轻松搞定,但在Quartz整合中,虽然也能使用几个简单注解就轻松完成配置,但我还是习惯写配置文件,因为自由度比较高。我在其中实现了多定时任务和动态编辑定时任务,主要是修改定时规则。
1.导入依赖包
项目是直接用STS直接建的Spring Boot项目。
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 该依赖必加,里面有sping对schedule的支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
2.Quartz配置类
配置部分主要涉及到的就是Quartz的三个部分,JobDetail(创建任务)、JobTrigger(任务触发器)和Scheduler(任务调度)。其中JobDetail与JobTrigger的关系可以为一对一和多对一,JobTrigger与Scheduler也是一样,而之后的定时任务动态管理配置类就可以根据上面的关系去管理。
package com.itsm.conf.quartz;
import java.util.List;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import com.itsm.job.SyncWorkOrderFileTask;
/**
* @ClassName: QuartzConfigration
* @Description: Quartz配置
* @author lcy
* @date 2018年3月16日 上午11:32:50
*/
@Configuration
public class QuartzConfigration {
private Logger LOGGER = LoggerFactory.getLogger(QuartzConfigration.class);
/*----------------------------------- 每10分钟同步文件 ------------------------------------------*/
/**
* attention: Details:配置定时任务
*/
@Bean(name = "syncWorkOrderFile10MJobDetail")
public MethodInvokingJobDetailFactoryBean syncWorkOrderFile10MJobDetailFactoryBean(SyncWorkOrderFileTask task) {// task为需要执行的任务,即任务类
MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
/*
* 是否并发执行 例如每5s执行一次任务,但是当前任务还没有执行完,就已经过了5s了,
* 如果此处为true,则下一个任务会执行,如果此处为false,则下一个任务会等待上一个任务执行完后,再开始执行
*/
jobDetail.setConcurrent(false);
jobDetail.setName("syncWorkOrderFile10MJobDetail");// 设置任务的名字,自定义
jobDetail.setGroup("srd");// 设置任务的分组,自定义
// 这些属性都可以存储在数据库中,在多任务的时候使用
/*
* 为需要执行的实体类对应的对象
*/
jobDetail.setTargetObject(task);
/*
* syncWorkOrderFile10M为需要执行的方法
* 通过这几个配置,告诉JobDetailFactoryBean我们需要执行定时执行SyncWorkOrderFileTask类中的syncWorkOrderFile10M方法
*/
jobDetail.setTargetMethod("syncWorkOrderFile10M");
LOGGER.info("定时任务【同步工单文件】配置完成");
return jobDetail;
}
/**
* attention: Details:配置定时任务的触发器,也就是什么时候触发执行定时任务
*/
@Bean(name = "syncWorkOrderFile10MJobTrigger")
public CronTriggerFactoryBean syncWorkOrderFile10MCronJobTriggerFactoryBean(JobDetail syncWorkOrderFile10MJobDetail) {
CronTriggerFactoryBean jobTrigger = new CronTriggerFactoryBean();
/*
* 为需要执行的定时任务
*/
jobTrigger.setJobDetail(syncWorkOrderFile10MJobDetail);
jobTrigger.setCronExpression("0 0/10 * * * ?");// 初始时的cron表达式
jobTrigger.setName("syncWorkOrderFile10MJobTrigger");// trigger的name
LOGGER.info("定时触发器【同步工单文件】配置完成");
return jobTrigger;
}
/*----------------------------------- 测试任务 ------------------------------------------*/
@Bean(name = "testJobDetail")
public MethodInvokingJobDetailFactoryBean testJobDetailFactoryBean(TestTask task) {
MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
jobDetail.setConcurrent(false);
jobDetail.setName("testJobDetail");
jobDetail.setGroup("srd");
jobDetail.setTargetObject(task);
jobDetail.setTargetMethod("sayGoodbye");
LOGGER.info("定时任务【测试】配置完成");
return jobDetail;
}
@Bean(name = "testJobTrigger")
public CronTriggerFactoryBean testCronJobTrigger(JobDetail testJobDetail) {
CronTriggerFactoryBean jobTrigger = new CronTriggerFactoryBean();
jobTrigger.setJobDetail(testJobDetail);
jobTrigger.setCronExpression("40-50 * * * * ?");
jobTrigger.setName("testJobTrigger");
LOGGER.info("定时触发器【测试】配置完成");
return jobTrigger;
}
/**
* attention: Details:定义quartz调度工厂
* 这里的参数为List,Spring会把上面声明的触发器都放到这个集合中
*/
@Bean(name = "scheduler")
public SchedulerFactoryBean schedulerFactory(List<Trigger> triggers) {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
// 用于quartz集群,QuartzScheduler 启动时更新己存在的Job
bean.setOverwriteExistingJobs(true);
// 延时启动,应用启动1秒后
bean.setStartupDelay(1);
/*
* 注册触发器
* 这里的setTriggers()方法,参数可以为多个Trigger,
* 所以将配置的每个触发器放入里面即可(但当触发器多时,这里的参数就会很多,目前没找到更好的方式)
*/
bean.setTriggers(triggers.get(0), triggers.get(1));
LOGGER.info("调度工厂配置完成,Quartz在应用启动1秒后启动");
return bean;
}
}
由于是使用Quartz中的类去自定义,所以自由度很高,只能去了解源码进行定制还是挺方便的。3.动态管理任务配置类
其实这个动态管理本质也是一个定时任务,我这里使用的是通过现有任务的配置与数据库中存储的配置进行对比,来判断是否执行更新配置的方式来达到动态管理任务,目前只做了设置定时规则。其他的也可以往里面添加。(我使用的是JPA来与数据库交互)
任务配置实体类:
package com.itsm.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* @ClassName: Config
* @Description: 定时任务配置实体类
* @author lcy
* @date 2018年3月16日 下午2:58:19
*/
@Entity
public class Config {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", length = 10)
private Long id;
/**
* 任务名
*/
@Column(name = "job_name", length = 50)
private String jobName;
/**
* 调度器bean的name
*/
@Column(name = "job_trigger", length = 50)
private String jobTrigger;
/**
* cron公式
*/
@Column(name = "cron", length = 20)
private String cron;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public String getJobTrigger() {
return jobTrigger;
}
public void setJobTrigger(String jobTrigger) {
this.jobTrigger = jobTrigger;
}
public String getCron() {
return cron;
}
public void setCron(String cron) {
this.cron = cron;
}
@Override
public String toString() {
return "Config [id=" + id + ", jobName=" + jobName + ", jobTrigger=" + jobTrigger + ", cron=" + cron + "]";
}
}
定时查库更新任务类:
package com.itsm.conf.quartz;
import java.util.List;
import javax.annotation.Resource;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.itsm.entity.Config;
import com.itsm.entity.repository.ConfigRepository;
import com.itsm.util.SpringUtil;
/**
* @ClassName: ScheduleRefreshDatabase
* @Description: 定时查库更新任务
* @author lcy
* @date 2018年3月16日 上午11:56:47
*/
/*
* 在这个类中的方法上需要@Scheduled注解配合@EnableScheduling使用。
* @EnableScheduling是声明这是个计划任务配置类
* @Scheduled是声明这是个计划任务
* @Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注
* 当加入这个注解的好处是可以直接使用Spring的依赖注入
*/
@Configuration
@EnableScheduling// 必加
@Component// 必加
public class ScheduleRefreshDatabase {
private Logger LOGGER = LoggerFactory.getLogger(ScheduleRefreshDatabase.class);
@Autowired
private ConfigRepository repository;
/*
* 将QuartzConfigration.class中的调度工厂注入
*/
@Resource(name = "scheduler")
private Scheduler scheduler;
/*
* 该方法和任务类方法一样,写业务就行,区别在于这个方法用了@Scheduled注解去控制
* 按上面的任务配置类的写法,正式的任务方法不需要加这个注解
*/
@Scheduled(fixedRate = 1000 * 60) // 每隔1min查库,并根据查询结果决定是否重新设置定时任务
public void scheduleUpdateCronTrigger() throws SchedulerException {
List<Config> configs = repository.findAll();
for (Config config : configs) {
String jobTrigger = config.getJobTrigger();// 获取triggerBean的name
CronTrigger trigger = (CronTrigger) SpringUtil.getBean(jobTrigger);// 根据name获取对应的CronTrigger的bean
// SpringUtil是我自定义的工具类,为了方便获取bean写的
/*
* 这个是关键点,需要通过从调度工厂获取的当前trigger才能进行接下来的操作,
* 否则只通过bean的name拿到的trigger的配置都会一直是QuartzConfigration类中的配置而不是加入到调度工厂中的触发器
*/
trigger = (CronTrigger) scheduler.getTrigger(trigger.getKey());
String currentCron = trigger.getCronExpression();// 当前Trigger使用的规则
String searchCron = config.getCron();// 从数据库查询出来的规则
if (currentCron.equals(searchCron)) {
// 如果当前使用的cron表达式和从数据库中查询出来的cron表达式一致,则不刷新任务
} else {
LOGGER.info("任务【" + config.getJobName() + "】的调度规则已变更为[" + searchCron + "],正在重新设置.");
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(searchCron);// 表达式调度构建器
trigger = trigger.getTriggerBuilder().withIdentity(trigger.getKey()).withSchedule(scheduleBuilder)
.build();// 按新的cronExpression表达式重新构建trigger
scheduler.rescheduleJob(trigger.getKey(), trigger);// 按新的trigger重新设置job执行
LOGGER.info("已设置完毕.");
}
}
}
}
3.任务类
因为和定时查库更新任务类基本一样,就是方法不用加注解,我就不放代码上来了。
其实可拓展性还是很高的,但如果只是简单的定时任务,用注解是最快的,几个注解就搞定。