Quartz是一款功能强大的任务调度器,可以实现较为复杂的调度功能,如每月一号执行、每天凌晨执行、每周五执行等等,还支持分布式调度。本文使用Springboot+Mybatis+Quartz实现对定时任务的增、删、改、查、启用、停用等功能。并把定时任务持久化到数据库以及支持集群。
1.Scheduler:调度器。所有的调度都是由它控制。
2.Trigger: 触发器。决定什么时候来执行任务。
3.JobDetail & Job: JobDetail定义的是任务数据,而真正的执行逻辑是在Job中。使用JobDetail + Job而不是Job,这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。
1.首先需要到quartz官网下载quartz包,http://www.quartz-scheduler.org/downloads/,我这边下载的是2.2.3的版本,解压后执行\quartz-2.2.3\docs\dbTables下的数据库建表语句,我这边用的是mysql,因此执行tables_mysql_innodb.sql文件
2.添加maven依赖:
org.quartz-scheduler
quartz
2.2.3
slf4j-api
org.slf4j
org.quartz-scheduler
quartz-jobs
2.2.3
slf4j-api
org.slf4j
org.springframework
spring-context-support
3.创建配置文件
在maven项目的resource目录下创建quartz.properties
org.quartz.scheduler.instanceName=quartzScheduler
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.rmi.export=false
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false
org.quartz.jobStore.misfireThreshold=50000
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.useProperties=false
org.quartz.jobStore.clusterCheckinInterval=20000
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=10
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
4.数据原配置
@Configuration
public class DataSourceConfig
{
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.username}")
private String userName;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private int maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMilli}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.testOnReturn}")
private boolean testOnReturn;
@Value("${spring.datasource.poolPreparedStatements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.connectionProperties}")
private String properties;
@Bean
public DataSource dataSource()
{
DruidDataSource configDataSource = new DruidDataSource();
configDataSource.setUrl(url);
configDataSource.setDriverClassName(driverClassName);
configDataSource.setUsername(userName);
configDataSource.setPassword(password);
configDataSource.setInitialSize(initialSize);
configDataSource.setDefaultAutoCommit(true);
configDataSource.setMinIdle(minIdle);
configDataSource.setMaxActive(maxActive);
configDataSource.setMaxWait(maxWait);
configDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
configDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
configDataSource.setValidationQuery(validationQuery);
configDataSource.setTestWhileIdle(testWhileIdle);
configDataSource.setTestOnBorrow(testOnBorrow);
configDataSource.setTestOnReturn(testOnReturn);
configDataSource.setConnectionProperties(properties);
return configDataSource;
}
}
5.quartz配置
首先定义一个jobfactory,继承org.springframework.scheduling.quartz.SpringBeanJobFactory,实现任务实例化方式。
public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory
implements ApplicationContextAware
{
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context)
{
beanFactory = context.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle)
throws Exception
{
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
然后对调度器做一些配置,将之前的数据源配置,jobfactory都注入spring ioc。
@EnableScheduling
@Configuration
public class SchedulerConfig
{
@Autowired
private DataSource dataSource;
@Bean
public JobFactory jobFactory(ApplicationContext applicationContext)
{
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory,
Trigger simpleJobTrigger)
throws IOException
{
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setJobFactory(jobFactory);
factory.setQuartzProperties(quartzProperties());
factory.setTriggers(simpleJobTrigger);
factory.setDataSource(dataSource);
factory.setWaitForJobsToCompleteOnShutdown(
true);//这样当spring关闭时,会等待所有已经启动的quartz job结束后spring才能完全shutdown。
factory.setOverwriteExistingJobs(false);
factory.setStartupDelay(1);
//设置调度器自动运行
factory.setAutoStartup(true);
//设置上下文spring bean name
factory.setApplicationContextSchedulerContextKey("applicationContext");
return factory;
}
/**
* 静态方式配置定时任务
*
* @param jobDetail
* @return
*/
@Bean
public CronTriggerFactoryBean simpleJobTrigger(
@Qualifier("simpleJobDetail") JobDetail jobDetail)
{
CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
factoryBean.setJobDetail(jobDetail);
factoryBean.setStartDelay(1000L);
factoryBean.setName("trigger1");
factoryBean.setGroup("group1");
//周1至周5,每天上午8点至下午18点,每分钟执行一次
factoryBean.setCronExpression("0 0/1 8-18 ? * 2-6");
return factoryBean;
}
@Bean
public JobDetailFactoryBean simpleJobDetail()
{
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
factoryBean.setJobClass(ScheduledJob.class);
factoryBean.setGroup("group1");
factoryBean.setName("job1");
return factoryBean;
}
@Bean
public Properties quartzProperties()
throws IOException
{
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
}
定义一个监听器,用于在任务执行前后做一些操作:
public class SchedulerListener implements JobListener
{
public static final String LISTENER_NAME = "QuartSchedulerListener";
@Override
public String getName()
{
return LISTENER_NAME; //must return a name
}
//任务被调度前
@Override
public void jobToBeExecuted(JobExecutionContext context)
{
String jobName = context.getJobDetail().getKey().toString();
System.out.println("jobToBeExecuted");
System.out.println("Job : " + jobName + " is going to start...");
}
//任务调度被拒了
@Override
public void jobExecutionVetoed(JobExecutionContext context)
{
System.out.println("jobExecutionVetoed");
//可以做一些日志记录原因
}
//任务被调度后
@Override
public void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException)
{
System.out.println("jobWasExecuted");
String jobName = context.getJobDetail().getKey().toString();
System.out.println("Job : " + jobName + " is finished...");
if (jobException != null && !jobException.getMessage().equals(""))
{
System.out.println("Exception thrown by: " + jobName
+ " Exception: " + jobException.getMessage());
}
}
}
定义一个具体的job,用于实现具体的业务逻辑:
public class ScheduledJob implements Job
{
private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class);
@Override
public void execute(JobExecutionContext jobExecutionContext)
throws JobExecutionException
{
//执行任务逻辑....
LOGGER.info("执行自定义定时任务, time is {}.", new Date());
}
}
定义一个管理类,用于实现任务的创建,删除等操作:
@Component
public class SchedulerManager
{
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
private JobListener scheduleListener;
/**
* 开始定时任务
*
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void startJob(String cron, String jobName, String jobGroup,
Class extends Job> jobClass)
throws SchedulerException
{
Scheduler scheduler = schedulerFactoryBean.getScheduler();
if (scheduleListener == null)
{
scheduleListener = new SchedulerListener();
scheduler.getListenerManager().addJobListener(scheduleListener);
}
JobKey jobKey = new JobKey(jobName, jobGroup);
if (!scheduler.checkExists(jobKey))
{
scheduleJob(cron, scheduler, jobName, jobGroup, jobClass);
}
}
/**
* 移除定时任务
*
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void deleteJob(String jobName, String jobGroup)
throws SchedulerException
{
Scheduler scheduler = schedulerFactoryBean.getScheduler();
JobKey jobKey = new JobKey(jobName, jobGroup);
scheduler.deleteJob(jobKey);
}
/**
* 暂停定时任务
*
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void pauseJob(String jobName, String jobGroup)
throws SchedulerException
{
Scheduler scheduler = schedulerFactoryBean.getScheduler();
JobKey jobKey = new JobKey(jobName, jobGroup);
scheduler.pauseJob(jobKey);
}
/**
* 恢复定时任务
*
* @param jobName
* @param jobGroup
* @throws SchedulerException
*/
public void resumeJob(String jobName, String jobGroup)
throws SchedulerException
{
Scheduler scheduler = schedulerFactoryBean.getScheduler();
JobKey triggerKey = new JobKey(jobName, jobGroup);
scheduler.resumeJob(triggerKey);
}
/**
* 清空所有当前scheduler对象下的定时任务【目前只有全局一个scheduler对象】
*
* @throws SchedulerException
*/
public void clearAll()
throws SchedulerException
{
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.clear();
}
/**
* 动态创建Job
* 此处的任务可以配置可以放到properties或者是放到数据库中
* Trigger:name和group 目前和job的name、group一致,之后可以扩展归类
*
* @param scheduler
* @throws SchedulerException
*/
private void scheduleJob(String cron, Scheduler scheduler, String jobName, String jobGroup,
Class extends Job> jobClass)
throws SchedulerException
{
/*
* 此处可以先通过任务名查询数据库,如果数据库中存在该任务,更新任务的配置以及触发器
* 如果此时数据库中没有查询到该任务,则按照下面的步骤新建一个任务,并配置初始化的参数,并将配置存到数据库中
*/
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build();
// 每5s执行一次
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobName,
jobGroup).withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, cronTrigger);
}
}
最后定义一个controller,用于出发定时任务:
@RestController
@RequestMapping("/quartz")
public class QuartzController
{
@Autowired
public SchedulerManager myScheduler;
@RequestMapping(value = "/job2", method = RequestMethod.GET)
public String scheduleJob2()
{
try
{
myScheduler.startJob("0/5 * * * * ?", "job2", "group2", ScheduledJob.class);//每五秒执行一次
return "启动定时器成功";
}
catch (SchedulerException e)
{
e.printStackTrace();
}
return "启动定时器失败";
}
@RequestMapping(value = "/del_job2", method = RequestMethod.GET)
public String deleteScheduleJob2()
{
try
{
myScheduler.deleteJob("job2", "group2");
return "删除定时器成功";
}
catch (SchedulerException e)
{
e.printStackTrace();
}
return "删除定时器失败";
}
}
这样通过页面的调用就能出发定时任务完成特定的业务逻辑了。