您的位置:首页 > 编程语言 > Java开发

SpringBoot之定时任务quartz

2016-10-13 16:01 381 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/ZuiChuDeQiDian/article/details/95681705

撸了今年阿里、网易和美团的面试,我有一个重要发现.......>>>

前言:对于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。

1.Quartz的简单介绍

(百度百科)Quartz是一个完全由java编写的开源作业调度框架,是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。

2.Quartz的主要接口介绍

Scheduler – 与scheduler交互的主要API,这就是所谓的作业调度器

Job – 你通过scheduler执行任务,你的任务类需要实现的接口;

JobDetail – 定义Job的实例;

Trigger – 触发Job的执行;

3.简单的Scheduled任务

3.1 首先在主程序开启对定时任务的支持

@EnableScheduling

3.2 编写需要定时跑的代码

@Scheduled(fixedRate=10000)
public void test(){
System.out.println("程序跑来了");
}

通过@Scheduled注解 使用fixedRate时表示多少次执行一次,单位是毫秒

其实还有cron表达式属性,具体设置可以参考https://my.oschina.net/wangnian/blog/668209

4.整合Quartz

     我整合是设置时间到点执行,不是上面的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的任务方法即可   

博客地址:https://my.oschina.net/wangnian

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: