java web定時任務框架Spring Quartz和Spring Task總結以及Corn表達式詳解及舉例

 這幾天都在準備面試,發現很多知識點和技術自己用過但沒有好好總結,只是在雲筆記裏零亂記錄了一下,有點茶壺煮餃子有貨吐不出的感覺,尷尬~,這次決定遇到一個就總結一個。本文主要總結了一下spring quartz(包括帶線程池和不帶線程池版本、cron表達式規則)和基於@Scheduled註解的spring定時任務。
 本文源碼已上傳GitHub:https://github.com/leon2016/egg.git

Spring Quartz

1.導入依賴

<!-- quartz定時任務插件 -->
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
			<version>${quartzVersion}</version>
		</dependency>
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz-jobs</artifactId>
			<version>${quartzVersion}</version>
		</dependency>
		
<!--quartz必須依賴 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>${springVersion}</version>
		</dependency>

我的pom.xml (僅做出現問題參考,因爲裏面有很多其他用處的依賴):

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.leon</groupId>
	<artifactId>egg</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>egg Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<!-- 版本信息常量 -->
	<properties>
		<encodeType>UTF-8</encodeType>
		<jdkVersion>1.7</jdkVersion>
		<mavenCompilerPluginVersion>3.1</mavenCompilerPluginVersion>
		<mavenWarPluginVersion>2.3</mavenWarPluginVersion>
		<jstlVersion>1.2</jstlVersion>
		<servletVersion>2.5</servletVersion>
		<springVersion>4.3.1.RELEASE</springVersion>
		<jacksonVersion>2.5.0</jacksonVersion>
		<hibernateVersion>4.3.5.Final</hibernateVersion>
		<mysqlVersion>5.1.24</mysqlVersion>
		<c3p0Version>0.9.1.2</c3p0Version>

		<fastjsonVersion>1.2.20</fastjsonVersion>
		<apacheHttpVersion>4.4.1</apacheHttpVersion>
		<quartzVersion>2.2.1</quartzVersion>
		<dom4jVersion>1.6.1</dom4jVersion>
		<xstreamVersion>1.4.7</xstreamVersion>
		<log4jVersion>1.2.17</log4jVersion>
	</properties>

	<dependencies>
		<!-- 單元測試 -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.1</version>
			<scope>test</scope>
		</dependency>
		<!-- *****************************************springMVC+spring+hibernate基本依賴*************************************************** -->
		<!-- jsp頁面使用的jstl支持 -->
		<dependency>
			<groupId>jstl</groupId>
			<artifactId>jstl</artifactId>
			<version>${jstlVersion}</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>${servletVersion}</version>
			<scope>provided</scope>
		</dependency>

		<!-- spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>${springVersion}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${springVersion}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${springVersion}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${springVersion}</version>
		</dependency>

		<!--quartz必須依賴 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>${springVersion}</version>
		</dependency>

		<!-- spring web + spring MVC -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${springVersion}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${springVersion}</version>
		</dependency>

		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>${jacksonVersion}</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>${jacksonVersion}</version>
		</dependency>

		<!-- hibernate配置 -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${hibernateVersion}</version>
		</dependency>

		<!-- DataBase數據庫連接 mysql包 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysqlVersion}</version>
		</dependency>

		<!-- 數據庫連接池 -->
		<dependency>
			<groupId>c3p0</groupId>
			<artifactId>c3p0</artifactId>
			<version>${c3p0Version}</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>${fastjsonVersion}</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>${apacheHttpVersion}</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpcore</artifactId>
			<version>${apacheHttpVersion}</version>
		</dependency>

		<!-- quartz定時任務插件 -->
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
			<version>${quartzVersion}</version>
		</dependency>
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz-jobs</artifactId>
			<version>${quartzVersion}</version>
		</dependency>

		<!-- xml -->
		<dependency>
			<groupId>org.apache.directory.studio</groupId>
			<artifactId>org.dom4j.dom4j</artifactId>
			<version>${dom4jVersion}</version>
		</dependency>

		<dependency>
			<groupId>com.thoughtworks.xstream</groupId>
			<artifactId>xstream</artifactId>
			<version>${xstreamVersion}</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/log4j/log4j -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>${log4jVersion}</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.3.2</version>
		</dependency>

		<!-- aop -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${springVersion}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>${springVersion}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${springVersion}</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>${mavenCompilerPluginVersion}</version>
				<configuration>
					<source>${jdkVersion}</source>
					<target>${jdkVersion}</target>
					<encoding>${encodeType}</encoding>
				</configuration>
			</plugin>
		</plugins>
		<finalName>egg</finalName>
	</build>
</project>

 

spring項目中使用quartz [kwɔrts](不帶線程池版本)

在Spring中使用Quartz有兩種方式實現:第一種是任務類繼承QuartzJobBean,第二種則是在配置文件裏定義任務類和要執行的方法,類和方法仍然是普通類。

 很顯然,第二種方式遠比第一種方式來的靈活,本文只介紹第二種。

1.編寫定時任務類(普通java類)

如下:是我的一個獲取微信token和ticket的定時任務類。

package com.leon.wx.task;

import org.apache.log4j.Logger;

import com.leon.wx.util.WeChatTask;

public class QuartzJob {
	private static Logger logger = Logger.getLogger(QuartzJob.class);

	/**
	 * @Description: 定時任務執行獲取 token
	 * @param
	 */
	public static void workForToken() {
		logger.info("開始執行定時任務workForToken...");
		try {
			WeChatTask.getToken_getTicket(); 
		} catch (Exception e) {
			e.printStackTrace();
		}
		logger.info("定時任務workForToken執行完畢。");
	}

}
2.xml中配置quartz定時任務

 新建在spring-quartz.xml(和spring配置文件放同一個位置):不同位置也可通過
在spring配置文件中引入quartz配置文件。
spring-quartz.xml如下: 配置 1,2,3(可選),4。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
	<!-- 定時器工作調度的bean -->
	<bean id="quartzJob" class="com.leon.wx.task.QuartzJob"></bean>

	<!--1.創建JobDteail: 定義調用對象和調用對象的方法 -->
	<bean id="jobtaskForToken"
		class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
		<!-- 調用的類 -->
		<property name="targetObject">
			<ref bean="quartzJob" />
		</property>
		<!-- 調用類中的方法 -->
		<property name="targetMethod">
			<value>workForToken</value>
		</property>

	</bean>

	<!--2.創建Trigger:定義觸發時間 -->
	<bean id="doTimeForToken"
		class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
		<property name="jobDetail">
			<ref bean="jobtaskForToken" />
		</property>
		<!-- cron 表達式 -->
		<property name="cronExpression">
			<value>0 0/60 * * * ?</value>
		</property>
	</bean>

	<!-- 3.配置項目啓動後任務就執行一次(可選) -->
	<bean id="initTrigger"
		class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
		<property name="jobDetail" ref="jobtaskForToken" />
		<property name="startDelay" value="500" />
		<property name="repeatInterval" value="0" />
		<property name="repeatCount" value="0" />
	</bean>

	<!-- 4.配置調度工廠:定時任務總管理類,如果將 lazy-init='false'那麼容器啓動就會執行調度程序 -->
	<bean id="startQuertz" lazy-init="false" autowire="no"
		class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="triggers">
			<list>
				<ref bean="initTrigger" /><!--可選-->
				<ref bean="doTimeForToken" />
			</list>
		</property>
	</bean>

</beans>

 

spring項目中使用quartz [kwɔrts](帶線程池版本)

 使用調度器Quartz來進行數據歸檔的時候,當我們開的定時任務很多的時候,就會出現一些定時任務不會被觸發的現象,這就是線程阻塞。那到底什麼叫線程阻塞呢?
 線程阻塞,顧名思義就是說線程被阻塞了,沒有按時執行,即定時任務沒有被觸發。那麼爲什麼會出現中定時任務沒被觸發的現象呢?
 通過對調取器的調度原理的分析,我們可以知道:當正在執行的調度任務個數超過了調度器中設置的最大值時,就會出線程阻塞,調度任務延遲執行的現象。
 --摘自 https://blog.csdn.net/ghgzczxcvxv/article/details/44457833

在上面不帶線程池版本上,在spring-quartz.xml添加下列配置即可實現帶線程池版本的quartz定時任務。

1.增加一個線程池
<!-- 線程執行器配置,用於任務註冊 -->
<bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
 <property name="corePoolSize" value="10" />
 <property name="maxPoolSize" value="100" />
 <property name="queueCapacity" value="500" />
</bean>
2.配置調度工廠的taskExecutor屬性
<property name="taskExecutor" ref="executor" />

完整spring-quartz.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
	<!-- 定時器工作調度的bean -->
	<bean id="quartzJob" class="com.leon.wx.task.QuartzJob"></bean>

	<!--1.創建JobDteail: 定義調用對象和調用對象的方法 -->
	<bean id="jobtaskForToken"
		class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
		<!-- 調用的類 -->
		<property name="targetObject">
			<ref bean="quartzJob" />
		</property>
		<!-- 調用類中的方法 -->
		<property name="targetMethod">
			<value>workForToken</value>
		</property>

	</bean>

	<!--2.創建Trigger:定義觸發時間 -->
	<bean id="doTimeForToken"
		class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
		<property name="jobDetail">
			<ref bean="jobtaskForToken" />
		</property>
		<!-- cron 表達式 -->
		<property name="cronExpression">
			<value>0 0/60 * * * ?</value>
		</property>
	</bean>

	<!-- 3.配置項目啓動後任務就執行一次(可選) -->
	<bean id="initTrigger"
		class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
		<property name="jobDetail" ref="jobtaskForToken" />
		<property name="startDelay" value="500" />
		<property name="repeatInterval" value="0" />
		<property name="repeatCount" value="0" />
	</bean>

<!-- 線程執行器配置,用於任務註冊 -->
<bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
	 <property name="corePoolSize" value="10" />
	 <property name="maxPoolSize" value="100" />
	 <property name="queueCapacity" value="500" />
</bean>

	<!-- 4.配置調度工廠:定時任務總管理類,如果將 lazy-init='false'那麼容器啓動就會執行調度程序 -->
	<bean id="startQuertz" lazy-init="false" autowire="no"
		class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="triggers">
			<list>
				<ref bean="initTrigger" /><!--可選-->
				<ref bean="doTimeForToken" />
			</list>
		</property>
		<property name="taskExecutor" ref="executor" />
	</bean>

</beans>

Spring Task: 基於@Scheduled註解定時任務

1.開啓註解

在spring配置文件中:
(1)添加命名空間

// 1.xmlns添加:
xmlns:task="http://www.springframework.org/schema/task"
// 2.xsi:schemaLocation添加:
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd

(2)啓用註解驅動的定時任務

<task:annotation-driven/>

(3)配置定時任務的線程池(可選)
 tip:推薦配置線程池,同quartz中配置線程池原因,若不配置多任務下會出現線程阻塞,導致定時任務無法正常執行。

<task:executor id="executor" pool-size="5" />
 <task:scheduler id="scheduler" pool-size="5" />
 <task:annotation-driven executor="executor" scheduler="scheduler" />

2.編寫定時任務類

這裏我直接修改上面quartz的任務類實現同樣定時任務:

package com.leon.wx.task;

import org.apache.log4j.Logger;

import com.leon.wx.util.WeChatTask;

public class Job {
	private static Logger logger = Logger.getLogger(QuartzJob.class);

	/**
	 * @Description: 定時任務執行獲取 token
	 * @param
	 */
	@Scheduled(cron="0 0/60 * * * ?")   //每小時執行一次
	public static void workForToken() {
		logger.info("開始執行定時任務workForToken...");
		try {
			WeChatTask.getToken_getTicket(); 
		} catch (Exception e) {
			e.printStackTrace();
		}
		logger.info("定時任務workForToken執行完畢。");
	}

}

 

使用Quartz與SpringTask: @Schedule註解區別

 這篇文章寫道比較好:Quartz和Spring Task定時任務的簡單應用和比較
 我的結論是:總的來說,Quartz包含Spring Task:@Schedule所有功能,推薦使用Quartz。
 需要注意的是,對異常的處理,兩者有較大區別,需要按需選用:

  • Quartz的某次執行任務過程中拋出異常,不影響下一次任務的執行,當下一次執行時間到來時,定時器會再次執行任務。
  • SpringTask不同,一旦某個任務在執行過程中拋出異常,則整個定時器生命週期就結束,以後永遠不會再執行定時器任務。

 

Cron表達式的用法及參考實例

在線工具:在線Cron表達式生成器
在這裏插入圖片描述

Corn表達式元素:
字段 允許值 允許的特殊字符
0-59 ,- * /
0-59 , - * /
小時 0-23 ,- * /
日期 1-31 ,- * ? / L W C
月份 1-12 或者 JAN-DEC ,- * /
星期 1-7 或者 SUN-SAT ,- * ? / L C #
年(可選) 留空,1970-2099 , - * /
符號含義
  • “*”字符:被用來指定所有的值。如:"*"在分鐘的字段域裏表示“每分鐘”。
  • “?”字符:只在日期域和星期域中使用。它被用來指定“非明確的值”。當你需要通過在這兩個域中的一個來指定一些東西的時候,它是有用的。月份中的日期和星期中的日期這兩個元素時互斥的一起應該通過設置一個問號來表明不想設置那個字段,詳情看下面Corn表達式舉例。
  • “-”字符:被用來指定一個範圍。如:“10-12”在小時域意味着“10點、11點、12點”。
  • “,”字符:被用來指定另外的值。如:“MON,WED,FRI”在星期域裏表示”星期一、星期三、星期五”。
  • “/”字符:用於指定增量。如:“0/15”在秒域意思是每分鐘的0,15,30和45秒。“5/15”在分鐘域表示每小時的5,20,35和50。符號“”在“/”前面(如:/10)等價於0在“/”前面(如:0/10)。記住一條本質:表達式的每個數值域都是一個有最大值和最小值的集合,如:秒域和分鐘域的集合是0-59,日期域是1-31,月份域是1-12。字符“/”可以幫助你在每個字符域中取相應的數值。如:“7/6”在月份域的時候只有當7月的時候纔會觸發,並不是表示每個6月。
  • “L”字符:是‘last’的省略寫法可以表示day-of-month和day-of-week域,但在兩個字段中的意思不同,例如day-of-month域中表示一個月的最後一天。如果在day-of-week域表示‘7’或者‘SAT’,如果在day-of-week域中前面加上數字,它表示一個月的最後幾天,例如‘6L’就表示一個月的最後一個星期五。
  • “W”字符:只允許日期域出現。這個字符用於指定日期的最近工作日。例如:如果你在日期域中寫 “15W”,表示:這個月15號最近的工作日。所以,如果15號是週六,則任務會在14號觸發。如果15好是週日,則任務會在週一也就是16號觸發。如果是在日期域填寫“1W”即使1號是週六,那麼任務也只會在下週一,也就是3號觸發,“W”字符指定的最近工作日是不能夠跨月份的。字符“W”只能配合一個單獨的數值使用,不能夠是一個數字段,如:1-15W是錯誤的。“L”和“W”可以在日期域中聯合使用,LW表示這個月最後一週的工作日。
  • “#”字符:只允許在星期域中出現。這個字符用於指定本月的某某天。例如:“6#3”表示本月第三週的星期五(6表示星期五,3表示第三週)。“2#1”表示本月第一週的星期
    一。“4#5”表示第五週的星期三。
  • “C”字符:允許在日期域和星期域出現。這個字符依靠一個指定的“日曆”。也就是說這個表達式的值依賴於相關的“日曆”的計算結果,如果沒有“日曆”關聯,則等價於所有包含的“日曆”。如:日期域是“5C”表示關聯“日曆”中第一天,或者這個月開始的第一天的後5天。星期域是“1C”表示關聯“日曆”中第一天,或者星期的第一天的後1天,也就是週日的後一天(週一)。
     
常見Corn表達式舉例

好記性不如爛筆頭啊~~

corn表達式 含義
0/5 * * * * ? 每5秒執行一次
“0 0 12 * * ?” 每天中午12點觸發
“0 15 10 ? * *” 每天上午10:15觸發
“0 15 10 * * ?” 每天上午10:15觸發
“0 15 10 * * ? *” 每天上午10:15觸發
“0 15 10 * * ? 2005” 2005年的每天上午10:15觸發
“0 * 14 * * ?” 在每天下午2點到下午2:59期間的每1分鐘觸發
“0 0/5 14 * * ?” 在每天下午2點到下午2:55期間的每5分鐘觸發
“0 0/5 14,18 * * ?” 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發
“0 0-5 14 * * ?” 在每天下午2點到下午2:05期間的每1分鐘觸發
“0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44觸發
“0 15 10 ? * MON-FRI” 週一至週五的上午10:15觸發
“0 15 10 15 * ?” 每月15日上午10:15觸發
“0 15 10 L * ?” 每月最後一日的上午10:15觸發
“0 15 10 ? * 6L” 每月的最後一個星期五上午10:15觸發
“0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最後一個星期五上午10:15觸發
“0 15 10 ? * 6#3” 每月的第三個星期五上午10:15觸發

 


參考文獻

https://www.cnblogs.com/hongwz/p/5642429.html
https://blog.csdn.net/tby415/article/details/80180692
https://www.cnblogs.com/kxxiang/p/4297535.html
https://www.cnblogs.com/marvinn/p/10563640.html
https://blog.csdn.net/zp437734552/article/details/51899275/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章