这几天都在准备面试,发现很多知识点和技术自己用过但没有好好总结,只是在云笔记里零乱记录了一下,有点茶壶煮饺子有货吐不出的感觉,尴尬~,这次决定遇到一个就总结一个。本文主要总结了一下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/