Quartz 使用(一)入门

Quartz 官方quick start

W3C quartz教程

Quartz Scheduler GitHub

quartz是一个任务调度框架,优于java原生Timer框架。用来定时处理任务

下载  

quartz下载

<dependency>
	<groupId>org.quartz-scheduler</groupId>
	<artifactId>quartz</artifactId>
	<version>2.3.0</version>
</dependency>
		
<dependency>
	<groupId>org.quartz-scheduler</groupId>
	<artifactId>quartz-jobs</artifactId>
	<version>2.3.0</version>
</dependency>

注意使用想要的版本代替上述版本号

例子

定义一个job

package com.learn.frame.quartz;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;


public class HelloJob implements Job{
	private String name; //SchedulerFactory自动注入
	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobDetail detail = context.getJobDetail();
		//String name = detail.getJobDataMap().getString("name");
		//打印当前时间
        SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(name + " : Current time is :"+simpleDateFormat.format(new Date()));
        System.out.println(Thread.currentThread().getName()); //观察发现默认开了10个线程去跑
       
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	

}

测试

package com.learn.frame.quartz;

import java.util.Date;

import org.junit.Test;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.DateBuilder.newDate;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;


public class QuartzTest {
	/**
	 * 将调度Trigger和要调度的任务JobDetail分离
	 * Job被创建后,可以保存在Scheduler中,与Trigger是独立的,同一个Job可以有多个Trigger;这种松耦合的另一个好处是,当与Scheduler中的Job关联的trigger都过期时,可以配置Job稍后被重新调度,而不用重新定义Job;还有,可以修改或者替换Trigger,而不用重新定义与之关联的Job。
	 * @throws SchedulerException
	 * @throws InterruptedException
	 */
	@Test
	public void test() throws SchedulerException, InterruptedException{
		//创建scheduler
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //定义一个Trigger
        Trigger trigger = newTrigger().withIdentity("trigger1", "group1") //定义name/group
            .startNow()//一旦加入scheduler,立即生效
            .withSchedule(simpleSchedule() //使用SimpleTrigger
                .withIntervalInSeconds(1) //每隔一秒执行一次
                .repeatForever()) //一直执行,奔腾到老不停歇
            .build();
  
      //定义一个JobDetail
        JobDetail job = newJob(HelloJob.class) //定义Job类为HelloQuartz类,这是真正的执行逻辑所在
            .withIdentity("job1", "group1") //定义name/group
            .usingJobData("name", "quartz") //定义属性
            .build();
      //加入这个调度
        scheduler.scheduleJob(job, trigger);
        	
        //启动之
        scheduler.start();

        //运行一段时间后关闭
        Thread.sleep(10000);
        scheduler.shutdown(true);
		
	}
}

结果

quartz : Current time is :2018-07-05 16:33:33
DefaultQuartzScheduler_Worker-1
quartz : Current time is :2018-07-05 16:33:34
DefaultQuartzScheduler_Worker-2
quartz : Current time is :2018-07-05 16:33:35
DefaultQuartzScheduler_Worker-3
quartz : Current time is :2018-07-05 16:33:36
DefaultQuartzScheduler_Worker-4
quartz : Current time is :2018-07-05 16:33:37
DefaultQuartzScheduler_Worker-5
quartz : Current time is :2018-07-05 16:33:38
DefaultQuartzScheduler_Worker-6

 

Quartz API

Quartz API的关键接口:

Job : 实现execute方法定义处理任务Job : 实现execute方法定义处理任务

JobDetail : 定义作业Job的实例,一个job 可以有多个JobDetail

Trigger : 触发器,定义指定Job的执行计划

Schedular : 调度器

Job接口

package org.quartz;

  public interface Job {

    public void execute(JobExecutionContext context)
      throws JobExecutionException;
  }

可以发现上述代码执行时,Schedular开了多个线程去执行job。而且在Job实现类中可以获得JobDetail中定义的JobDataMap数据。

原因:当Job的一个trigger被触发时,execute()方法由调度程序的一个工作线程调用。并传递给execute()方法一个Job执行的上下文对象 JobExecutionContext。

Job实例

很多用户对于Job实例到底由什么构成感到很迷惑。我们在这里解释一下,并在接下来的小节介绍job状态和并发。
你可以只创建一个job类,然后创建多个与该job关联的JobDetail实例,每一个实例都有自己的属性集和JobDataMap,最后,将所有的实例都加到scheduler中。
比如,你创建了一个实现Job接口的类“SalesReportJob”。该job需要一个参数(通过JobdataMap传入),表示负责该销售报告的销售员的名字。因此,你可以创建该job的多个实例(JobDetail),比如“SalesReportForJoe”、“SalesReportForMike”,将“joe”和“mike”作为JobDataMap的数据传给对应的job实例。
当一个trigger被触发时,与之关联的JobDetail实例会被加载,JobDetail引用的job类通过配置在Scheduler上的JobFactory进行初始化。默认的JobFactory实现,仅仅是调用job类的newInstance()方法,然后尝试调用JobDataMap中的key的setter方法。你也可以创建自己的JobFactory实现,比如让你的IOC或DI容器可以创建/初始化job实例。

在Quartz的描述语言中,我们将保存后的JobDetail称为“job定义”或者“JobDetail实例”,将一个正在执行的job称为“job实例”或者“job定义的实例”。当我们使用“job”时,一般指代的是job定义,或者JobDetail;当我们提到实现Job接口的类时,通常使用“job类”。

Job状态与并发

Quartz 定时任务默认都是并发执行的(Schedular中维护着多个线程),不会等待上一次任务执行完毕,只要间隔时间到就会执行,如果定时任执行太长,会长时间占用资源(无法释放线程资源),导致其它任务堵塞。

如何让任务阻塞式执行? -Job实现类添加@PersistJobDataAfterExecution注解

详解:关于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中存储的数据很可能是不确定的。

@DisallowConcurrentExecution
public class HelloJob implements Job{
	private String name; //SchedulerFactory自动注入
	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobDetail detail = context.getJobDetail();
		//String name = detail.getJobDataMap().getString("name");
		//打印当前时间
        SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(name + " : Current time is :"+simpleDateFormat.format(new Date()));
        System.out.println(Thread.currentThread().getName()); //观察发现默认开了10个线程去跑
        try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
       
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	

}


让当前线程睡眠2秒后,job并不是按照Trigger中定义,每一秒触发一次。线程会阻塞直到上个任务执行完成

JobDetail

为什么既有Job,又有Trigger呢?

在开发Quartz的时候,我们认为将调度和要调度的任务分离是合理的。在我们看来,这可以带来很多好处。
例如,Job被创建后,可以保存在Scheduler中,与Trigger是独立的,同一个Job可以有多个Trigger;这种松耦合的另一个好处是,当与Scheduler中的Job关联的trigger都过期时,可以配置Job稍后被重新调度,而不用重新定义Job;还有,可以修改或者替换Trigger,而不用重新定义与之关联的Job。

key

将Job和Trigger注册到Scheduler时,可以为它们设置key,配置其身份属性。Job和Trigger的key(JobKey和TriggerKey)可以用于将Job和Trigger放到不同的分组(group)里,然后基于分组进行操作。同一个分组下的Job或Trigger的名称必须唯一,即一个Job或Trigger的key由名称(name)和分组(group)组成。

JobDataMap

每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。

那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?答案就是:JobDataMap,将数据保存在JobDetail或Trigger中

*如果你在job类中,为JobDataMap中存储的数据的key增加set方法(如在上面示例中,增加setName()方法),那么Quartz的默认JobFactory实现在job被实例化的时候会自动调用这些set方法,这样你就不需要在execute()方法中显式地从map中取数据了。

在Job执行时,JobExecutionContext中的JobDataMap为我们提供了很多的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。

Job Stores

JobStore负责跟踪您提供给调度程序的所有“工作数据”:jobs,triggers,日历等

1)RAMJobStore

将所有数据保存在RAM中

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

2)JDBC JobStore

1 创建表

JDBCJobStore,必须首先创建一组数据库表以供Quartz使用

以2.3.x版本为例 quartz-quartz-2.3.0\quartz-core\src\main\resources\org\quartz\impl\jdbcjobstore 目录下包含不同数据库的建表sql,导入数据库生成下右图表

      

2.配置事务

创建表后,在配置和启动JDBCJobStore之前,您还有一个重要的决定。您需要确定应用程序需要哪种类型的事务。如果您不需要将调度命令(例如添加和删除triggers)绑定到其他事务,那么可以通过使用JobStoreTX作为JobStore 来管理事务(这是最常见的选择)。

如果您需要Quartz与其他事务(即J2EE应用程序服务器)一起工作,那么您应该使用JobStoreCMT - 在这种情况下,Quartz将让应用程序服务器容器管理事务。

3.配置数据源

最后一个难题是设置一个DataSource,JDBCJobStore可以从中获取与数据库的连接。DataSources在Quartz属性中使用几种不同的方法之一进行定义。一种方法是让Quartz创建和管理DataSource本身 - 通过提供数据库的所有连接信息。另一种方法是让Quartz使用由Quartz正在运行的应用程序服务器管理的DataSource,通过提供JDBCJobStore DataSource的JNDI名称。

quartz.properties (默认文件名)

#============================================================================
# Configure JobStore
#============================================================================

#default config
#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
#持久化配置
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
#我们仅为数据库制作了特定于数据库的代理
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题。
org.quartz.jobStore.useProperties:true
#数据库表前缀
org.quartz.jobStore.tablePrefix:qrtz_
org.quartz.jobStore.dataSource:qzDS
#============================================================================
# Configure Datasources
#============================================================================
#JDBC驱动
org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz
org.quartz.dataSource.qzDS.user:root
org.quartz.dataSource.qzDS.password:123456
org.quartz.dataSource.qzDS.maxConnections:10

小结

在Quartz可以完成其工作之前需要配置的主要组件有:

  • 线程池 ThreadPool
  • JobStore
  • DataSources(如有必要)
  • 计划程序本身 Schedular实例

猜你喜欢

转载自blog.csdn.net/zl_momomo/article/details/80928656