需要加入的jar包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
Quartz的相关核心概念:
1、Job:是一个接口,只有一个execute(JobExecutionContext var1)方法,开发者通过实现该接口来定义需要执行的任务。Job运行时的信息保存在JobDataMap实例中。
2、JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不是直接接收一个Job实例,而是接收一个Job实现类,以便通过反射机制实例化Job,因此需要一个类来描述Job的实现类及其相关的静态信息(Job名称、描述等)。
3、Trigger:一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或以固定间隔周期性执行时,可以使用SimpleTrigger。当需要复杂的调度方案,可以使用CronTrigger。
4、Calendar:org.quartz.Calendar和java.util.Calendar不一样,它是一些日历特定时间点的集合(每周、每年、五一、十一)。java.util.Calendar代表一个日历时间点(20171001)。
5、Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,二者在Scheduler中拥有各自的组和名称。组及名称是Scheduler查找定位容器中某一对象的依据。因此,组及名称必须唯一(但是Trigger和JobDetail的组和名称可以相同,因为它们是不同的类型)。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。
Job有一个StatefulJob子接口,代表有状态的任务。它是一个没有方法的标签接口,目的是让Quartz知道任务的类型,以便执行不同的方案。
无状态任务在执行时拥有自己的JobDataMap复制,对JobDataMap的更改不会影响下次的执行。而有状态任务共享同一个JobDataMap实例,每次执行任务对JobDataMap的更改会保存下来,会对后面的执行产生影响。
因此,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行。如果前次的StatefulJob还没有执行完毕,则下次的任务将阻塞等待,直到前次任务执行完毕。避免使用有状态的Job。
使用数据库持久化任务调度信息,则无状态的JobDataMap仅在Scheduler注册任务时保存一次,而有状态的任务的JobDataMap在每次执行任务后都会进行保存。
一、Spring中使用Quartz
1.1、JobDetailFactoryBean
使用该Bean声明JobDetail时,Bean的名字即是任务的名字,如果没有指定所属组,就使用默认组。
<bean name="jobdetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!--实现Job接口的任务类-->
<property name="jobClass" value="com.smart.service.SampleJob"/>
<!--设置一个键,将Spring的ApplicationContext的引用保存到JobDataMap中,以便在Job的代码中访问-->
<property name="applicationContextJobDataKey" value="applicationContext"/>
<!--为任务所对应的JobDataMap提供值-->
<property name="jobDataAsMap">
<map>
<entry key="size" value="10"/>
</map>
</property>
</bean>
任务类:
public class SampleJob implements Job {
public void execute(JobExecutionContext jtx) throws JobExecutionException {
Map dataMap=jtx.getJobDetail().getJobDataMap();
//获取保存的值
String size=(String)dataMap.get("size");
//获取Spring的上下文信息,可以访问容器中的其他任何Bean
ApplicationContext ctx=(ApplicationContext)dataMap.get("applicationContext");
//对JobDataMap中保存的值进行修改,要根据任务类型进行区分
//如果实现Job接口,这种更改对于下一次执行是不可见的
//如果实现StatefulJob接口,对于下一次执行是可见的
dataMap.put("size",size+"0");
}
}
1.2、MethodInvokingJobDetailFactoryBean
可以将一个Bean的某个方法封装成满足Quartz要求的Job,但是产生的JobDetail不能被序列化,所以不能被持久化到数据库中。
<bean id="sampleDetailJob" class="com.smart.service.SampleDetailJob"/>
<bean id="jobDetail_1" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!--引用一个Bean-->
<property name="targetObject" ref="sampleDetailJob"/>
<!--指定目标Bean的方法,可以是static方法和非static方法,但是不能拥有参数-->
<property name="targetMethod" value="execute"/>
<!--指定任务是否有状态 false:不并发,说明有状态 -->
<property name="concurrent" value="false"/>
</bean>
2、创建Trigger
2.1、SimpleTriggerFactoryBean
在默认情况下,通过SimpleTriggerFactoryBean配置的Trigger名称即为Bean的名称,属于默认值。
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<!--对应的JobDetail-->
<property name="jobDetail" ref="jobdetail"/>
<property name="startDelay" value="1000"/>
<property name="repeatCount" value="100"/>
<property name="repeatInterval" value="2000"/>
<property name="jobDataAsMap">
<map>
<entry key="count" value="10"/>
</map>
</property>
</bean>
在Job中获取Trigger中的dataMap的值:
Map triggerMap=jtx.getTrigger().getJobDataMap();
String count=(String)triggerMap.get("count");
//对dataMap的更改不会持久化,也不影响下次的执行
triggerMap.put("count","30");
2.2、CronTriggerFactorBean
新增的属性和配置与SimpleTriggerFactoryBean相似,
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobdetail"/>
<property name="cronExpression" value="0 0 5 * * * ?"/>
</bean>
3、创建Scheduler
3.1、SchedulerFactoryBean
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<!--初始化完成后,延迟10秒启动,默认是0,立即启动,一般情况下,延迟一小段时间启动,以便Spring可以初始化剩余的Bean-->
<property name="startupDelay" value="10"/>
<!--配置多个trigger-->
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
<ref bean="simpleTrigger"/>
</list>
</property>
<property name="schedulerContextAsMap">
<map>
<entry key="timeout" value="30"/>
</map>
</property>
<!--Quartz本身有一个默认的配置文件,也可以自己设置自定义的Quartz的配置文件覆盖默认的-->
<property name="configLocation" value="classpath:quartz.properties"/>
<!--也可以直接通过属性配置,而不是配置文件-->
<property name="quartzProperties">
<props>
<!--数据保存到数据库而不是内存中-->
<prop key="org.quartz.jobStore.class">
org.quartz.impl.jdbcjobstore.JobStoreTX
</prop>
</props>
</property>
</bean>
二、普通的创建定时任务
2.1、创建 JobDetail
public JobDetail createJob(Task t) {
//任务执行的Job
JobBuilder jobBuiler = JobBuilder.newJob(WebCollectJob.class);
JobDataMap dataMap = new JobDataMap();
dataMap.put("service", service);
dataMap.put("Task", t);
jobBuiler.usingJobData(dataMap);
jobBuiler.withIdentity(DEFAULT_JOB_KEY + t.getId(), DEFAULT_JOB_GROUP);
JobDetail jobDetail = jobBuiler.build();
return jobDetail;
}
2.2、创建 Trigger
public Trigger createTrigger(Task t) throws ParseException {
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
String name = DEFAULT_TRIGFER_KEY + t.getId();
triggerBuilder.withIdentity(name, DEFAULT_JOB_GROUP);
triggerBuilder.withSchedule(getSimpleSchedule(t.getTaskInterval() * 60 * 60 * 1000));
triggerBuilder.startNow();
Trigger trigger = triggerBuilder.build();
return trigger;
}
2.3、创建 Scheduler
//配置
<bean id="abstractSchedule" abstract="true"
class="com.jolly.spider.core.job.AbstractScheduleService">
<property name="schedulerFactory" ref="schedulerFactory" />
</bean>
<bean id="schedulerFactory" class="org.quartz.impl.StdSchedulerFactory">
<constructor-arg>
<!--覆盖默认的配置-->
<value>quartz.properties</value>
</constructor-arg>
</bean>
//获取
private Scheduler scheduler;
private SchedulerFactory schedulerFactory;
public Scheduler getScheduler() {
return scheduler;
}
public void setScheduler(Scheduler scheduler) {
this.scheduler = scheduler;
}
public SchedulerFactory getSchedulerFactory() {
return schedulerFactory;
}
public void setSchedulerFactory(SchedulerFactory schedulerFactory) {
this.schedulerFactory = schedulerFactory;
try {
setScheduler(this.schedulerFactory.getScheduler());
} catch (SchedulerException e) {
logger.error("获取Scheduler失败.", e);
}
}
2.4、执行 任务
public boolean createScheduler(Task task) {
try {
JobDetail jobDetail = createJob(task);
Trigger trigger = createTrigger(task);
//代码片段。getScheduler()返回 Scheduler 对象
getScheduler().scheduleJob(jobDetail, trigger);
getScheduler().start();
return true;
} catch (Exception e) {
logger.error("任务[{}]启动失败", task.getId(), e);
}
return false;
}
Quartz Properties 文件
#主要分为四个部分 线程池属性,调度器属性,作业存储设置,插件配置
#===============================================================
# 1、 Configure ThreadPool 线程池属性
#===============================================================
#处理Job的线程个数,至少为1,但最多的话最好不要超过100,在多数机器上设置该值超过100的话就会显得相当不实用了,特别是在你的 Job 执行时间较长的情况下
org.quartz.threadPool.threadCount = 5
#线程的优先级,优先级别高的线程比级别低的线程优先得到执行。最小为1,最大为10,默认为5
org.quartz.threadPool.threadPriority = 5
#一个实现了 org.quartz.spi.ThreadPool 接口的类,Quartz 自带的线程池实现类是 org.quartz.smpl.SimpleThreadPool (threadPool类名(SimpleThreadPool就好))
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#===============================================================
# 2、 Configure JobStore 作业存储设置
#===============================================================
#要使 Job 存储在内存中需通过设置 org.quartz.jobStrore.class 属性为 org.quartz.simpl.RAMJobStore ,
#将schedule相关信息保存在RAM中,轻量级,速度快,遗憾的是应用重启时相关信息都将丢失。
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
# misfireThreshold : 最大能忍受的触发超时时间,如果超过则认为“失误”
org.quartz.jobStore.misfireThreshold = 60000
#===============================================================
# 3、 Configure Main Scheduler Properties 调度器属性
#===============================================================
#调度器的实例名
org.quartz.scheduler.instanceName = QuartzScheduler
#调度器的实例ID,大多数情况设置为auto即可
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export=false
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false
#===============================================================
# 4、 Configure Plugins 插件配置
#===============================================================
org.quartz.plugin.jobInitializer.class =
org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.validating=false