前言:对于Quartz(kwɔrts)之前在公司用过,比较尴尬的是真的只是用过,写个控制器在任务系统里配置一下cron表达式就完事 https://github.com/songwie/task。从那天起我就对Quartz失去了兴趣,后来在使用SpringBoot的时候了解到Scheduled(Spring 3.1之后支持),就用Scheduled搭建了一个简单的任务系统。当时我就在想怎么弄个到点就能执行的任务,因为用Scheduled注解有很大的局限性,查阅了好多文档(我好后悔我当初没有学好英语,造成现在一直很反感英文文档,每次都是搜索中文博客(开源中国,推酷,简书segmentfault,scdn,.....),如果我英语给力,技术也不会这么差)还是没有发现比较好的解决方案,当时正好做众筹票务APP,比如用户下单之后30分钟没有支付需要将该订单的库存回收并改变订单状态为失效。如果轮询1秒一次的话,这样会频繁查询订单表,将所有失效时间小于当前时间的并且未支付的所有订单设置为失效,这样即不能做到及时,量比较多的话还会频繁锁表,订单表对于票务网站本身就很高频的,不管是下订单,支付过程的状态变更,还是查询订单状态。我当时采用了很low的方式,就是查询订单的时候,如果失效时间小于或者等于当前时间就update该ID的状态。对于用户来说没有什么变化,如果10条订单中只有一个就只会更新一个。问题来了,如果该用户没有查询订单是不是状态还是未支付的状态呢?所以我写了一个1分钟一次的轮询来解决状态问题。今天我不是来BB这种方案,其实Quartz除了CronTrigger还有SimpleTrigger。
(百度百科)Quartz是一个完全由java编写的开源作业调度框架,是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。
Scheduler – 与scheduler交互的主要API,这就是所谓的作业调度器
Job – 你通过scheduler执行任务,你的任务类需要实现的接口;
JobDetail – 定义Job的实例;
Trigger – 触发Job的执行;
3.1 首先在主程序开启对定时任务的支持
@EnableScheduling
3.2 编写需要定时跑的代码
@Scheduled(fixedRate=10000)
public void test(){
System.out.println("程序跑来了");
}
通过@Scheduled注解 使用fixedRate时表示多少次执行一次,单位是毫秒
其实还有cron表达式属性,具体设置可以参考https://cloud.tencent.com/developer/article/1472746
我整合是设置时间到点执行,不是上面的cron表达式那种计划时间或者循环执行。
4.1添加依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
4.2 配置job交给spring管理
package com.yudianbank.task.config;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;
@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;
}
}
主要目的就是解决job类 注入其他service或者使用Spring组件
4.3配置javaconfig bean
@Configuration
public class BeanConfig {
@Autowired
JobFactory jobFactory;
/**
* 注册调度器
*
* @return
*/
@Bean
public SchedulerFactoryBean createSchedulerFactoryBean() {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setJobFactory(jobFactory);
return schedulerFactoryBean;
}
@Bean
public JobDetailImpl createJobDetailImpl() {
return new JobDetailImpl();
}
}
4.4 任务方法
/**
* 添加任务
*
* @param job 任务类
* @param date 任务时间
* @param jobDetailName 任务消息名字
* @param triggerIdentity 触发器的唯一名
* @param description 触发器的描述
*/
public synchronized void addJob(Job job, Date date, String jobDetailName, String triggerIdentity, String description, String url, String bodyParameter) {
//job类的参数
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("url", url);
jobDataMap.put("bodyParameter", bodyParameter);
jobDataMap.put("jobDetailName", jobDetailName);
//这是job类的任务
jobDetail.setName(jobDetailName);
jobDetail.setJobClass(job.getClass());
jobDetail.setJobDataMap(jobDataMap);
//作业触发器
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerIdentity).withDescription(description)
// .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(3))//循环间隔多久
.startAt(date)//执行时间
.build();
//作业调度器
try {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
//Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} catch (SchedulerException e) {
logger.error("任务调度器异常:", e);
4.5 编写任务类(这里就可以注入service)
@Service
public class JobTask implements Job {
static final Logger logger = LoggerFactory.getLogger(JobTask.class);
@Autowired
ExecuteTaskService executeTaskService;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDataMap jobDataMap = jobExecutionContext.getMergedJobDataMap();
String url = jobDataMap.get("url").toString();
String bodyParameter = jobDataMap.get("bodyParameter").toString();
String jobDetailName = jobDataMap.get("jobDetailName").toString();
try {
executeTaskService.execute(url, bodyParameter, jobDetailName);
} catch (Exception ex) {
}
}
关于job的状态数据(即JobDataMap)和并发性,还有一些地方需要注意。在job类上可以加入一些注解,这些注解会影响job的状态和并发性。
@DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例。请注意这里的用词。拿前一小节的例子来说,如果“SalesReportJob”类上有该注解,则同一时刻仅允许执行一个“SalesReportForJoe”实例,但可以并发地执行“SalesReportForMike”类的一个实例。所以该限制是针对JobDetail的,而不是job类的。但是我们认为(在设计Quartz的时候)应该将该注解放在job类上,因为job类的改变经常会导致其行为发生变化。
@PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据。和 @DisallowConcurrentExecution注解一样,尽管注解是加在job类上的,但其限制作用是针对job实例的,而不是job类的。由job类来承载注解,是因为job类的内容经常会影响其行为状态(比如,job类的execute方法需要显式地“理解”其”状态“)。
如果你使用了@PersistJobDataAfterExecution注解,我们强烈建议你同时使用@DisallowConcurrentExecution注解,因为当同一个job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap中存储的数据很可能是不确定的。
调用4.4的任务方法即可