百味皆苦 java后端开发攻城狮

Quartz

2019-12-15
百味皆苦

简介

Job

  • Job:实现业务逻辑的任务接口。job接口很容易实现,只有一个execute方法,类似TimerTask的run方法,在里面编写业务逻辑。
  • Job实例在quartz中的生命周期:每次调度器执行job时,它在调用execute方法前会创建一个新的job实例。当调用完成后,关联的job对象实例会被释放,释放的实例会被垃圾回收机制回收。

JobDetail

  • JobDetail为Job实例提供了许多设置属性,以及JobDataMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。
    • name:名称
    • group:所在组
    • jobClass:任务类
    • jobDataMap:

JobExecutionContext

  • 当Scheduler调用一个job,就会将JobExecutionContext传递给job的execute方法
  • job能通过JobExecutionContext对象访问到quartz运行时候的环境以及job本身的明细数据

JobDataMap

  • 属于JobDetail的一部分,可以在构建JobDetail时传递参数

  • 在进行job任务调度时JobDataMap存储在execute方法的入参JobExecutionContext中,非常方便获取。
  • JobDataMap可以用来装载任何可序列化的对象,当job实例对象被执行时这些参数对象会传递给它
  • JobDataMap实现了JDK中的Map接口,并且添加了一些非常方便的方法来存取基本数据类型。
public class MyJob implements Job {

    @Setter@Getter
    private String msg;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        LocalTime localTime = LocalTime.now();
        System.out.println(localTime.toString()+",msg="+msg);
    }
}
public class QuartzDemo {
    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        
        scheduler.start();

        JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                .withIdentity("jobDetail1","group1")
                .usingJobData("name","value")
                .build();
        
        Trigger trigger = TriggerBuilder.newTrigger()
                .startNow()
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule()
            		.withIntervallnSeconds(10)
            			.repeatForever()
                )
                .build();
        
        scheduler.scheduleJob(jobDetail,trigger);
        try {
            Thread.sleep(600000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        scheduler.shutdown();
    }
}
  • 可以使用@PersistJobDataAfterExecution注解更新JobDataMap
@PersistJobDataAfterExecution
public class MyJob implements Job {

    @Setter@Getter
    private int count;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        LocalTime localTime = LocalTime.now();
        count++;
        context.getJobDetail().getJobDataMap().put("count"+count);
        System.out.println(localTime.toString()+",count="+count);
    }
}
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                .withIdentity("jobDetail1","group1")
                .usingJobData("count",new Random().nextInt(10))
                .build();

Trigger

  • quartz中的触发器用来告诉调度程序作业什么时候触发。即Trigger对象是用来触发执行job的

  • 三个属性:

    • JobKey:表示job实例的标识,触发器被触发时,该指定的job实例会执行。
    • StartTime:表示触发器的时间表首次被触发的时间。类型是java.util.Date
    • EndTime:指定触发器的不再被触发的时间,类型是java.util.Date
    • 优先级:当同一时间有多个Trigger同时触发时需要判断优先级,优先级别越高越先执行
  • SimpleTrigger:在一个指定时间段内执行一次作业任务,或是在指定的时间间隔内多次执行作业任务

    • 重复次数可以为0,正整数或SimpleTrigger.REPEAT_INDEFINITELY常量值。
    • 重复执行间隔必须为0或长整数
    • 一旦被指定了endTime参数,那么它会重复次数参数的效果
  • CronTrigger:基于日历的作业调度器,而不是像SimpleTrigger那样精确指定时间间隔,比SimpleTrigger更常用。

    • Cron表达式:用于配置CronTrigger实例,是由七个子表达式组成的字符串,描述了时间表的详细信息。格式为:【秒】【分】【小时】【日】【月】【周】【年】

    • 字段 是否必填 允许值 允许的字符
      0~59 ,- * /
      0~59 ,- * /
      小时 0~23 ,- * /
      0~31 ,- * ? / L W C
      0~12或JAN-DEC ,- * /
      1~7或SUN-SAT ,- * ? / L # C
      empty,1970~2099 ,- * /
    • 表达式 含义
      0 15 10 ? * * 每天10点15分触发
      0 0/15 14 * * ? 每天下午的2点到2点59分(整点开始,每隔5分触发)
      0 15 10 ? * MON-FRI 从周一到周五每天上午的10点15分触发
      0 15 10 ? * 6#3 每月的第三周的星期五开始触发
      0 15 10 ? * 6L 2016~2017 从2016~2017年每月最后一周的星期五的10点15分触发
    • 可借助在线生成器进行规则生成

Scheduler

  • 调度器,所有的Scheduler实例应该由SchedulerFactory来创建
  • 主要函数:
    • Date scheduleJob(JobDetail jobDetail,Trigger trigger);绑定任务和触发器
    • void start();开启调度器
    • void standby();暂停任务,挂起,再次start可继续执行任务
    • void shutdown(true/false);终止任务,参数为true时等待任务全部执行完毕后关闭,参数为false直接关闭。
  • StdSchedulerFactory:
    • 使用一组参数(java.util.Properties)来创建和初始化Quartz调度器。
    • 配置参数一般存储在quartz.properties中
    • 调用getScheduler方法就能创建和初始化调度器对象。
SchedulerFactory sfact = new StdSchedulerFactory();
Scheduler scheduler = sfact.getScheduler();

DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
Scheduler scheduler = factory.getScheduler();

配置文件

  • quartz.properties文件组成部分:
    • 调度器属性:
      • org.quartz.scheduler.instanceName属性用来区分特定的调度器实例,可以按照功能用途来给调度器起名
      • org.quartz.scheduler.instanceId属性和前者一样,也允许任何字符串,但这个值必须是在所有调度器实例中是唯一的,尤其是在一个集群中,作为集群的唯一key。假如想让quartz自动生成这个值得话,可以设置为AUTO
    • 作业存储设置:描述了在调度器实例的生命周期中,Job和Trigger信息是如何被存储的。
    • 线程池属性:
      • threadCount
      • threadPriority
      • org.quartz.threadPool.class
    • 插件配置:满足特定需求用到的quartz插件配置

定时任务并发

  • 比如一个任务需要执行7s,但是任务每5s执行一次
  • 使用@DisallowConcurrentExecution防止并发
  • 把注解加在job类上

错过机制

  • Misfire机制:

整合WEB

<dependencies>
		<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
			<version>2.2.3</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
  • quartz.properties
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
# ===========================================================================
# Configure Main Scheduler Properties 调度器属性
# ===========================================================================
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.instanceid:AUTO
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
# ===========================================================================  
# Configure ThreadPool 线程池属性  
# ===========================================================================
#线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
#指定线程数,至少为1(无默认值)(一般设置为1-100直接的整数合适)
org.quartz.threadPool.threadCount: 10
#设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority: 5
#设置SimpleThreadPool的一些属性
#设置是否为守护线程
#org.quartz.threadpool.makethreadsdaemons = false
#org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
#org.quartz.threadpool.threadsinheritgroupofinitializingthread=false
#线程前缀默认值是:[Scheduler Name]_Worker
#org.quartz.threadpool.threadnameprefix=swhJobThead;
# 配置全局监听(TriggerListener,JobListener) 则应用程序可以接收和执行 预定的事件通知
# ===========================================================================
# Configuring a Global TriggerListener 配置全局的Trigger监听器
# MyTriggerListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)
# ===========================================================================
#org.quartz.triggerListener.NAME.class = com.swh.MyTriggerListenerClass
#org.quartz.triggerListener.NAME.propName = propValue
#org.quartz.triggerListener.NAME.prop2Name = prop2Value
# ===========================================================================
# Configuring a Global JobListener 配置全局的Job监听器
# MyJobListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)
# ===========================================================================
#org.quartz.jobListener.NAME.class = com.swh.MyJobListenerClass
#org.quartz.jobListener.NAME.propName = propValue
#org.quartz.jobListener.NAME.prop2Name = prop2Value
# ===========================================================================  
# Configure JobStore 存储调度信息(工作,触发器和日历等)
# ===========================================================================
# 信息保存时间 默认值60秒
org.quartz.jobStore.misfireThreshold: 60000
#保存job和Trigger的状态信息到内存中的类
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
# ===========================================================================  
# Configure SchedulerPlugins 插件属性 配置
# ===========================================================================
# 自定义插件  
#org.quartz.plugin.NAME.class = com.swh.MyPluginClass
#org.quartz.plugin.NAME.propName = propValue
#org.quartz.plugin.NAME.prop2Name = prop2Value
#配置trigger执行历史日志(可以看到类的文档和参数列表)
org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin  
org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger {1}.{0} fired job {6}.{5} at: {4, date, HH:mm:ss MM/dd/yyyy}  
org.quartz.plugin.triggHistory.triggerCompleteMessage = Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy} with resulting trigger instruction code: {9}  
#配置job调度插件  quartz_jobs(jobs and triggers内容)的XML文档  
#加载 Job 和 Trigger 信息的类   (1.8之前用:org.quartz.plugins.xml.JobInitializationPlugin)
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
#指定存放调度器(Job 和 Trigger)信息的xml文件,默认是classpath下quartz_jobs.xml
org.quartz.plugin.jobInitializer.fileNames = my_quartz_job2.xml  
#org.quartz.plugin.jobInitializer.overWriteExistingJobs = false  
org.quartz.plugin.jobInitializer.failOnFileNotFound = true  
#自动扫描任务单并发现改动的时间间隔,单位为秒
org.quartz.plugin.jobInitializer.scanInterval = 10
#覆盖任务调度器中同名的jobDetail,避免只修改了CronExpression所造成的不能重新生效情况
org.quartz.plugin.jobInitializer.wrapInUserTransaction = false
# ===========================================================================  
# Sample configuration of ShutdownHookPlugin  ShutdownHookPlugin插件的配置样例
# ===========================================================================
#org.quartz.plugin.shutdownhook.class = \org.quartz.plugins.management.ShutdownHookPlugin
#org.quartz.plugin.shutdownhook.cleanShutdown = true
#
# Configure RMI Settings 远程服务调用配置
#
#如果你想quartz-scheduler出口本身通过RMI作为服务器,然后设置“出口”标志true(默认值为false)。
#org.quartz.scheduler.rmi.export = false
#主机上rmi注册表(默认值localhost)
#org.quartz.scheduler.rmi.registryhost = localhost
#注册监听端口号(默认值1099)
#org.quartz.scheduler.rmi.registryport = 1099
#创建rmi注册,false/never:如果你已经有一个在运行或不想进行创建注册
# true/as_needed:第一次尝试使用现有的注册,然后再回来进行创建
# always:先进行创建一个注册,然后再使用回来使用注册
#org.quartz.scheduler.rmi.createregistry = never
#Quartz Scheduler服务端端口,默认是随机分配RMI注册表
#org.quartz.scheduler.rmi.serverport = 1098
#true:链接远程服务调度(客户端),这个也要指定registryhost和registryport,默认为false
# 如果export和proxy同时指定为true,则export的设置将被忽略
#org.quartz.scheduler.rmi.proxy = false
  • Job
public class HelloJob implements Job {

	public void execute(JobExecutionContext context)
			throws JobExecutionException {
		try {
			Thread.sleep(5000L);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// 打印当前的执行时间,格式为2017-01-01 00:00:00
		Date date = new Date();
		SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println("Current Exec Time Is : " + sf.format(date));
		System.out.println("Hello World");
	}

}
  • Scheduler
public class HelloScheduler {
	public static void main(String[] args) throws SchedulerException, InterruptedException {
		// 打印当前的时间,格式为2017-01-01 00:00:00
		Date date = new Date();
		SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println("Current Time Is : " + sf.format(date));
		// 创建一个JobDetail实例,将该实例与HelloJob Class绑定
		JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
				.withIdentity("myJob").build();
		CronTrigger trigger = (CronTrigger) TriggerBuilder
				.newTrigger()
				.withIdentity("myTrigger", "group1")
				.withSchedule(
						CronScheduleBuilder.cronSchedule("* * * * * ?"))
				.build();
		// 创建Scheduler实例
		SchedulerFactory sfact = new StdSchedulerFactory();
		Scheduler scheduler = sfact.getScheduler();
		scheduler.start();
		System.out.println("scheduled time is :"
				+ sf.format(scheduler.scheduleJob(jobDetail, trigger)));
		//scheduler执行两秒后挂起
		Thread.sleep(2000L);		
		//shutdown(true)表示等待所有正在执行的job执行完毕之后,再关闭scheduler
		//shutdown(false)即shutdown()表示直接关闭scheduler
		scheduler.shutdown(false);
		System.out.println("scheduler is shut down? " + scheduler.isShutdown());
	}
}

整合spring

  • 整合spring有两种方式:MethodInvokingJobDetailFactoryBean,JobDetailFactoryBean
<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
			<version>2.2.3</version>
		</dependency>
  • web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	version="3.0">
	<display-name>Archetype Created Web Application</display-name>
	<!-- spring MVC的核心就是DispatcherServlet,使用springMVC的第一步就是将下面的servlet放入web.xml 
		servlet-name属性非常重要,默认情况下,DispatchServlet会加载这个名字-servlet.xml的文件,如下,就会加载 dispather-servlet.xml,也是在WEN-INF目录下。 -->
	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<!-- 设置dispatchservlet的匹配模式,通过把dispatchservlet映射到/,默认servlet会处理所有的请求,包括静态资源 -->
	<servlet-mapping>
		<servlet-name>dispatcher</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>

</web-app>  
  • dispatcher-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans  
            http://www.springframework.org/schema/beans/spring-beans.xsd  
            http://www.springframework.org/schema/mvc  
            http://www.springframework.org/schema/mvc/spring-mvc.xsd  
            http://www.springframework.org/schema/context  
            http://www.springframework.org/schema/context/spring-context.xsd"
	default-lazy-init="true">

	<!-- 通过mvc:resources设置静态资源,这样servlet就会处理这些静态资源,而不通过控制器 -->
	<!-- 设置不过滤内容,比如:css,jquery,img 等资源文件 -->
	<mvc:resources location="/*.html" mapping="/**.html" />
	<mvc:resources location="/css/*" mapping="/css/**" />
	<mvc:resources location="/js/*" mapping="/js/**" />
	<mvc:resources location="/images/*" mapping="/images/**" />
	<!-- 设定消息转换的编码为utf-8防止controller返回中文乱码 -->
	<bean
		class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
		<property name="messageConverters">
			<list>
				<bean
					class="org.springframework.http.converter.StringHttpMessageConverter">
					<property name="supportedMediaTypes">
						<list>
							<value>text/html;charset=UTF-8</value>
						</list>
					</property>
				</bean>
			</list>
		</property>
	</bean>
	<!-- 添加注解驱动 -->
	<mvc:annotation-driven />
	<!-- 默认扫描的包路径 -->
	<context:component-scan base-package="com.imooc.springquartz" />

	<!-- mvc:view-controller可以在不需要Controller处理request的情况,转向到设置的View -->
	<!-- 像下面这样设置,如果请求为/,则不通过controller,而直接解析为/index.jsp -->
	<mvc:view-controller path="/" view-name="index" />
	<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
		<property name="viewClass"
			value="org.springframework.web.servlet.view.JstlView"></property>
		<!-- 配置jsp路径前缀 -->
		<property name="prefix" value="/"></property>
		<!-- 配置URl后缀 -->
		<property name="suffix" value=".jsp"></property>
	</bean>
    
	<!-- 第一种作业 -->
	<bean id="simpleJobDetail"
		class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
		<property name="targetObject" ref="myBean" />
		<property name="targetMethod" value="printMessage" />
	</bean>
	<!-- 第二种作业 -->
	<bean id="firstComplexJobDetail"
		class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
		<property name="jobClass"
			value="com.imooc.springquartz.quartz.FirstScheduledJob" />
		<property name="jobDataMap">
			<map>
				<entry key="anotherBean" value-ref="anotherBean" />
			</map>
		</property>
		<property name="Durability" value="true"/>				
	</bean>
    
	<!-- 简单触发器,距离当前时间1秒之后执行,之后每隔两秒钟执行一次 -->
	<bean id="mySimpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
	    <property name="jobDetail"  ref="simpleJobDetail"/>
	    <property name="startDelay"  value="1000"/>
	    <property name="repeatInterval"  value="2000"/>
	</bean>
	
	<!-- cron触发器,每隔5秒钟执行一次 -->
	<bean id="myCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
	    <property name="jobDetail"  ref="firstComplexJobDetail"/>
	    <property name="cronExpression"  value="0/5 * * ? * *"/>
	</bean>
	
    <!-- 绑定作业任务和触发器 -->
	<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
	    <property name="jobDetails">
	        <list>
	            <ref bean="simpleJobDetail"/>
	            <ref bean="firstComplexJobDetail"/>
	        </list>
	    </property>
	    <property name="triggers">
	        <list>
	            <ref bean="mySimpleTrigger"/>
	            <ref bean="myCronTrigger"/>
	        </list>
	    </property>
	</bean>
</beans>  
  • AnotherBean
@Component("anotherBean")
public class AnotherBean {
	public void printAnotherMessage() {
		System.out.println("AnotherMessage");
	}
}
  • FirstScheduledJob
public class FirstScheduledJob extends QuartzJobBean{
     private AnotherBean anotherBean;
     
     public void setAnotherBean(AnotherBean anotherBean){
    	 this.anotherBean = anotherBean;
     }

	@Override
	protected void executeInternal(JobExecutionContext arg0)
			throws JobExecutionException {
		Date date = new Date();
		SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println("FirstScheduledJob Executes!" + sf.format(date));
		this.anotherBean.printAnotherMessage();		
	}
}
  • MyBean
@Component("myBean")
public class MyBean {
	public void printMessage() {
		// 打印当前的执行时间,格式为2017-01-01 00:00:00
		Date date = new Date();
		SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println("MyBean Executes!" + sf.format(date));
	}
}

整合springboot

  • maven
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • 配置文件
spring.quartz.scheduler-name=mySpringbootQuartz
spring.quartz.job-store-type=jdbc
spring.quartz.overwrite-existing-jobs=true
spring.quartz.jdbc.initialize-schema=never
spring.quartz.jdbc.comment-prefix=#
spring.quartz.wait-for-jobs-to-complete-on-shutdown=true

spring.datasource.url=jdbc:mysql://localhost:3306/order?serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
logging.pattern.dateformat=HH:mm:ss

mybatis.mapper-locations=mybatis/*.xml
  • 作业
@Slf4j
public class MyJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("我正在执行!");
    }
}
  • quartz配置类
@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail myJobDetail(){
        JobDetail detail = JobBuilder.newJob(MyJob.class)
                .withIdentity("myjob","group1")
                .storeDurably()
                .build();
        return detail;
    }


    @Bean
    public Trigger myTrigger1(){

        Trigger trigger = TriggerBuilder.newTrigger()
                .startNow()
                .withIdentity("myjobTrigger1","group1")
                .forJob(myJobDetail())
                .withSchedule(CronScheduleBuilder.cronSchedule("0/15 * * * * ?")
                ).build();

        return trigger;
    }

}

TriggerListener详解

  • 监听与监听器(trigger)有关的事件
  • 触发器开始:triggerFired
  • 触发器结束:triggerComplete
  • 触发器否决:vetoJobExecution

API

  • trigger监听器,实际案例:节假日不执行任务
public class MyTriggerListener extends TriggerListenerSupport {
    @Override
    public String getName() {
        return "myTriggerListener";
    }

    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        System.out.println("vetoJobExecution正在执行!!");
        //如果时间是5,15,25,尾数是5的时间点,就不执行
        Calendar now = Calendar.getInstance();
        int second = now.get(Calendar.SECOND);
        if (second % 10 ==5){
            System.out.println("本次Trigger不触发!!");
            return true;
        }
        return false;
    }
}
  • 主程序类
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener());

spring

  • trigger
public class MyListener extends TriggerListenerSupport {
    @Override
    public String getName() {
        return "myTriggerListener";
    }

    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        super.triggerFired(trigger, context);
        LocalTime now = LocalTime.now();
        System.out.println(now.toString()+"Trigger is fired!!");
    }
}
  • xml
<bean id="myTriggerListener" class="com.example.job.listener.MyListener"/>

<bean id="schedule" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" destroy-method="destroy">
        <property name="dataSource" ref="datasource"/>
        <property name="triggers">
            <list>
                <ref bean="trigger1"/>
                <ref bean="trigger2"/>
            </list>
        </property>
        <property name="globalTriggerListeners">
            <list>
                <ref bean="myTriggerListener"/>
            </list>
        </property>
</bean>

JobListener详解

  • 监听job相关事件
  • 主要事件:job即将执行,job完成执行

API

  • 实际案例:记录作业的执行时间
  • 引入dbcp2和mysql的pom
  • 编写监听器
public class MyJobListener extends JobListenerSupport {
    @Override
    public String getName() {
        return "myJobListener";
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        super.jobToBeExecuted(context);
        LocalTime now = LocalTime.now();
        System.out.println(now.toString()+"jobToBeExecuted");
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/order?serverTimezone=Asia/Shanghai&useSSL=false");
        try {
            dataSource.setDriver(new Driver());
        } catch (SQLException e) {
            e.printStackTrace();
        }

        DataSourceConnectionFactory connectionFactory = new DataSourceConnectionFactory(dataSource);
        try {
            Connection connection = connectionFactory.createConnection();
            PreparedStatement preparedStatement = connection.prepareStatement("insert into quartz_job_log (job_name,type,time) values (?,?,?)");
            preparedStatement.setString(1,context.getJobDetail().getKey().toString());
            preparedStatement.setInt(2,1);
            preparedStatement.setTime(3,new Time(new Date().getTime()));
            preparedStatement.execute();
            preparedStatement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            dataSource.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        super.jobWasExecuted(context, jobException);
        LocalTime now = LocalTime.now();
        System.out.println(now.toString()+"jobWasExecuted");
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/order?serverTimezone=Asia/Shanghai&useSSL=false");
        try {
            dataSource.setDriver(new Driver());
        } catch (SQLException e) {
            e.printStackTrace();
        }

        DataSourceConnectionFactory connectionFactory = new DataSourceConnectionFactory(dataSource);
        try {
            Connection connection = connectionFactory.createConnection();
            PreparedStatement preparedStatement = connection.prepareStatement("insert into quartz_job_log (job_name,type,time) values (?,?,?)");
            preparedStatement.setString(1,context.getJobDetail().getKey().toString());
            preparedStatement.setInt(2,2);
            preparedStatement.setTime(3,new Time(new Date().getTime()));
            preparedStatement.execute();
            preparedStatement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            dataSource.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}
  • 注册监听器
scheduler.getListenerManager().addJobListener(new MyJobListener());

spring

  • 编写监听器
public class MyJobListener extends JobListenerSupport {
    @Override
    public String getName() {
        return "myJobListener";
    }

    private DataSource dataSource;

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        super.jobToBeExecuted(context);
        LocalTime now = LocalTime.now();
        System.out.println(now.toString()+"jobToBeExecuted");
        DataSourceConnectionFactory connectionFactory = new DataSourceConnectionFactory(dataSource);
        try {
            Connection connection = connectionFactory.createConnection();
            PreparedStatement preparedStatement = connection.prepareStatement("insert into quartz_job_log (job_name,type,time) values (?,?,?)");
            preparedStatement.setString(1,context.getJobDetail().getKey().toString());
            preparedStatement.setInt(2,1);
            preparedStatement.setTime(3,new Time(new Date().getTime()));
            preparedStatement.execute();
            preparedStatement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        super.jobWasExecuted(context, jobException);
        LocalTime now = LocalTime.now();
        System.out.println(now.toString()+"jobWasExecuted");
        DataSourceConnectionFactory connectionFactory = new DataSourceConnectionFactory(dataSource);
        try {
            Connection connection = connectionFactory.createConnection();
            PreparedStatement preparedStatement = connection.prepareStatement("insert into quartz_job_log (job_name,type,time) values (?,?,?)");
            preparedStatement.setString(1,context.getJobDetail().getKey().toString());
            preparedStatement.setInt(2,2);
            preparedStatement.setTime(3,new Time(new Date().getTime()));
            preparedStatement.execute();
            preparedStatement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}
  • xml
<bean id="datasource" destroy-method="close" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <property name="url" value="jdbc:mysql://localhost:3306/order?serverTimezone=Asia/Shanghai&amp;useSSL=false"/>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>

</bean>

<bean id="myJobListener" class="com.example.job.listener.MyJobListener">
        <property name="dataSource" ref="datasource"/>
</bean>

<bean id="schedule" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" destroy-method="destroy">
        <property name="dataSource" ref="datasource"/>
        <property name="triggers">
            <list>
                <ref bean="trigger1"/>
                <ref bean="trigger2"/>
            </list>
        </property>
        <property name="globalJobListeners">
            <list>
                <ref bean="myJobListener"/>
            </list>
        </property>
</bean>

SchedulerListener详解

API

  • 记录定时任务的启停
  • 编写监听器
public class MySchedulerListener extends SchedulerListenerSupport {


    @Override
    public void schedulerStarting() {
        super.schedulerStarting();
        LocalTime now = LocalTime.now();
        System.out.println(now.toString()+",schedulerStarting!!");
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/order?serverTimezone=Asia/Shanghai&useSSL=false");
        try {
            dataSource.setDriver(new Driver());
        } catch (SQLException e) {
            e.printStackTrace();
        }

        DataSourceConnectionFactory connectionFactory = new DataSourceConnectionFactory(dataSource);
        try {
            Connection connection = connectionFactory.createConnection();
            PreparedStatement preparedStatement = connection.prepareStatement("insert into quartz_scheduler_log (ip,type,time) values (?,?,?)");
            String ip = InetAddress.getLocalHost().getHostAddress();

            preparedStatement.setString(1,ip);
            preparedStatement.setInt(2,1);
            preparedStatement.setTime(3,new Time(new Date().getTime()));
            preparedStatement.execute();
            preparedStatement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        try {
            dataSource.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void schedulerShuttingdown() {
        super.schedulerShuttingdown();
        LocalTime now = LocalTime.now();
        System.out.println(now.toString()+",schedulerShuttingdown!!");
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/order?serverTimezone=Asia/Shanghai&useSSL=false");
        try {
            dataSource.setDriver(new Driver());
        } catch (SQLException e) {
            e.printStackTrace();
        }

        DataSourceConnectionFactory connectionFactory = new DataSourceConnectionFactory(dataSource);
        try {
            Connection connection = connectionFactory.createConnection();
            PreparedStatement preparedStatement = connection.prepareStatement("insert into quartz_scheduler_log (ip,type,time) values (?,?,?)");
            String ip = InetAddress.getLocalHost().getHostAddress();

            preparedStatement.setString(1,ip);
            preparedStatement.setInt(2,2);
            preparedStatement.setTime(3,new Time(new Date().getTime()));
            preparedStatement.execute();
            preparedStatement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        try {
            dataSource.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  • 注册监听器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());
Runtime.getRuntime().addShutdownHook(new Thread(){
    @Override
    public void run() {
        super.run();
        try {
            scheduler.shutdown();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
});
scheduler.start();

spring

  • 编写监听器
public class MySchedulerListener extends SchedulerListenerSupport {


    @Override
    public void schedulerStarting() {
        super.schedulerStarting();
        LocalTime now = LocalTime.now();
        System.out.println(now.toString()+",schedulerStarting!!");
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/order?serverTimezone=Asia/Shanghai&useSSL=false");
        try {
            dataSource.setDriver(new Driver());
        } catch (SQLException e) {
            e.printStackTrace();
        }

        DataSourceConnectionFactory connectionFactory = new DataSourceConnectionFactory(dataSource);
        try {
            Connection connection = connectionFactory.createConnection();
            PreparedStatement preparedStatement = connection.prepareStatement("insert into quartz_scheduler_log (ip,type,time) values (?,?,?)");
            String ip = InetAddress.getLocalHost().getHostAddress();

            preparedStatement.setString(1,ip);
            preparedStatement.setInt(2,1);
            preparedStatement.setTime(3,new Time(new Date().getTime()));
            preparedStatement.execute();
            preparedStatement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        try {
            dataSource.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void schedulerShutdown() {
        super.schedulerShuttingdown();
        LocalTime now = LocalTime.now();
        System.out.println(now.toString()+",schedulerShuttingdown!!");
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/order?serverTimezone=Asia/Shanghai&useSSL=false");
        try {
            dataSource.setDriver(new Driver());
        } catch (SQLException e) {
            e.printStackTrace();
        }

        DataSourceConnectionFactory connectionFactory = new DataSourceConnectionFactory(dataSource);
        try {
            Connection connection = connectionFactory.createConnection();
            PreparedStatement preparedStatement = connection.prepareStatement("insert into quartz_scheduler_log (ip,type,time) values (?,?,?)");
            String ip = InetAddress.getLocalHost().getHostAddress();

            preparedStatement.setString(1,ip);
            preparedStatement.setInt(2,2);
            preparedStatement.setTime(3,new Time(new Date().getTime()));
            preparedStatement.execute();
            preparedStatement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        try {
            dataSource.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  • xml
<bean id="mySchedulerListener" class="com.example.job.listener.MySchedulerListener"/>

<bean id="schedule" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" destroy-method="destroy">
        <property name="dataSource" ref="datasource"/>
        <property name="triggers">
            <list>
                <ref bean="trigger1"/>
                <ref bean="trigger2"/>
            </list>
        </property>
        <property name="schedulerListeners">
            <list>
                <ref bean="mySchedulerListener"/>
            </list>
        </property>
    </bean>

    <bean id="datasource" destroy-method="close" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <property name="url" value="jdbc:mysql://localhost:3306/order?serverTimezone=Asia/Shanghai&amp;useSSL=false"/>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    </bean>

quartz集群

  • 支持失效转义,不支持分布式
  • 案例:按小时统计订单数量

Comments

Content