场景:使用定时任务进行结算操作,需要跨服务。
技术:定时任务有很多种,因为数据量较小,不涉及到分布式操作,所以不准备用很重的框架。但是希望定时任务能手动开关,所以springboot自带的定时任务就不合适了。最终决定使用quartz。
job:接口,里面有一个execute方法需要实现。其中参数JobExecutionContext封装了一些跟job有关的信息。
JobDetail:Use a JobKey
with the given name and group to identify the JobDetail
就是说name一样group不一样也不算同一个定时任务。比job多了一个参数group。
Trigger:Use a TriggerKey with the given name and group to identify the Trigger
同样是通过name和group进行唯一标识,但是如果TriggerBuilder在构建trigger时没有使用withIdentity(name,group)
,则使用通用的trigger。
Scheduler:调度容器,Job与Trigger都需要在容器中注册,被容器统一管理。scheduler中有job的一些基础操作,比如启动暂停删除等。
引入包
org.quartz-scheduler
quartz
2.2.1
org.springframework
spring-context-support
启动时tomcat信息
2018-11-22 11:26:57 localhost-startStop-1 INFO Scheduler meta-data: Quartz Scheduler (v2.2.1) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 5 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
quartz存放的方式有很多,我这里为了方便直接存在了内存中,也可以存在数据库。
这里把配置文件直接复制到resource下面就可以了,springboot自动读取配置信息。
# 固定前缀org.quartz
# 主要分为scheduler、threadPool、jobStore、plugin等部分
#
#
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# threadCount和threadPriority将以setter的形式注入ThreadPool实例
# 并发个数
org.quartz.threadPool.threadCount = 5
# 优先级
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 5000
# 默认存储在内存中
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#持久化,数据源的链接方式
#org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#org.quartz.jobStore.tablePrefix = QRTZ_
#org.quartz.jobStore.dataSource = qzDS
#org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
#org.quartz.dataSource.qzDS.URL =
#org.quartz.dataSource.qzDS.user =
#org.quartz.dataSource.qzDS.password =
#org.quartz.dataSource.qzDS.maxConnections = 10
这里记录一个坑,在job实现类中使用@Autowired
注入services进行数据库操作或者别的,就报空指针异常。就是说jobinstance
没有被spring代理到,需要在JobFactory
中使用AutowireCapableBeanFactory
进行手动注入
@Component
public class JobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
//进行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
配置信息:
@Configuration
public class SchedulerConfig {
@Autowired
private JobFactory jobFactory;
@Bean(name="SchedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setJobFactory(jobFactory);
factory.setQuartzProperties(quartzProperties());
return factory;
}
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
//在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
/*
* quartz初始化监听器
*/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
/*
* 通过SchedulerFactoryBean获取Scheduler的实例
*/
@Bean(name="Scheduler")
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
}
使用@RestContorller
进行定时任务的开启暂停等操作。
@RestController
@RequestMapping(value="/job")
public class JobController
{
//加入Qulifier注解,通过名称注入bean
@Autowired @Qualifier("Scheduler")
private Scheduler scheduler;
@PostMapping(value="/addjob")
public void addjob(@RequestBody Map paraMap) throws Exception
{
addJob(paraMap.get("jobClassName"), paraMap.get("jobGroupName"), paraMap.get("cronExpression"));
}
public void addJob(String jobClassName, String jobGroupName, String cronExpression)throws Exception{
// 启动调度器
scheduler.start();
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobClassName, jobGroupName).build();
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
.withSchedule(scheduleBuilder).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
System.out.println("创建定时任务失败"+e);
throw new Exception("创建定时任务失败");
}
}
@PostMapping(value="/pausejob")
public void pausejob(@RequestBody Map paraMap) throws Exception
{
jobPause(paraMap.get("jobClassName"), paraMap.get("jobGroupName"));
}
public void jobPause(String jobClassName, String jobGroupName) throws Exception
{
scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@PostMapping(value="/resumejob")
public void resumejob(@RequestBody Map paraMap) throws Exception
{
jobresume(paraMap.get("jobClassName"), paraMap.get("jobGroupName"));
}
public void jobresume(String jobClassName, String jobGroupName) throws Exception
{
scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@PostMapping(value="/reschedulejob")
public void rescheduleJob(@RequestBody Map paraMap) throws Exception
{
jobreschedule(paraMap.get("jobClassName"), paraMap.get("jobGroupName"), paraMap.get("cronExpression"));
}
public void jobreschedule(String jobClassName, String jobGroupName, String cronExpression) throws Exception
{
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
System.out.println("更新定时任务失败"+e);
throw new Exception("更新定时任务失败");
}
}
@PostMapping(value="/deletejob")
public void deletejob(@RequestBody Map paraMap) throws Exception
{
jobdelete(paraMap.get("jobClassName"), paraMap.get("jobGroupName"));
}
public void jobdelete(String jobClassName, String jobGroupName) throws Exception
{
scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@PostMapping(value="/getalljob")
public List getalljob() throws Exception
{
return getAllJobs();
}
public List getAllJobs(){
try {
List list = new ArrayList();
for (String groupName : scheduler.getJobGroupNames()) {
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
String jobName = jobKey.getName();
String jobGroup = jobKey.getGroup();
//get job's trigger
List triggers = (List) scheduler.getTriggersOfJob(jobKey);
Date nextFireTime = triggers.get(0).getNextFireTime();
list.add(("[jobName] : " + jobName + " [groupName] : "
+ jobGroup + " - " + nextFireTime));
}
}
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static BaseJob getClass(String classname) throws Exception
{
Class> class1 = Class.forName(classname);
return (BaseJob)class1.newInstance();
}
}
import org.quartz.Job;
public interface BaseJob extends Job {
}