mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-08-18 20:38:02 +08:00
change line sep
This commit is contained in:
@@ -1,29 +1,29 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<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>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 定时任务</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<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>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 定时任务</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
package cn.hutool.cron;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* 定时任务配置类
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.4.7
|
||||
*/
|
||||
public class CronConfig {
|
||||
|
||||
/**
|
||||
* 时区
|
||||
*/
|
||||
protected TimeZone timezone = TimeZone.getDefault();
|
||||
/**
|
||||
* 是否支持秒匹配
|
||||
*/
|
||||
protected boolean matchSecond;
|
||||
|
||||
public CronConfig(){
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置时区
|
||||
*
|
||||
* @param timezone 时区
|
||||
* @return this
|
||||
*/
|
||||
public CronConfig setTimeZone(TimeZone timezone) {
|
||||
this.timezone = timezone;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得时区,默认为 {@link TimeZone#getDefault()}
|
||||
*
|
||||
* @return 时区
|
||||
*/
|
||||
public TimeZone getTimeZone() {
|
||||
return this.timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持秒匹配
|
||||
*
|
||||
* @return <code>true</code>使用,<code>false</code>不使用
|
||||
*/
|
||||
public boolean isMatchSecond() {
|
||||
return this.matchSecond;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否支持秒匹配,默认不使用
|
||||
*
|
||||
* @param isMatchSecond <code>true</code>支持,<code>false</code>不支持
|
||||
* @return this
|
||||
*/
|
||||
public CronConfig setMatchSecond(boolean isMatchSecond) {
|
||||
this.matchSecond = isMatchSecond;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* 定时任务配置类
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.4.7
|
||||
*/
|
||||
public class CronConfig {
|
||||
|
||||
/**
|
||||
* 时区
|
||||
*/
|
||||
protected TimeZone timezone = TimeZone.getDefault();
|
||||
/**
|
||||
* 是否支持秒匹配
|
||||
*/
|
||||
protected boolean matchSecond;
|
||||
|
||||
public CronConfig(){
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置时区
|
||||
*
|
||||
* @param timezone 时区
|
||||
* @return this
|
||||
*/
|
||||
public CronConfig setTimeZone(TimeZone timezone) {
|
||||
this.timezone = timezone;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得时区,默认为 {@link TimeZone#getDefault()}
|
||||
*
|
||||
* @return 时区
|
||||
*/
|
||||
public TimeZone getTimeZone() {
|
||||
return this.timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持秒匹配
|
||||
*
|
||||
* @return <code>true</code>使用,<code>false</code>不使用
|
||||
*/
|
||||
public boolean isMatchSecond() {
|
||||
return this.matchSecond;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否支持秒匹配,默认不使用
|
||||
*
|
||||
* @param isMatchSecond <code>true</code>支持,<code>false</code>不支持
|
||||
* @return this
|
||||
*/
|
||||
public CronConfig setMatchSecond(boolean isMatchSecond) {
|
||||
this.matchSecond = isMatchSecond;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 定时任务异常
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class CronException extends RuntimeException{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public CronException(Throwable e) {
|
||||
super(e.getMessage(), e);
|
||||
}
|
||||
|
||||
public CronException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CronException(String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public CronException(Throwable throwable, String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 定时任务异常
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class CronException extends RuntimeException{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public CronException(Throwable e) {
|
||||
super(e.getMessage(), e);
|
||||
}
|
||||
|
||||
public CronException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CronException(String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public CronException(Throwable throwable, String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +1,99 @@
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.log.Log;
|
||||
import cn.hutool.log.LogFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 定时任务计时器<br>
|
||||
* 计时器线程每隔一分钟(一秒钟)检查一次任务列表,一旦匹配到执行对应的Task
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class CronTimer extends Thread implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Log log = LogFactory.get();
|
||||
|
||||
/** 定时单元:秒 */
|
||||
private final long TIMER_UNIT_SECOND = DateUnit.SECOND.getMillis();
|
||||
/** 定时单元:分 */
|
||||
private final long TIMER_UNIT_MINUTE = DateUnit.MINUTE.getMillis();
|
||||
|
||||
/** 定时任务是否已经被强制关闭 */
|
||||
private boolean isStop;
|
||||
private final Scheduler scheduler;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param scheduler {@link Scheduler}
|
||||
*/
|
||||
public CronTimer(Scheduler scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final long timerUnit = this.scheduler.config.matchSecond ? TIMER_UNIT_SECOND : TIMER_UNIT_MINUTE;
|
||||
|
||||
long thisTime = System.currentTimeMillis();
|
||||
long nextTime;
|
||||
long sleep;
|
||||
while(false == isStop){
|
||||
//下一时间计算是按照上一个执行点开始时间计算的
|
||||
//此处除以定时单位是为了清零单位以下部分,例如单位是分则秒和毫秒清零
|
||||
nextTime = ((thisTime / timerUnit) + 1) * timerUnit;
|
||||
sleep = nextTime - System.currentTimeMillis();
|
||||
if(isValidSleepMillis(sleep, timerUnit)){
|
||||
if (false == ThreadUtil.safeSleep(sleep)) {
|
||||
//等待直到下一个时间点,如果被中断直接退出Timer
|
||||
break;
|
||||
}
|
||||
//执行点,时间记录为执行开始的时间,而非结束时间
|
||||
thisTime = System.currentTimeMillis();
|
||||
spawnLauncher(thisTime);
|
||||
} else{
|
||||
// 非正常时间重新计算(issue#1224@Github)
|
||||
thisTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
log.debug("Hutool-cron timer stopped.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭定时器
|
||||
*/
|
||||
synchronized public void stopTimer() {
|
||||
this.isStop = true;
|
||||
ThreadUtil.interrupt(this, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动匹配
|
||||
* @param millis 当前时间
|
||||
*/
|
||||
private void spawnLauncher(final long millis){
|
||||
this.scheduler.taskLauncherManager.spawnLauncher(millis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为有效的sleep毫秒数,包括:
|
||||
* <pre>
|
||||
* 1. 是否>0,防止用户向未来调整时间
|
||||
* 1. 是否<两倍的间隔单位,防止用户向历史调整时间
|
||||
* </pre>
|
||||
*
|
||||
* @param millis 毫秒数
|
||||
* @param timerUnit 定时单位,为秒或者分的毫秒值
|
||||
* @return 是否为有效的sleep毫秒数
|
||||
* @since 5.3.2
|
||||
*/
|
||||
private static boolean isValidSleepMillis(long millis, long timerUnit){
|
||||
return millis > 0 &&
|
||||
// 防止用户向前调整时间导致的长时间sleep
|
||||
millis < (2 * timerUnit);
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.log.Log;
|
||||
import cn.hutool.log.LogFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 定时任务计时器<br>
|
||||
* 计时器线程每隔一分钟(一秒钟)检查一次任务列表,一旦匹配到执行对应的Task
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class CronTimer extends Thread implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Log log = LogFactory.get();
|
||||
|
||||
/** 定时单元:秒 */
|
||||
private final long TIMER_UNIT_SECOND = DateUnit.SECOND.getMillis();
|
||||
/** 定时单元:分 */
|
||||
private final long TIMER_UNIT_MINUTE = DateUnit.MINUTE.getMillis();
|
||||
|
||||
/** 定时任务是否已经被强制关闭 */
|
||||
private boolean isStop;
|
||||
private final Scheduler scheduler;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param scheduler {@link Scheduler}
|
||||
*/
|
||||
public CronTimer(Scheduler scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final long timerUnit = this.scheduler.config.matchSecond ? TIMER_UNIT_SECOND : TIMER_UNIT_MINUTE;
|
||||
|
||||
long thisTime = System.currentTimeMillis();
|
||||
long nextTime;
|
||||
long sleep;
|
||||
while(false == isStop){
|
||||
//下一时间计算是按照上一个执行点开始时间计算的
|
||||
//此处除以定时单位是为了清零单位以下部分,例如单位是分则秒和毫秒清零
|
||||
nextTime = ((thisTime / timerUnit) + 1) * timerUnit;
|
||||
sleep = nextTime - System.currentTimeMillis();
|
||||
if(isValidSleepMillis(sleep, timerUnit)){
|
||||
if (false == ThreadUtil.safeSleep(sleep)) {
|
||||
//等待直到下一个时间点,如果被中断直接退出Timer
|
||||
break;
|
||||
}
|
||||
//执行点,时间记录为执行开始的时间,而非结束时间
|
||||
thisTime = System.currentTimeMillis();
|
||||
spawnLauncher(thisTime);
|
||||
} else{
|
||||
// 非正常时间重新计算(issue#1224@Github)
|
||||
thisTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
log.debug("Hutool-cron timer stopped.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭定时器
|
||||
*/
|
||||
synchronized public void stopTimer() {
|
||||
this.isStop = true;
|
||||
ThreadUtil.interrupt(this, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动匹配
|
||||
* @param millis 当前时间
|
||||
*/
|
||||
private void spawnLauncher(final long millis){
|
||||
this.scheduler.taskLauncherManager.spawnLauncher(millis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为有效的sleep毫秒数,包括:
|
||||
* <pre>
|
||||
* 1. 是否>0,防止用户向未来调整时间
|
||||
* 1. 是否<两倍的间隔单位,防止用户向历史调整时间
|
||||
* </pre>
|
||||
*
|
||||
* @param millis 毫秒数
|
||||
* @param timerUnit 定时单位,为秒或者分的毫秒值
|
||||
* @return 是否为有效的sleep毫秒数
|
||||
* @since 5.3.2
|
||||
*/
|
||||
private static boolean isValidSleepMillis(long millis, long timerUnit){
|
||||
return millis > 0 &&
|
||||
// 防止用户向前调整时间导致的长时间sleep
|
||||
millis < (2 * timerUnit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,448 +1,448 @@
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.thread.ExecutorBuilder;
|
||||
import cn.hutool.core.thread.ThreadFactoryBuilder;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.listener.TaskListener;
|
||||
import cn.hutool.cron.listener.TaskListenerManager;
|
||||
import cn.hutool.cron.pattern.CronPattern;
|
||||
import cn.hutool.cron.task.InvokeTask;
|
||||
import cn.hutool.cron.task.RunnableTask;
|
||||
import cn.hutool.cron.task.Task;
|
||||
import cn.hutool.log.StaticLog;
|
||||
import cn.hutool.setting.Setting;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* 任务调度器<br>
|
||||
*
|
||||
* 调度器启动流程:<br>
|
||||
*
|
||||
* <pre>
|
||||
* 启动Timer =》 启动TaskLauncher =》 启动TaskExecutor
|
||||
* </pre>
|
||||
*
|
||||
* 调度器关闭流程:<br>
|
||||
*
|
||||
* <pre>
|
||||
* 关闭Timer =》 关闭所有运行中的TaskLauncher =》 关闭所有运行中的TaskExecutor
|
||||
* </pre>
|
||||
*
|
||||
* 其中:
|
||||
*
|
||||
* <pre>
|
||||
* <strong>TaskLauncher</strong>:定时器每分钟调用一次(如果{@link Scheduler#isMatchSecond()}为<code>true</code>每秒调用一次),
|
||||
* 负责检查<strong>TaskTable</strong>是否有匹配到此时间运行的Task
|
||||
* </pre>
|
||||
*
|
||||
* <pre>
|
||||
* <strong>TaskExecutor</strong>:TaskLauncher匹配成功后,触发TaskExecutor执行具体的作业,执行完毕销毁
|
||||
* </pre>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class Scheduler implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
/** 定时任务配置 */
|
||||
protected CronConfig config = new CronConfig();
|
||||
/** 是否已经启动 */
|
||||
private boolean started = false;
|
||||
/** 是否为守护线程 */
|
||||
protected boolean daemon;
|
||||
|
||||
/** 定时器 */
|
||||
private CronTimer timer;
|
||||
/** 定时任务表 */
|
||||
protected TaskTable taskTable = new TaskTable();
|
||||
/** 启动器管理器 */
|
||||
protected TaskLauncherManager taskLauncherManager;
|
||||
/** 执行器管理器 */
|
||||
protected TaskExecutorManager taskExecutorManager;
|
||||
/** 监听管理器列表 */
|
||||
protected TaskListenerManager listenerManager = new TaskListenerManager();
|
||||
/** 线程池,用于执行TaskLauncher和TaskExecutor */
|
||||
protected ExecutorService threadExecutor;
|
||||
|
||||
// --------------------------------------------------------- Getters and Setters start
|
||||
/**
|
||||
* 设置时区
|
||||
*
|
||||
* @param timeZone 时区
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler setTimeZone(TimeZone timeZone) {
|
||||
this.config.setTimeZone(timeZone);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得时区,默认为 {@link TimeZone#getDefault()}
|
||||
*
|
||||
* @return 时区
|
||||
*/
|
||||
public TimeZone getTimeZone() {
|
||||
return this.config.getTimeZone();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否为守护线程<br>
|
||||
* 如果为true,则在调用{@link #stop()}方法后执行的定时任务立即结束,否则等待执行完毕才结束。默认非守护线程
|
||||
*
|
||||
* @param on <code>true</code>为守护线程,否则非守护线程
|
||||
* @return this
|
||||
* @throws CronException 定时任务已经启动抛出此异常
|
||||
*/
|
||||
public Scheduler setDaemon(boolean on) throws CronException {
|
||||
lock.lock();
|
||||
try {
|
||||
if (this.started) {
|
||||
throw new CronException("Scheduler already started!");
|
||||
}
|
||||
this.daemon = on;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为守护线程
|
||||
*
|
||||
* @return 是否为守护线程
|
||||
*/
|
||||
public boolean isDaemon() {
|
||||
return this.daemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持秒匹配
|
||||
*
|
||||
* @return <code>true</code>使用,<code>false</code>不使用
|
||||
*/
|
||||
public boolean isMatchSecond() {
|
||||
return this.config.isMatchSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否支持秒匹配,默认不使用
|
||||
*
|
||||
* @param isMatchSecond <code>true</code>支持,<code>false</code>不支持
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler setMatchSecond(boolean isMatchSecond) {
|
||||
this.config.setMatchSecond(isMatchSecond);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加监听器
|
||||
*
|
||||
* @param listener {@link TaskListener}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler addListener(TaskListener listener) {
|
||||
this.listenerManager.addListener(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听器
|
||||
*
|
||||
* @param listener {@link TaskListener}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler removeListener(TaskListener listener) {
|
||||
this.listenerManager.removeListener(listener);
|
||||
return this;
|
||||
}
|
||||
// --------------------------------------------------------- Getters and Setters end
|
||||
|
||||
// -------------------------------------------------------------------- shcedule start
|
||||
/**
|
||||
* 批量加入配置文件中的定时任务<br>
|
||||
* 配置文件格式为: xxx.xxx.xxx.Class.method = * * * * *
|
||||
*
|
||||
* @param cronSetting 定时任务设置文件
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler schedule(Setting cronSetting) {
|
||||
if (CollUtil.isNotEmpty(cronSetting)) {
|
||||
String group;
|
||||
for (Entry<String, LinkedHashMap<String, String>> groupedEntry : cronSetting.getGroupedMap().entrySet()) {
|
||||
group = groupedEntry.getKey();
|
||||
for (Entry<String, String> entry : groupedEntry.getValue().entrySet()) {
|
||||
String jobClass = entry.getKey();
|
||||
if (StrUtil.isNotBlank(group)) {
|
||||
jobClass = group + CharUtil.DOT + jobClass;
|
||||
}
|
||||
final String pattern = entry.getValue();
|
||||
StaticLog.debug("Load job: {} {}", pattern, jobClass);
|
||||
try {
|
||||
schedule(pattern, new InvokeTask(jobClass));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Schedule [{}] [{}] error!", pattern, jobClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task,使用随机UUID
|
||||
*
|
||||
* @param pattern {@link CronPattern}对应的String表达式
|
||||
* @param task {@link Runnable}
|
||||
* @return ID
|
||||
*/
|
||||
public String schedule(String pattern, Runnable task) {
|
||||
return schedule(pattern, new RunnableTask(task));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task,使用随机UUID
|
||||
*
|
||||
* @param pattern {@link CronPattern}对应的String表达式
|
||||
* @param task {@link Task}
|
||||
* @return ID
|
||||
*/
|
||||
public String schedule(String pattern, Task task) {
|
||||
String id = IdUtil.fastUUID();
|
||||
schedule(id, pattern, task);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task,如果任务ID已经存在,抛出异常
|
||||
*
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param pattern {@link CronPattern}对应的String表达式
|
||||
* @param task {@link Runnable}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler schedule(String id, String pattern, Runnable task) {
|
||||
return schedule(id, new CronPattern(pattern), new RunnableTask(task));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task,如果任务ID已经存在,抛出异常
|
||||
*
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param pattern {@link CronPattern}对应的String表达式
|
||||
* @param task {@link Task}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler schedule(String id, String pattern, Task task) {
|
||||
return schedule(id, new CronPattern(pattern), task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task,如果任务ID已经存在,抛出异常
|
||||
*
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param pattern {@link CronPattern}
|
||||
* @param task {@link Task}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler schedule(String id, CronPattern pattern, Task task) {
|
||||
taskTable.add(id, pattern, task);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除Task
|
||||
*
|
||||
* @param id Task的ID
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler deschedule(String id) {
|
||||
this.taskTable.remove(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新Task执行的时间规则
|
||||
*
|
||||
* @param id Task的ID
|
||||
* @param pattern {@link CronPattern}
|
||||
* @return this
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public Scheduler updatePattern(String id, CronPattern pattern) {
|
||||
this.taskTable.updatePattern(id, pattern);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取定时任务表,注意此方法返回非复制对象,对返回对象的修改将影响已有定时任务
|
||||
*
|
||||
* @return 定时任务表{@link TaskTable}
|
||||
* @since 4.6.7
|
||||
*/
|
||||
public TaskTable getTaskTable() {
|
||||
return this.taskTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定id的{@link CronPattern}
|
||||
*
|
||||
* @param id ID
|
||||
* @return {@link CronPattern}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public CronPattern getPattern(String id) {
|
||||
return this.taskTable.getPattern(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定id的{@link Task}
|
||||
*
|
||||
* @param id ID
|
||||
* @return {@link Task}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public Task getTask(String id) {
|
||||
return this.taskTable.getTask(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否无任务
|
||||
*
|
||||
* @return true表示无任务
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.taskTable.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前任务数
|
||||
*
|
||||
* @return 当前任务数
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public int size() {
|
||||
return this.taskTable.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空任务表
|
||||
* @return this
|
||||
* @since 4.1.17
|
||||
*/
|
||||
public Scheduler clear() {
|
||||
this.taskTable = new TaskTable();
|
||||
return this;
|
||||
}
|
||||
// -------------------------------------------------------------------- shcedule end
|
||||
|
||||
/**
|
||||
* @return 是否已经启动
|
||||
*/
|
||||
public boolean isStarted() {
|
||||
return this.started;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动
|
||||
*
|
||||
* @param isDaemon 是否以守护线程方式启动,如果为true,则在调用{@link #stop()}方法后执行的定时任务立即结束,否则等待执行完毕才结束。
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler start(boolean isDaemon) {
|
||||
this.daemon = isDaemon;
|
||||
return start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler start() {
|
||||
lock.lock();
|
||||
try {
|
||||
if (this.started) {
|
||||
throw new CronException("Schedule is started!");
|
||||
}
|
||||
|
||||
// 无界线程池,确保每一个需要执行的线程都可以及时运行,同时复用已有现成避免线程重复创建
|
||||
this.threadExecutor = ExecutorBuilder.create().useSynchronousQueue().setThreadFactory(//
|
||||
ThreadFactoryBuilder.create().setNamePrefix("hutool-cron-").setDaemon(this.daemon).build()//
|
||||
).build();
|
||||
this.taskLauncherManager = new TaskLauncherManager(this);
|
||||
this.taskExecutorManager = new TaskExecutorManager(this);
|
||||
|
||||
// Start CronTimer
|
||||
timer = new CronTimer(this);
|
||||
timer.setDaemon(this.daemon);
|
||||
timer.start();
|
||||
this.started = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止定时任务<br>
|
||||
* 此方法调用后会将定时器进程立即结束,如果为守护线程模式,则正在执行的作业也会自动结束,否则作业线程将在执行完成后结束。<br>
|
||||
* 此方法并不会清除任务表中的任务,请调用{@link #clear()} 方法清空任务或者使用{@link #stop(boolean)}方法可选是否清空
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler stop() {
|
||||
return stop(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止定时任务<br>
|
||||
* 此方法调用后会将定时器进程立即结束,如果为守护线程模式,则正在执行的作业也会自动结束,否则作业线程将在执行完成后结束。
|
||||
*
|
||||
* @param clearTasks 是否清除所有任务
|
||||
* @return this
|
||||
* @since 4.1.17
|
||||
*/
|
||||
public Scheduler stop(boolean clearTasks) {
|
||||
lock.lock();
|
||||
try {
|
||||
if (false == started) {
|
||||
throw new IllegalStateException("Scheduler not started !");
|
||||
}
|
||||
|
||||
// 停止CronTimer
|
||||
this.timer.stopTimer();
|
||||
this.timer = null;
|
||||
|
||||
//停止线程池
|
||||
this.threadExecutor.shutdown();
|
||||
this.threadExecutor = null;
|
||||
|
||||
//可选是否清空任务表
|
||||
if(clearTasks) {
|
||||
clear();
|
||||
}
|
||||
|
||||
// 修改标志
|
||||
started = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.thread.ExecutorBuilder;
|
||||
import cn.hutool.core.thread.ThreadFactoryBuilder;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.listener.TaskListener;
|
||||
import cn.hutool.cron.listener.TaskListenerManager;
|
||||
import cn.hutool.cron.pattern.CronPattern;
|
||||
import cn.hutool.cron.task.InvokeTask;
|
||||
import cn.hutool.cron.task.RunnableTask;
|
||||
import cn.hutool.cron.task.Task;
|
||||
import cn.hutool.log.StaticLog;
|
||||
import cn.hutool.setting.Setting;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* 任务调度器<br>
|
||||
*
|
||||
* 调度器启动流程:<br>
|
||||
*
|
||||
* <pre>
|
||||
* 启动Timer =》 启动TaskLauncher =》 启动TaskExecutor
|
||||
* </pre>
|
||||
*
|
||||
* 调度器关闭流程:<br>
|
||||
*
|
||||
* <pre>
|
||||
* 关闭Timer =》 关闭所有运行中的TaskLauncher =》 关闭所有运行中的TaskExecutor
|
||||
* </pre>
|
||||
*
|
||||
* 其中:
|
||||
*
|
||||
* <pre>
|
||||
* <strong>TaskLauncher</strong>:定时器每分钟调用一次(如果{@link Scheduler#isMatchSecond()}为<code>true</code>每秒调用一次),
|
||||
* 负责检查<strong>TaskTable</strong>是否有匹配到此时间运行的Task
|
||||
* </pre>
|
||||
*
|
||||
* <pre>
|
||||
* <strong>TaskExecutor</strong>:TaskLauncher匹配成功后,触发TaskExecutor执行具体的作业,执行完毕销毁
|
||||
* </pre>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class Scheduler implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
/** 定时任务配置 */
|
||||
protected CronConfig config = new CronConfig();
|
||||
/** 是否已经启动 */
|
||||
private boolean started = false;
|
||||
/** 是否为守护线程 */
|
||||
protected boolean daemon;
|
||||
|
||||
/** 定时器 */
|
||||
private CronTimer timer;
|
||||
/** 定时任务表 */
|
||||
protected TaskTable taskTable = new TaskTable();
|
||||
/** 启动器管理器 */
|
||||
protected TaskLauncherManager taskLauncherManager;
|
||||
/** 执行器管理器 */
|
||||
protected TaskExecutorManager taskExecutorManager;
|
||||
/** 监听管理器列表 */
|
||||
protected TaskListenerManager listenerManager = new TaskListenerManager();
|
||||
/** 线程池,用于执行TaskLauncher和TaskExecutor */
|
||||
protected ExecutorService threadExecutor;
|
||||
|
||||
// --------------------------------------------------------- Getters and Setters start
|
||||
/**
|
||||
* 设置时区
|
||||
*
|
||||
* @param timeZone 时区
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler setTimeZone(TimeZone timeZone) {
|
||||
this.config.setTimeZone(timeZone);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得时区,默认为 {@link TimeZone#getDefault()}
|
||||
*
|
||||
* @return 时区
|
||||
*/
|
||||
public TimeZone getTimeZone() {
|
||||
return this.config.getTimeZone();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否为守护线程<br>
|
||||
* 如果为true,则在调用{@link #stop()}方法后执行的定时任务立即结束,否则等待执行完毕才结束。默认非守护线程
|
||||
*
|
||||
* @param on <code>true</code>为守护线程,否则非守护线程
|
||||
* @return this
|
||||
* @throws CronException 定时任务已经启动抛出此异常
|
||||
*/
|
||||
public Scheduler setDaemon(boolean on) throws CronException {
|
||||
lock.lock();
|
||||
try {
|
||||
if (this.started) {
|
||||
throw new CronException("Scheduler already started!");
|
||||
}
|
||||
this.daemon = on;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为守护线程
|
||||
*
|
||||
* @return 是否为守护线程
|
||||
*/
|
||||
public boolean isDaemon() {
|
||||
return this.daemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持秒匹配
|
||||
*
|
||||
* @return <code>true</code>使用,<code>false</code>不使用
|
||||
*/
|
||||
public boolean isMatchSecond() {
|
||||
return this.config.isMatchSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否支持秒匹配,默认不使用
|
||||
*
|
||||
* @param isMatchSecond <code>true</code>支持,<code>false</code>不支持
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler setMatchSecond(boolean isMatchSecond) {
|
||||
this.config.setMatchSecond(isMatchSecond);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加监听器
|
||||
*
|
||||
* @param listener {@link TaskListener}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler addListener(TaskListener listener) {
|
||||
this.listenerManager.addListener(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听器
|
||||
*
|
||||
* @param listener {@link TaskListener}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler removeListener(TaskListener listener) {
|
||||
this.listenerManager.removeListener(listener);
|
||||
return this;
|
||||
}
|
||||
// --------------------------------------------------------- Getters and Setters end
|
||||
|
||||
// -------------------------------------------------------------------- shcedule start
|
||||
/**
|
||||
* 批量加入配置文件中的定时任务<br>
|
||||
* 配置文件格式为: xxx.xxx.xxx.Class.method = * * * * *
|
||||
*
|
||||
* @param cronSetting 定时任务设置文件
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler schedule(Setting cronSetting) {
|
||||
if (CollUtil.isNotEmpty(cronSetting)) {
|
||||
String group;
|
||||
for (Entry<String, LinkedHashMap<String, String>> groupedEntry : cronSetting.getGroupedMap().entrySet()) {
|
||||
group = groupedEntry.getKey();
|
||||
for (Entry<String, String> entry : groupedEntry.getValue().entrySet()) {
|
||||
String jobClass = entry.getKey();
|
||||
if (StrUtil.isNotBlank(group)) {
|
||||
jobClass = group + CharUtil.DOT + jobClass;
|
||||
}
|
||||
final String pattern = entry.getValue();
|
||||
StaticLog.debug("Load job: {} {}", pattern, jobClass);
|
||||
try {
|
||||
schedule(pattern, new InvokeTask(jobClass));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Schedule [{}] [{}] error!", pattern, jobClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task,使用随机UUID
|
||||
*
|
||||
* @param pattern {@link CronPattern}对应的String表达式
|
||||
* @param task {@link Runnable}
|
||||
* @return ID
|
||||
*/
|
||||
public String schedule(String pattern, Runnable task) {
|
||||
return schedule(pattern, new RunnableTask(task));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task,使用随机UUID
|
||||
*
|
||||
* @param pattern {@link CronPattern}对应的String表达式
|
||||
* @param task {@link Task}
|
||||
* @return ID
|
||||
*/
|
||||
public String schedule(String pattern, Task task) {
|
||||
String id = IdUtil.fastUUID();
|
||||
schedule(id, pattern, task);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task,如果任务ID已经存在,抛出异常
|
||||
*
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param pattern {@link CronPattern}对应的String表达式
|
||||
* @param task {@link Runnable}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler schedule(String id, String pattern, Runnable task) {
|
||||
return schedule(id, new CronPattern(pattern), new RunnableTask(task));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task,如果任务ID已经存在,抛出异常
|
||||
*
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param pattern {@link CronPattern}对应的String表达式
|
||||
* @param task {@link Task}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler schedule(String id, String pattern, Task task) {
|
||||
return schedule(id, new CronPattern(pattern), task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task,如果任务ID已经存在,抛出异常
|
||||
*
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param pattern {@link CronPattern}
|
||||
* @param task {@link Task}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler schedule(String id, CronPattern pattern, Task task) {
|
||||
taskTable.add(id, pattern, task);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除Task
|
||||
*
|
||||
* @param id Task的ID
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler deschedule(String id) {
|
||||
this.taskTable.remove(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新Task执行的时间规则
|
||||
*
|
||||
* @param id Task的ID
|
||||
* @param pattern {@link CronPattern}
|
||||
* @return this
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public Scheduler updatePattern(String id, CronPattern pattern) {
|
||||
this.taskTable.updatePattern(id, pattern);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取定时任务表,注意此方法返回非复制对象,对返回对象的修改将影响已有定时任务
|
||||
*
|
||||
* @return 定时任务表{@link TaskTable}
|
||||
* @since 4.6.7
|
||||
*/
|
||||
public TaskTable getTaskTable() {
|
||||
return this.taskTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定id的{@link CronPattern}
|
||||
*
|
||||
* @param id ID
|
||||
* @return {@link CronPattern}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public CronPattern getPattern(String id) {
|
||||
return this.taskTable.getPattern(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定id的{@link Task}
|
||||
*
|
||||
* @param id ID
|
||||
* @return {@link Task}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public Task getTask(String id) {
|
||||
return this.taskTable.getTask(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否无任务
|
||||
*
|
||||
* @return true表示无任务
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.taskTable.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前任务数
|
||||
*
|
||||
* @return 当前任务数
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public int size() {
|
||||
return this.taskTable.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空任务表
|
||||
* @return this
|
||||
* @since 4.1.17
|
||||
*/
|
||||
public Scheduler clear() {
|
||||
this.taskTable = new TaskTable();
|
||||
return this;
|
||||
}
|
||||
// -------------------------------------------------------------------- shcedule end
|
||||
|
||||
/**
|
||||
* @return 是否已经启动
|
||||
*/
|
||||
public boolean isStarted() {
|
||||
return this.started;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动
|
||||
*
|
||||
* @param isDaemon 是否以守护线程方式启动,如果为true,则在调用{@link #stop()}方法后执行的定时任务立即结束,否则等待执行完毕才结束。
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler start(boolean isDaemon) {
|
||||
this.daemon = isDaemon;
|
||||
return start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler start() {
|
||||
lock.lock();
|
||||
try {
|
||||
if (this.started) {
|
||||
throw new CronException("Schedule is started!");
|
||||
}
|
||||
|
||||
// 无界线程池,确保每一个需要执行的线程都可以及时运行,同时复用已有现成避免线程重复创建
|
||||
this.threadExecutor = ExecutorBuilder.create().useSynchronousQueue().setThreadFactory(//
|
||||
ThreadFactoryBuilder.create().setNamePrefix("hutool-cron-").setDaemon(this.daemon).build()//
|
||||
).build();
|
||||
this.taskLauncherManager = new TaskLauncherManager(this);
|
||||
this.taskExecutorManager = new TaskExecutorManager(this);
|
||||
|
||||
// Start CronTimer
|
||||
timer = new CronTimer(this);
|
||||
timer.setDaemon(this.daemon);
|
||||
timer.start();
|
||||
this.started = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止定时任务<br>
|
||||
* 此方法调用后会将定时器进程立即结束,如果为守护线程模式,则正在执行的作业也会自动结束,否则作业线程将在执行完成后结束。<br>
|
||||
* 此方法并不会清除任务表中的任务,请调用{@link #clear()} 方法清空任务或者使用{@link #stop(boolean)}方法可选是否清空
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler stop() {
|
||||
return stop(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止定时任务<br>
|
||||
* 此方法调用后会将定时器进程立即结束,如果为守护线程模式,则正在执行的作业也会自动结束,否则作业线程将在执行完成后结束。
|
||||
*
|
||||
* @param clearTasks 是否清除所有任务
|
||||
* @return this
|
||||
* @since 4.1.17
|
||||
*/
|
||||
public Scheduler stop(boolean clearTasks) {
|
||||
lock.lock();
|
||||
try {
|
||||
if (false == started) {
|
||||
throw new IllegalStateException("Scheduler not started !");
|
||||
}
|
||||
|
||||
// 停止CronTimer
|
||||
this.timer.stopTimer();
|
||||
this.timer = null;
|
||||
|
||||
//停止线程池
|
||||
this.threadExecutor.shutdown();
|
||||
this.threadExecutor = null;
|
||||
|
||||
//可选是否清空任务表
|
||||
if(clearTasks) {
|
||||
clear();
|
||||
}
|
||||
|
||||
// 修改标志
|
||||
started = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.cron.task.CronTask;
|
||||
import cn.hutool.cron.task.Task;
|
||||
|
||||
/**
|
||||
* 作业执行器<br>
|
||||
* 执行具体的作业,执行完毕销毁<br>
|
||||
* 作业执行器唯一关联一个作业,负责管理作业的运行的生命周期。
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class TaskExecutor implements Runnable {
|
||||
|
||||
private final Scheduler scheduler;
|
||||
private final CronTask task;
|
||||
|
||||
/**
|
||||
* 获得原始任务对象
|
||||
*
|
||||
* @return 任务对象
|
||||
*/
|
||||
public Task getTask() {
|
||||
return this.task.getRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得原始任务对象
|
||||
*
|
||||
* @return 任务对象
|
||||
* @since 5.4.7
|
||||
*/
|
||||
public CronTask getCronTask() {
|
||||
return this.task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param scheduler 调度器
|
||||
* @param task 被执行的任务
|
||||
*/
|
||||
public TaskExecutor(Scheduler scheduler, CronTask task) {
|
||||
this.scheduler = scheduler;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
scheduler.listenerManager.notifyTaskStart(this);
|
||||
task.execute();
|
||||
scheduler.listenerManager.notifyTaskSucceeded(this);
|
||||
} catch (Exception e) {
|
||||
scheduler.listenerManager.notifyTaskFailed(this, e);
|
||||
} finally {
|
||||
scheduler.taskExecutorManager.notifyExecutorCompleted(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.cron.task.CronTask;
|
||||
import cn.hutool.cron.task.Task;
|
||||
|
||||
/**
|
||||
* 作业执行器<br>
|
||||
* 执行具体的作业,执行完毕销毁<br>
|
||||
* 作业执行器唯一关联一个作业,负责管理作业的运行的生命周期。
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class TaskExecutor implements Runnable {
|
||||
|
||||
private final Scheduler scheduler;
|
||||
private final CronTask task;
|
||||
|
||||
/**
|
||||
* 获得原始任务对象
|
||||
*
|
||||
* @return 任务对象
|
||||
*/
|
||||
public Task getTask() {
|
||||
return this.task.getRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得原始任务对象
|
||||
*
|
||||
* @return 任务对象
|
||||
* @since 5.4.7
|
||||
*/
|
||||
public CronTask getCronTask() {
|
||||
return this.task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param scheduler 调度器
|
||||
* @param task 被执行的任务
|
||||
*/
|
||||
public TaskExecutor(Scheduler scheduler, CronTask task) {
|
||||
this.scheduler = scheduler;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
scheduler.listenerManager.notifyTaskStart(this);
|
||||
task.execute();
|
||||
scheduler.listenerManager.notifyTaskSucceeded(this);
|
||||
} catch (Exception e) {
|
||||
scheduler.listenerManager.notifyTaskFailed(this, e);
|
||||
} finally {
|
||||
scheduler.taskExecutorManager.notifyExecutorCompleted(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.cron.task.CronTask;
|
||||
import cn.hutool.cron.task.Task;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 作业执行管理器<br>
|
||||
* 负责管理作业的启动、停止等
|
||||
*
|
||||
* <p>
|
||||
* 此类用于管理正在运行的作业情况,作业启动后加入任务列表,任务结束移除
|
||||
* </p>
|
||||
*
|
||||
* @author Looly
|
||||
* @since 3.0.1
|
||||
*/
|
||||
public class TaskExecutorManager implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected Scheduler scheduler;
|
||||
/**
|
||||
* 执行器列表
|
||||
*/
|
||||
private final List<TaskExecutor> executors = new ArrayList<>();
|
||||
|
||||
public TaskExecutorManager(Scheduler scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有正在执行的任务调度执行器
|
||||
*
|
||||
* @return 任务执行器列表
|
||||
* @since 4.6.7
|
||||
*/
|
||||
public List<TaskExecutor> getExecutors() {
|
||||
return Collections.unmodifiableList(this.executors);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动 执行器TaskExecutor,即启动作业
|
||||
*
|
||||
* @param task {@link Task}
|
||||
* @return {@link TaskExecutor}
|
||||
*/
|
||||
public TaskExecutor spawnExecutor(CronTask task) {
|
||||
final TaskExecutor executor = new TaskExecutor(this.scheduler, task);
|
||||
synchronized (this.executors) {
|
||||
this.executors.add(executor);
|
||||
}
|
||||
// 子线程是否为deamon线程取决于父线程,因此此处无需显示调用
|
||||
// executor.setDaemon(this.scheduler.daemon);
|
||||
// executor.start();
|
||||
this.scheduler.threadExecutor.execute(executor);
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行器执行完毕调用此方法,将执行器从执行器列表移除,此方法由{@link TaskExecutor}对象调用,用于通知管理器自身已完成执行
|
||||
*
|
||||
* @param executor 执行器 {@link TaskExecutor}
|
||||
* @return this
|
||||
*/
|
||||
public TaskExecutorManager notifyExecutorCompleted(TaskExecutor executor) {
|
||||
synchronized (executors) {
|
||||
executors.remove(executor);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止所有TaskExecutor
|
||||
*
|
||||
* @return this
|
||||
* @deprecated 作业执行器只是执行给定的定时任务线程,无法强制关闭,可通过deamon线程方式关闭之
|
||||
*/
|
||||
@Deprecated
|
||||
public TaskExecutorManager destroy() {
|
||||
// synchronized (this.executors) {
|
||||
// for (TaskExecutor taskExecutor : executors) {
|
||||
// ThreadUtil.interupt(taskExecutor, false);
|
||||
// }
|
||||
// }
|
||||
this.executors.clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.cron.task.CronTask;
|
||||
import cn.hutool.cron.task.Task;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 作业执行管理器<br>
|
||||
* 负责管理作业的启动、停止等
|
||||
*
|
||||
* <p>
|
||||
* 此类用于管理正在运行的作业情况,作业启动后加入任务列表,任务结束移除
|
||||
* </p>
|
||||
*
|
||||
* @author Looly
|
||||
* @since 3.0.1
|
||||
*/
|
||||
public class TaskExecutorManager implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected Scheduler scheduler;
|
||||
/**
|
||||
* 执行器列表
|
||||
*/
|
||||
private final List<TaskExecutor> executors = new ArrayList<>();
|
||||
|
||||
public TaskExecutorManager(Scheduler scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有正在执行的任务调度执行器
|
||||
*
|
||||
* @return 任务执行器列表
|
||||
* @since 4.6.7
|
||||
*/
|
||||
public List<TaskExecutor> getExecutors() {
|
||||
return Collections.unmodifiableList(this.executors);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动 执行器TaskExecutor,即启动作业
|
||||
*
|
||||
* @param task {@link Task}
|
||||
* @return {@link TaskExecutor}
|
||||
*/
|
||||
public TaskExecutor spawnExecutor(CronTask task) {
|
||||
final TaskExecutor executor = new TaskExecutor(this.scheduler, task);
|
||||
synchronized (this.executors) {
|
||||
this.executors.add(executor);
|
||||
}
|
||||
// 子线程是否为deamon线程取决于父线程,因此此处无需显示调用
|
||||
// executor.setDaemon(this.scheduler.daemon);
|
||||
// executor.start();
|
||||
this.scheduler.threadExecutor.execute(executor);
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行器执行完毕调用此方法,将执行器从执行器列表移除,此方法由{@link TaskExecutor}对象调用,用于通知管理器自身已完成执行
|
||||
*
|
||||
* @param executor 执行器 {@link TaskExecutor}
|
||||
* @return this
|
||||
*/
|
||||
public TaskExecutorManager notifyExecutorCompleted(TaskExecutor executor) {
|
||||
synchronized (executors) {
|
||||
executors.remove(executor);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止所有TaskExecutor
|
||||
*
|
||||
* @return this
|
||||
* @deprecated 作业执行器只是执行给定的定时任务线程,无法强制关闭,可通过deamon线程方式关闭之
|
||||
*/
|
||||
@Deprecated
|
||||
public TaskExecutorManager destroy() {
|
||||
// synchronized (this.executors) {
|
||||
// for (TaskExecutor taskExecutor : executors) {
|
||||
// ThreadUtil.interupt(taskExecutor, false);
|
||||
// }
|
||||
// }
|
||||
this.executors.clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
package cn.hutool.cron;
|
||||
|
||||
/**
|
||||
* 作业启动器<br>
|
||||
* 负责检查<strong>TaskTable</strong>是否有匹配到此时运行的Task<br>
|
||||
* 检查完毕后启动器结束
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class TaskLauncher implements Runnable{
|
||||
|
||||
private final Scheduler scheduler;
|
||||
private final long millis;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param scheduler {@link Scheduler}
|
||||
* @param millis 毫秒数
|
||||
*/
|
||||
public TaskLauncher(Scheduler scheduler, long millis) {
|
||||
this.scheduler = scheduler;
|
||||
this.millis = millis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//匹配秒部分由用户定义决定,始终不匹配年
|
||||
scheduler.taskTable.executeTaskIfMatch(this.scheduler, this.millis);
|
||||
|
||||
//结束通知
|
||||
scheduler.taskLauncherManager.notifyLauncherCompleted(this);
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron;
|
||||
|
||||
/**
|
||||
* 作业启动器<br>
|
||||
* 负责检查<strong>TaskTable</strong>是否有匹配到此时运行的Task<br>
|
||||
* 检查完毕后启动器结束
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class TaskLauncher implements Runnable{
|
||||
|
||||
private final Scheduler scheduler;
|
||||
private final long millis;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param scheduler {@link Scheduler}
|
||||
* @param millis 毫秒数
|
||||
*/
|
||||
public TaskLauncher(Scheduler scheduler, long millis) {
|
||||
this.scheduler = scheduler;
|
||||
this.millis = millis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//匹配秒部分由用户定义决定,始终不匹配年
|
||||
scheduler.taskTable.executeTaskIfMatch(this.scheduler, this.millis);
|
||||
|
||||
//结束通知
|
||||
scheduler.taskLauncherManager.notifyLauncherCompleted(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
package cn.hutool.cron;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 作业启动管理器
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class TaskLauncherManager implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected Scheduler scheduler;
|
||||
/** 启动器列表 */
|
||||
protected final List<TaskLauncher> launchers = new ArrayList<>();
|
||||
|
||||
public TaskLauncherManager(Scheduler scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动 TaskLauncher
|
||||
* @param millis 触发事件的毫秒数
|
||||
* @return {@link TaskLauncher}
|
||||
*/
|
||||
protected TaskLauncher spawnLauncher(long millis) {
|
||||
final TaskLauncher launcher = new TaskLauncher(this.scheduler, millis);
|
||||
synchronized (this.launchers) {
|
||||
this.launchers.add(launcher);
|
||||
}
|
||||
//子线程是否为deamon线程取决于父线程,因此此处无需显示调用
|
||||
//launcher.setDaemon(this.scheduler.daemon);
|
||||
// launcher.start();
|
||||
this.scheduler.threadExecutor.execute(launcher);
|
||||
return launcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动器启动完毕,启动完毕后从执行器列表中移除
|
||||
* @param launcher 启动器 {@link TaskLauncher}
|
||||
*/
|
||||
protected void notifyLauncherCompleted(TaskLauncher launcher) {
|
||||
synchronized (launchers) {
|
||||
launchers.remove(launcher);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止所有TaskLauncher
|
||||
* @return this
|
||||
* @deprecated 作业启动器只是调用定时任务检查,无法强制关闭,可通过deamon线程方式关闭之
|
||||
*/
|
||||
@Deprecated
|
||||
public TaskLauncherManager destroy() {
|
||||
// synchronized (this.launchers) {
|
||||
// for (TaskLauncher taskLauncher : launchers) {
|
||||
// ThreadUtil.interupt(taskLauncher, false);
|
||||
// }
|
||||
// }
|
||||
this.launchers.clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 作业启动管理器
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class TaskLauncherManager implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected Scheduler scheduler;
|
||||
/** 启动器列表 */
|
||||
protected final List<TaskLauncher> launchers = new ArrayList<>();
|
||||
|
||||
public TaskLauncherManager(Scheduler scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动 TaskLauncher
|
||||
* @param millis 触发事件的毫秒数
|
||||
* @return {@link TaskLauncher}
|
||||
*/
|
||||
protected TaskLauncher spawnLauncher(long millis) {
|
||||
final TaskLauncher launcher = new TaskLauncher(this.scheduler, millis);
|
||||
synchronized (this.launchers) {
|
||||
this.launchers.add(launcher);
|
||||
}
|
||||
//子线程是否为deamon线程取决于父线程,因此此处无需显示调用
|
||||
//launcher.setDaemon(this.scheduler.daemon);
|
||||
// launcher.start();
|
||||
this.scheduler.threadExecutor.execute(launcher);
|
||||
return launcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动器启动完毕,启动完毕后从执行器列表中移除
|
||||
* @param launcher 启动器 {@link TaskLauncher}
|
||||
*/
|
||||
protected void notifyLauncherCompleted(TaskLauncher launcher) {
|
||||
synchronized (launchers) {
|
||||
launchers.remove(launcher);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止所有TaskLauncher
|
||||
* @return this
|
||||
* @deprecated 作业启动器只是调用定时任务检查,无法强制关闭,可通过deamon线程方式关闭之
|
||||
*/
|
||||
@Deprecated
|
||||
public TaskLauncherManager destroy() {
|
||||
// synchronized (this.launchers) {
|
||||
// for (TaskLauncher taskLauncher : launchers) {
|
||||
// ThreadUtil.interupt(taskLauncher, false);
|
||||
// }
|
||||
// }
|
||||
this.launchers.clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,285 +1,285 @@
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.cron.pattern.CronPattern;
|
||||
import cn.hutool.cron.task.CronTask;
|
||||
import cn.hutool.cron.task.Task;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* 定时任务表<br>
|
||||
* 任务表将ID、表达式、任务一一对应,定时任务执行过程中,会周期性检查定时任务表中的所有任务表达式匹配情况,从而执行其对应的任务<br>
|
||||
* 任务的添加、移除使用读写锁保证线程安全性
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class TaskTable implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final int DEFAULT_CAPACITY = 10;
|
||||
|
||||
private final ReadWriteLock lock;
|
||||
|
||||
private final List<String> ids;
|
||||
private final List<CronPattern> patterns;
|
||||
private final List<Task> tasks;
|
||||
private int size;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public TaskTable() {
|
||||
this(DEFAULT_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param initialCapacity 容量,即预估的最大任务数
|
||||
*/
|
||||
public TaskTable(int initialCapacity) {
|
||||
lock = new ReentrantReadWriteLock();
|
||||
|
||||
ids = new ArrayList<>(initialCapacity);
|
||||
patterns = new ArrayList<>(initialCapacity);
|
||||
tasks = new ArrayList<>(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task
|
||||
*
|
||||
* @param id ID
|
||||
* @param pattern {@link CronPattern}
|
||||
* @param task {@link Task}
|
||||
* @return this
|
||||
*/
|
||||
public TaskTable add(String id, CronPattern pattern, Task task) {
|
||||
final Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
if (ids.contains(id)) {
|
||||
throw new CronException("Id [{}] has been existed!", id);
|
||||
}
|
||||
ids.add(id);
|
||||
patterns.add(pattern);
|
||||
tasks.add(task);
|
||||
size++;
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有ID,返回不可变列表,即列表不可修改
|
||||
*
|
||||
* @return ID列表
|
||||
* @since 4.6.7
|
||||
*/
|
||||
public List<String> getIds() {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
return Collections.unmodifiableList(this.ids);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有定时任务表达式,返回不可变列表,即列表不可修改
|
||||
*
|
||||
* @return 定时任务表达式列表
|
||||
* @since 4.6.7
|
||||
*/
|
||||
public List<CronPattern> getPatterns() {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
return Collections.unmodifiableList(this.patterns);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有定时任务,返回不可变列表,即列表不可修改
|
||||
*
|
||||
* @return 定时任务列表
|
||||
* @since 4.6.7
|
||||
*/
|
||||
public List<Task> getTasks() {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
return Collections.unmodifiableList(this.tasks);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除Task
|
||||
*
|
||||
* @param id Task的ID
|
||||
*/
|
||||
public void remove(String id) {
|
||||
final Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
final int index = ids.indexOf(id);
|
||||
if (index > -1) {
|
||||
tasks.remove(index);
|
||||
patterns.remove(index);
|
||||
ids.remove(index);
|
||||
size--;
|
||||
}
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新某个Task的定时规则
|
||||
*
|
||||
* @param id Task的ID
|
||||
* @param pattern 新的表达式
|
||||
* @return 是否更新成功,如果id对应的规则不存在则不更新
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public boolean updatePattern(String id, CronPattern pattern) {
|
||||
final Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
final int index = ids.indexOf(id);
|
||||
if (index > -1) {
|
||||
patterns.set(index, pattern);
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定位置的{@link Task}
|
||||
*
|
||||
* @param index 位置
|
||||
* @return {@link Task}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public Task getTask(int index) {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
return tasks.get(index);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定id的{@link Task}
|
||||
*
|
||||
* @param id ID
|
||||
* @return {@link Task}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public Task getTask(String id) {
|
||||
final int index = ids.indexOf(id);
|
||||
if (index > -1) {
|
||||
return getTask(index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定位置的{@link CronPattern}
|
||||
*
|
||||
* @param index 位置
|
||||
* @return {@link CronPattern}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public CronPattern getPattern(int index) {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
return patterns.get(index);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务表大小,加入的任务数
|
||||
*
|
||||
* @return 任务表大小,加入的任务数
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public int size() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务表是否为空
|
||||
*
|
||||
* @return true为空
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.size < 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定id的{@link CronPattern}
|
||||
*
|
||||
* @param id ID
|
||||
* @return {@link CronPattern}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public CronPattern getPattern(String id) {
|
||||
final int index = ids.indexOf(id);
|
||||
if (index > -1) {
|
||||
return getPattern(index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果时间匹配则执行相应的Task,带读锁
|
||||
*
|
||||
* @param scheduler {@link Scheduler}
|
||||
* @param millis 时间毫秒
|
||||
*/
|
||||
public void executeTaskIfMatch(Scheduler scheduler, long millis) {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
executeTaskIfMatchInternal(scheduler, millis);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果时间匹配则执行相应的Task,无锁
|
||||
*
|
||||
* @param scheduler {@link Scheduler}
|
||||
* @param millis 时间毫秒
|
||||
* @since 3.1.1
|
||||
*/
|
||||
protected void executeTaskIfMatchInternal(Scheduler scheduler, long millis) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (patterns.get(i).match(scheduler.config.timezone, millis, scheduler.config.matchSecond)) {
|
||||
scheduler.taskExecutorManager.spawnExecutor(new CronTask(ids.get(i), patterns.get(i), tasks.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron;
|
||||
|
||||
import cn.hutool.cron.pattern.CronPattern;
|
||||
import cn.hutool.cron.task.CronTask;
|
||||
import cn.hutool.cron.task.Task;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* 定时任务表<br>
|
||||
* 任务表将ID、表达式、任务一一对应,定时任务执行过程中,会周期性检查定时任务表中的所有任务表达式匹配情况,从而执行其对应的任务<br>
|
||||
* 任务的添加、移除使用读写锁保证线程安全性
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class TaskTable implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final int DEFAULT_CAPACITY = 10;
|
||||
|
||||
private final ReadWriteLock lock;
|
||||
|
||||
private final List<String> ids;
|
||||
private final List<CronPattern> patterns;
|
||||
private final List<Task> tasks;
|
||||
private int size;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public TaskTable() {
|
||||
this(DEFAULT_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param initialCapacity 容量,即预估的最大任务数
|
||||
*/
|
||||
public TaskTable(int initialCapacity) {
|
||||
lock = new ReentrantReadWriteLock();
|
||||
|
||||
ids = new ArrayList<>(initialCapacity);
|
||||
patterns = new ArrayList<>(initialCapacity);
|
||||
tasks = new ArrayList<>(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Task
|
||||
*
|
||||
* @param id ID
|
||||
* @param pattern {@link CronPattern}
|
||||
* @param task {@link Task}
|
||||
* @return this
|
||||
*/
|
||||
public TaskTable add(String id, CronPattern pattern, Task task) {
|
||||
final Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
if (ids.contains(id)) {
|
||||
throw new CronException("Id [{}] has been existed!", id);
|
||||
}
|
||||
ids.add(id);
|
||||
patterns.add(pattern);
|
||||
tasks.add(task);
|
||||
size++;
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有ID,返回不可变列表,即列表不可修改
|
||||
*
|
||||
* @return ID列表
|
||||
* @since 4.6.7
|
||||
*/
|
||||
public List<String> getIds() {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
return Collections.unmodifiableList(this.ids);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有定时任务表达式,返回不可变列表,即列表不可修改
|
||||
*
|
||||
* @return 定时任务表达式列表
|
||||
* @since 4.6.7
|
||||
*/
|
||||
public List<CronPattern> getPatterns() {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
return Collections.unmodifiableList(this.patterns);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有定时任务,返回不可变列表,即列表不可修改
|
||||
*
|
||||
* @return 定时任务列表
|
||||
* @since 4.6.7
|
||||
*/
|
||||
public List<Task> getTasks() {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
return Collections.unmodifiableList(this.tasks);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除Task
|
||||
*
|
||||
* @param id Task的ID
|
||||
*/
|
||||
public void remove(String id) {
|
||||
final Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
final int index = ids.indexOf(id);
|
||||
if (index > -1) {
|
||||
tasks.remove(index);
|
||||
patterns.remove(index);
|
||||
ids.remove(index);
|
||||
size--;
|
||||
}
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新某个Task的定时规则
|
||||
*
|
||||
* @param id Task的ID
|
||||
* @param pattern 新的表达式
|
||||
* @return 是否更新成功,如果id对应的规则不存在则不更新
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public boolean updatePattern(String id, CronPattern pattern) {
|
||||
final Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
final int index = ids.indexOf(id);
|
||||
if (index > -1) {
|
||||
patterns.set(index, pattern);
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定位置的{@link Task}
|
||||
*
|
||||
* @param index 位置
|
||||
* @return {@link Task}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public Task getTask(int index) {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
return tasks.get(index);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定id的{@link Task}
|
||||
*
|
||||
* @param id ID
|
||||
* @return {@link Task}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public Task getTask(String id) {
|
||||
final int index = ids.indexOf(id);
|
||||
if (index > -1) {
|
||||
return getTask(index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定位置的{@link CronPattern}
|
||||
*
|
||||
* @param index 位置
|
||||
* @return {@link CronPattern}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public CronPattern getPattern(int index) {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
return patterns.get(index);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务表大小,加入的任务数
|
||||
*
|
||||
* @return 任务表大小,加入的任务数
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public int size() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务表是否为空
|
||||
*
|
||||
* @return true为空
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.size < 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定id的{@link CronPattern}
|
||||
*
|
||||
* @param id ID
|
||||
* @return {@link CronPattern}
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public CronPattern getPattern(String id) {
|
||||
final int index = ids.indexOf(id);
|
||||
if (index > -1) {
|
||||
return getPattern(index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果时间匹配则执行相应的Task,带读锁
|
||||
*
|
||||
* @param scheduler {@link Scheduler}
|
||||
* @param millis 时间毫秒
|
||||
*/
|
||||
public void executeTaskIfMatch(Scheduler scheduler, long millis) {
|
||||
final Lock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
executeTaskIfMatchInternal(scheduler, millis);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果时间匹配则执行相应的Task,无锁
|
||||
*
|
||||
* @param scheduler {@link Scheduler}
|
||||
* @param millis 时间毫秒
|
||||
* @since 3.1.1
|
||||
*/
|
||||
protected void executeTaskIfMatchInternal(Scheduler scheduler, long millis) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (patterns.get(i).match(scheduler.config.timezone, millis, scheduler.config.matchSecond)) {
|
||||
scheduler.taskExecutorManager.spawnExecutor(new CronTask(ids.get(i), patterns.get(i), tasks.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
package cn.hutool.cron.listener;
|
||||
|
||||
import cn.hutool.cron.TaskExecutor;
|
||||
|
||||
/**
|
||||
* 简单监听实现,不做任何操作<br>
|
||||
* 继承此监听后实现需要的方法即可
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class SimpleTaskListener implements TaskListener{
|
||||
|
||||
@Override
|
||||
public void onStart(TaskExecutor executor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSucceeded(TaskExecutor executor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(TaskExecutor executor, Throwable exception) {
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.cron.listener;
|
||||
|
||||
import cn.hutool.cron.TaskExecutor;
|
||||
|
||||
/**
|
||||
* 简单监听实现,不做任何操作<br>
|
||||
* 继承此监听后实现需要的方法即可
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class SimpleTaskListener implements TaskListener{
|
||||
|
||||
@Override
|
||||
public void onStart(TaskExecutor executor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSucceeded(TaskExecutor executor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(TaskExecutor executor, Throwable exception) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
package cn.hutool.cron.listener;
|
||||
|
||||
import cn.hutool.cron.TaskExecutor;
|
||||
|
||||
/**
|
||||
* 定时任务监听接口<br>
|
||||
* 通过实现此接口,实现对定时任务的各个环节做监听
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public interface TaskListener {
|
||||
/**
|
||||
* 定时任务启动时触发
|
||||
* @param executor {@link TaskExecutor}
|
||||
*/
|
||||
void onStart(TaskExecutor executor);
|
||||
|
||||
/**
|
||||
* 任务成功结束时触发
|
||||
*
|
||||
* @param executor {@link TaskExecutor}
|
||||
*/
|
||||
void onSucceeded(TaskExecutor executor);
|
||||
|
||||
/**
|
||||
* 任务启动失败时触发
|
||||
*
|
||||
* @param executor {@link TaskExecutor}
|
||||
* @param exception 异常
|
||||
*/
|
||||
void onFailed(TaskExecutor executor, Throwable exception);
|
||||
}
|
||||
package cn.hutool.cron.listener;
|
||||
|
||||
import cn.hutool.cron.TaskExecutor;
|
||||
|
||||
/**
|
||||
* 定时任务监听接口<br>
|
||||
* 通过实现此接口,实现对定时任务的各个环节做监听
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public interface TaskListener {
|
||||
/**
|
||||
* 定时任务启动时触发
|
||||
* @param executor {@link TaskExecutor}
|
||||
*/
|
||||
void onStart(TaskExecutor executor);
|
||||
|
||||
/**
|
||||
* 任务成功结束时触发
|
||||
*
|
||||
* @param executor {@link TaskExecutor}
|
||||
*/
|
||||
void onSucceeded(TaskExecutor executor);
|
||||
|
||||
/**
|
||||
* 任务启动失败时触发
|
||||
*
|
||||
* @param executor {@link TaskExecutor}
|
||||
* @param exception 异常
|
||||
*/
|
||||
void onFailed(TaskExecutor executor, Throwable exception);
|
||||
}
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
package cn.hutool.cron.listener;
|
||||
|
||||
import cn.hutool.cron.TaskExecutor;
|
||||
import cn.hutool.log.StaticLog;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 监听调度器,统一管理监听
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class TaskListenerManager implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final List<TaskListener> listeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 增加监听器
|
||||
* @param listener {@link TaskListener}
|
||||
* @return this
|
||||
*/
|
||||
public TaskListenerManager addListener(TaskListener listener){
|
||||
synchronized (listeners) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听器
|
||||
* @param listener {@link TaskListener}
|
||||
* @return this
|
||||
*/
|
||||
public TaskListenerManager removeListener(TaskListener listener){
|
||||
synchronized (listeners) {
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有监听任务启动器启动
|
||||
* @param executor {@link TaskExecutor}
|
||||
*/
|
||||
public void notifyTaskStart(TaskExecutor executor) {
|
||||
synchronized (listeners) {
|
||||
int size = listeners.size();
|
||||
TaskListener listener;
|
||||
for (TaskListener taskListener : listeners) {
|
||||
listener = taskListener;
|
||||
if (null != listener) {
|
||||
listener.onStart(executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有监听任务启动器成功结束
|
||||
* @param executor {@link TaskExecutor}
|
||||
*/
|
||||
public void notifyTaskSucceeded(TaskExecutor executor) {
|
||||
synchronized (listeners) {
|
||||
int size = listeners.size();
|
||||
for (TaskListener listener : listeners) {
|
||||
listener.onSucceeded(executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有监听任务启动器结束并失败<br>
|
||||
* 无监听将打印堆栈到命令行
|
||||
* @param executor {@link TaskExecutor}
|
||||
* @param exception 失败原因
|
||||
*/
|
||||
public void notifyTaskFailed(TaskExecutor executor, Throwable exception) {
|
||||
synchronized (listeners) {
|
||||
int size = listeners.size();
|
||||
if(size > 0){
|
||||
for (TaskListener listenerl : listeners) {
|
||||
listenerl.onFailed(executor, exception);
|
||||
}
|
||||
}else{
|
||||
StaticLog.error(exception, exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.listener;
|
||||
|
||||
import cn.hutool.cron.TaskExecutor;
|
||||
import cn.hutool.log.StaticLog;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 监听调度器,统一管理监听
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class TaskListenerManager implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final List<TaskListener> listeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 增加监听器
|
||||
* @param listener {@link TaskListener}
|
||||
* @return this
|
||||
*/
|
||||
public TaskListenerManager addListener(TaskListener listener){
|
||||
synchronized (listeners) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听器
|
||||
* @param listener {@link TaskListener}
|
||||
* @return this
|
||||
*/
|
||||
public TaskListenerManager removeListener(TaskListener listener){
|
||||
synchronized (listeners) {
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有监听任务启动器启动
|
||||
* @param executor {@link TaskExecutor}
|
||||
*/
|
||||
public void notifyTaskStart(TaskExecutor executor) {
|
||||
synchronized (listeners) {
|
||||
int size = listeners.size();
|
||||
TaskListener listener;
|
||||
for (TaskListener taskListener : listeners) {
|
||||
listener = taskListener;
|
||||
if (null != listener) {
|
||||
listener.onStart(executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有监听任务启动器成功结束
|
||||
* @param executor {@link TaskExecutor}
|
||||
*/
|
||||
public void notifyTaskSucceeded(TaskExecutor executor) {
|
||||
synchronized (listeners) {
|
||||
int size = listeners.size();
|
||||
for (TaskListener listener : listeners) {
|
||||
listener.onSucceeded(executor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有监听任务启动器结束并失败<br>
|
||||
* 无监听将打印堆栈到命令行
|
||||
* @param executor {@link TaskExecutor}
|
||||
* @param exception 失败原因
|
||||
*/
|
||||
public void notifyTaskFailed(TaskExecutor executor, Throwable exception) {
|
||||
synchronized (listeners) {
|
||||
int size = listeners.size();
|
||||
if(size > 0){
|
||||
for (TaskListener listenerl : listeners) {
|
||||
listenerl.onFailed(executor, exception);
|
||||
}
|
||||
}else{
|
||||
StaticLog.error(exception, exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 定时任务执行监听接口及部分实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 定时任务执行监听接口及部分实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cron.listener;
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 定时任务模块,提供类Crontab表达式的定时任务,实现参考了Cron4j,同时可以支持秒级别的定时任务定义和年的定义(同时兼容Crontab、Cron4j、Quartz表达式)
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 定时任务模块,提供类Crontab表达式的定时任务,实现参考了Cron4j,同时可以支持秒级别的定时任务定义和年的定义(同时兼容Crontab、Cron4j、Quartz表达式)
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cron;
|
||||
@@ -1,294 +1,294 @@
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.AlwaysTrueValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.DayOfMonthValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcherBuilder;
|
||||
import cn.hutool.cron.pattern.parser.DayOfMonthValueParser;
|
||||
import cn.hutool.cron.pattern.parser.DayOfWeekValueParser;
|
||||
import cn.hutool.cron.pattern.parser.HourValueParser;
|
||||
import cn.hutool.cron.pattern.parser.MinuteValueParser;
|
||||
import cn.hutool.cron.pattern.parser.MonthValueParser;
|
||||
import cn.hutool.cron.pattern.parser.SecondValueParser;
|
||||
import cn.hutool.cron.pattern.parser.ValueParser;
|
||||
import cn.hutool.cron.pattern.parser.YearValueParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* 定时任务表达式<br>
|
||||
* 表达式类似于Linux的crontab表达式,表达式使用空格分成5个部分,按顺序依次为:
|
||||
* <ol>
|
||||
* <li><strong>分</strong>:范围:0~59</li>
|
||||
* <li><strong>时</strong>:范围:0~23</li>
|
||||
* <li><strong>日</strong>:范围:1~31,<strong>"L"</strong>表示月的最后一天</li>
|
||||
* <li><strong>月</strong>:范围:1~12,同时支持不区分大小写的别名:"jan","feb", "mar", "apr", "may","jun", "jul", "aug", "sep","oct", "nov", "dec"</li>
|
||||
* <li><strong>周</strong>:范围:0 (Sunday)~6(Saturday),7也可以表示周日,同时支持不区分大小写的别名:"sun","mon", "tue", "wed", "thu","fri", "sat",<strong>"L"</strong>表示周六</li>
|
||||
* </ol>
|
||||
*
|
||||
* 为了兼容Quartz表达式,同时支持6位和7位表达式,其中:<br>
|
||||
*
|
||||
* <pre>
|
||||
* 当为6位时,第一位表示<strong>秒</strong>,范围0~59,但是第一位不做匹配
|
||||
* 当为7位时,最后一位表示<strong>年</strong>,范围1970~2099,但是第7位不做解析,也不做匹配
|
||||
* </pre>
|
||||
*
|
||||
* 当定时任务运行到的时间匹配这些表达式后,任务被启动。<br>
|
||||
* 注意:
|
||||
*
|
||||
* <pre>
|
||||
* 当isMatchSecond为<code>true</code>时才会匹配秒部分
|
||||
* 默认都是关闭的
|
||||
* </pre>
|
||||
*
|
||||
* 对于每一个子表达式,同样支持以下形式:
|
||||
* <ul>
|
||||
* <li><strong>*</strong>:表示匹配这个位置所有的时间</li>
|
||||
* <li><strong>?</strong>:表示匹配这个位置任意的时间(与"*"作用一致)</li>
|
||||
* <li><strong>*/2</strong>:表示间隔时间,例如在分上,表示每两分钟,同样*可以使用数字列表代替,逗号分隔</li>
|
||||
* <li><strong>2-8</strong>:表示连续区间,例如在分上,表示2,3,4,5,6,7,8分</li>
|
||||
* <li><strong>2,3,5,8</strong>:表示列表</li>
|
||||
* <li><strong>cronA | cronB</strong>:表示多个定时表达式</li>
|
||||
* </ul>
|
||||
* 注意:在每一个子表达式中优先级:
|
||||
*
|
||||
* <pre>
|
||||
* 间隔(/) > 区间(-) > 列表(,)
|
||||
* </pre>
|
||||
*
|
||||
* 例如 2,3,6/3中,由于“/”优先级高,因此相当于2,3,(6/3),结果与 2,3,6等价<br>
|
||||
* <br>
|
||||
*
|
||||
* 一些例子:
|
||||
* <ul>
|
||||
* <li><strong>5 * * * *</strong>:每个点钟的5分执行,00:05,01:05……</li>
|
||||
* <li><strong>* * * * *</strong>:每分钟执行</li>
|
||||
* <li><strong>*/2 * * * *</strong>:每两分钟执行</li>
|
||||
* <li><strong>* 12 * * *</strong>:12点的每分钟执行</li>
|
||||
* <li><strong>59 11 * * 1,2</strong>:每周一和周二的11:59执行</li>
|
||||
* <li><strong>3-18/5 * * * *</strong>:3~18分,每5分钟执行一次,即0:03, 0:08, 0:13, 0:18, 1:03, 1:08……</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class CronPattern {
|
||||
|
||||
private static final ValueParser SECOND_VALUE_PARSER = new SecondValueParser();
|
||||
private static final ValueParser MINUTE_VALUE_PARSER = new MinuteValueParser();
|
||||
private static final ValueParser HOUR_VALUE_PARSER = new HourValueParser();
|
||||
private static final ValueParser DAY_OF_MONTH_VALUE_PARSER = new DayOfMonthValueParser();
|
||||
private static final ValueParser MONTH_VALUE_PARSER = new MonthValueParser();
|
||||
private static final ValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser();
|
||||
private static final ValueParser YEAR_VALUE_PARSER = new YearValueParser();
|
||||
|
||||
private final String pattern;
|
||||
|
||||
/** 秒字段匹配列表 */
|
||||
private final List<ValueMatcher> secondMatchers = new ArrayList<>();
|
||||
/** 分字段匹配列表 */
|
||||
private final List<ValueMatcher> minuteMatchers = new ArrayList<>();
|
||||
/** 时字段匹配列表 */
|
||||
private final List<ValueMatcher> hourMatchers = new ArrayList<>();
|
||||
/** 每月几号字段匹配列表 */
|
||||
private final List<ValueMatcher> dayOfMonthMatchers = new ArrayList<>();
|
||||
/** 月字段匹配列表 */
|
||||
private final List<ValueMatcher> monthMatchers = new ArrayList<>();
|
||||
/** 星期字段匹配列表 */
|
||||
private final List<ValueMatcher> dayOfWeekMatchers = new ArrayList<>();
|
||||
/** 年字段匹配列表 */
|
||||
private final List<ValueMatcher> yearMatchers = new ArrayList<>();
|
||||
/** 匹配器个数,取决于复合任务表达式中的单一表达式个数 */
|
||||
private int matcherSize;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param pattern 表达式
|
||||
*/
|
||||
public CronPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
parseGroupPattern(pattern);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------- match start
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
* @param millis 时间毫秒数
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 如果匹配返回 <code>true</code>, 否则返回 <code>false</code>
|
||||
*/
|
||||
public boolean match(long millis, boolean isMatchSecond) {
|
||||
return match(TimeZone.getDefault(), millis, isMatchSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
* @param timezone 时区 {@link TimeZone}
|
||||
* @param millis 时间毫秒数
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 如果匹配返回 <code>true</code>, 否则返回 <code>false</code>
|
||||
*/
|
||||
public boolean match(TimeZone timezone, long millis, boolean isMatchSecond) {
|
||||
final GregorianCalendar calendar = new GregorianCalendar(timezone);
|
||||
calendar.setTimeInMillis(millis);
|
||||
return match(calendar, isMatchSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
* @param calendar 时间
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 如果匹配返回 <code>true</code>, 否则返回 <code>false</code>
|
||||
*/
|
||||
public boolean match(GregorianCalendar calendar, boolean isMatchSecond) {
|
||||
final int second = calendar.get(Calendar.SECOND);
|
||||
final int minute = calendar.get(Calendar.MINUTE);
|
||||
final int hour = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
|
||||
final int month = calendar.get(Calendar.MONTH) + 1;// 月份从1开始
|
||||
final int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 星期从0开始,0和7都表示周日
|
||||
final int year = calendar.get(Calendar.YEAR);
|
||||
|
||||
boolean eval;
|
||||
for (int i = 0; i < matcherSize; i++) {
|
||||
eval = ((false == isMatchSecond) || secondMatchers.get(i).match(second)) // 匹配秒(非秒匹配模式下始终返回true)
|
||||
&& minuteMatchers.get(i).match(minute)// 匹配分
|
||||
&& hourMatchers.get(i).match(hour)// 匹配时
|
||||
&& isMatchDayOfMonth(dayOfMonthMatchers.get(i), dayOfMonth, month, calendar.isLeapYear(year))// 匹配日
|
||||
&& monthMatchers.get(i).match(month) // 匹配月
|
||||
&& dayOfWeekMatchers.get(i).match(dayOfWeek)// 匹配周
|
||||
&& isMatch(yearMatchers, i, year);// 匹配年
|
||||
if (eval) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// --------------------------------------------------------------------------------------- match end
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 是否匹配日(指定月份的第几天)
|
||||
*
|
||||
* @param matcher {@link ValueMatcher}
|
||||
* @param dayOfMonth 日
|
||||
* @param month 月
|
||||
* @param isLeapYear 是否闰年
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private static boolean isMatchDayOfMonth(ValueMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) {
|
||||
return ((matcher instanceof DayOfMonthValueMatcher) //
|
||||
? ((DayOfMonthValueMatcher) matcher).match(dayOfMonth, month, isLeapYear) //
|
||||
: matcher.match(dayOfMonth));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否匹配指定的日期时间位置
|
||||
*
|
||||
* @param matchers 匹配器列表
|
||||
* @param index 位置
|
||||
* @param value 被匹配的值
|
||||
* @return 是否匹配
|
||||
* @since 4.0.2
|
||||
*/
|
||||
private static boolean isMatch(List<ValueMatcher> matchers, int index, int value) {
|
||||
return (matchers.size() <= index) || matchers.get(index).match(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析复合任务表达式
|
||||
*
|
||||
* @param groupPattern 复合表达式
|
||||
*/
|
||||
private void parseGroupPattern(String groupPattern) {
|
||||
List<String> patternList = StrUtil.split(groupPattern, '|');
|
||||
for (String pattern : patternList) {
|
||||
parseSinglePattern(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单一定时任务表达式
|
||||
*
|
||||
* @param pattern 表达式
|
||||
*/
|
||||
private void parseSinglePattern(String pattern) {
|
||||
final String[] parts = pattern.split("\\s");
|
||||
|
||||
int offset = 0;// 偏移量用于兼容Quartz表达式,当表达式有6或7项时,第一项为秒
|
||||
if (parts.length == 6 || parts.length == 7) {
|
||||
offset = 1;
|
||||
} else if (parts.length != 5) {
|
||||
throw new CronException("Pattern [{}] is invalid, it must be 5-7 parts!", pattern);
|
||||
}
|
||||
|
||||
// 秒
|
||||
if (1 == offset) {// 支持秒的表达式
|
||||
try {
|
||||
this.secondMatchers.add(ValueMatcherBuilder.build(parts[0], SECOND_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'second' field error!", pattern);
|
||||
}
|
||||
} else {// 不支持秒的表达式,则第一位按照表达式生成时间的秒数赋值,表示整分匹配
|
||||
this.secondMatchers.add(ValueMatcherBuilder.build(String.valueOf(DateUtil.date().second()), SECOND_VALUE_PARSER));
|
||||
}
|
||||
// 分
|
||||
try {
|
||||
this.minuteMatchers.add(ValueMatcherBuilder.build(parts[offset], MINUTE_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'minute' field error!", pattern);
|
||||
}
|
||||
// 小时
|
||||
try {
|
||||
this.hourMatchers.add(ValueMatcherBuilder.build(parts[1 + offset], HOUR_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'hour' field error!", pattern);
|
||||
}
|
||||
// 每月第几天
|
||||
try {
|
||||
this.dayOfMonthMatchers.add(ValueMatcherBuilder.build(parts[2 + offset], DAY_OF_MONTH_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'day of month' field error!", pattern);
|
||||
}
|
||||
// 月
|
||||
try {
|
||||
this.monthMatchers.add(ValueMatcherBuilder.build(parts[3 + offset], MONTH_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'month' field error!", pattern);
|
||||
}
|
||||
// 星期几
|
||||
try {
|
||||
this.dayOfWeekMatchers.add(ValueMatcherBuilder.build(parts[4 + offset], DAY_OF_WEEK_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'day of week' field error!", pattern);
|
||||
}
|
||||
// 年
|
||||
if (parts.length == 7) {// 支持年的表达式
|
||||
try {
|
||||
this.yearMatchers.add(ValueMatcherBuilder.build(parts[6], YEAR_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'year' field error!", pattern);
|
||||
}
|
||||
} else {// 不支持年的表达式,全部匹配
|
||||
this.yearMatchers.add(new AlwaysTrueValueMatcher());
|
||||
}
|
||||
matcherSize++;
|
||||
}
|
||||
// -------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.AlwaysTrueValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.DayOfMonthValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcherBuilder;
|
||||
import cn.hutool.cron.pattern.parser.DayOfMonthValueParser;
|
||||
import cn.hutool.cron.pattern.parser.DayOfWeekValueParser;
|
||||
import cn.hutool.cron.pattern.parser.HourValueParser;
|
||||
import cn.hutool.cron.pattern.parser.MinuteValueParser;
|
||||
import cn.hutool.cron.pattern.parser.MonthValueParser;
|
||||
import cn.hutool.cron.pattern.parser.SecondValueParser;
|
||||
import cn.hutool.cron.pattern.parser.ValueParser;
|
||||
import cn.hutool.cron.pattern.parser.YearValueParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* 定时任务表达式<br>
|
||||
* 表达式类似于Linux的crontab表达式,表达式使用空格分成5个部分,按顺序依次为:
|
||||
* <ol>
|
||||
* <li><strong>分</strong>:范围:0~59</li>
|
||||
* <li><strong>时</strong>:范围:0~23</li>
|
||||
* <li><strong>日</strong>:范围:1~31,<strong>"L"</strong>表示月的最后一天</li>
|
||||
* <li><strong>月</strong>:范围:1~12,同时支持不区分大小写的别名:"jan","feb", "mar", "apr", "may","jun", "jul", "aug", "sep","oct", "nov", "dec"</li>
|
||||
* <li><strong>周</strong>:范围:0 (Sunday)~6(Saturday),7也可以表示周日,同时支持不区分大小写的别名:"sun","mon", "tue", "wed", "thu","fri", "sat",<strong>"L"</strong>表示周六</li>
|
||||
* </ol>
|
||||
*
|
||||
* 为了兼容Quartz表达式,同时支持6位和7位表达式,其中:<br>
|
||||
*
|
||||
* <pre>
|
||||
* 当为6位时,第一位表示<strong>秒</strong>,范围0~59,但是第一位不做匹配
|
||||
* 当为7位时,最后一位表示<strong>年</strong>,范围1970~2099,但是第7位不做解析,也不做匹配
|
||||
* </pre>
|
||||
*
|
||||
* 当定时任务运行到的时间匹配这些表达式后,任务被启动。<br>
|
||||
* 注意:
|
||||
*
|
||||
* <pre>
|
||||
* 当isMatchSecond为<code>true</code>时才会匹配秒部分
|
||||
* 默认都是关闭的
|
||||
* </pre>
|
||||
*
|
||||
* 对于每一个子表达式,同样支持以下形式:
|
||||
* <ul>
|
||||
* <li><strong>*</strong>:表示匹配这个位置所有的时间</li>
|
||||
* <li><strong>?</strong>:表示匹配这个位置任意的时间(与"*"作用一致)</li>
|
||||
* <li><strong>*/2</strong>:表示间隔时间,例如在分上,表示每两分钟,同样*可以使用数字列表代替,逗号分隔</li>
|
||||
* <li><strong>2-8</strong>:表示连续区间,例如在分上,表示2,3,4,5,6,7,8分</li>
|
||||
* <li><strong>2,3,5,8</strong>:表示列表</li>
|
||||
* <li><strong>cronA | cronB</strong>:表示多个定时表达式</li>
|
||||
* </ul>
|
||||
* 注意:在每一个子表达式中优先级:
|
||||
*
|
||||
* <pre>
|
||||
* 间隔(/) > 区间(-) > 列表(,)
|
||||
* </pre>
|
||||
*
|
||||
* 例如 2,3,6/3中,由于“/”优先级高,因此相当于2,3,(6/3),结果与 2,3,6等价<br>
|
||||
* <br>
|
||||
*
|
||||
* 一些例子:
|
||||
* <ul>
|
||||
* <li><strong>5 * * * *</strong>:每个点钟的5分执行,00:05,01:05……</li>
|
||||
* <li><strong>* * * * *</strong>:每分钟执行</li>
|
||||
* <li><strong>*/2 * * * *</strong>:每两分钟执行</li>
|
||||
* <li><strong>* 12 * * *</strong>:12点的每分钟执行</li>
|
||||
* <li><strong>59 11 * * 1,2</strong>:每周一和周二的11:59执行</li>
|
||||
* <li><strong>3-18/5 * * * *</strong>:3~18分,每5分钟执行一次,即0:03, 0:08, 0:13, 0:18, 1:03, 1:08……</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class CronPattern {
|
||||
|
||||
private static final ValueParser SECOND_VALUE_PARSER = new SecondValueParser();
|
||||
private static final ValueParser MINUTE_VALUE_PARSER = new MinuteValueParser();
|
||||
private static final ValueParser HOUR_VALUE_PARSER = new HourValueParser();
|
||||
private static final ValueParser DAY_OF_MONTH_VALUE_PARSER = new DayOfMonthValueParser();
|
||||
private static final ValueParser MONTH_VALUE_PARSER = new MonthValueParser();
|
||||
private static final ValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser();
|
||||
private static final ValueParser YEAR_VALUE_PARSER = new YearValueParser();
|
||||
|
||||
private final String pattern;
|
||||
|
||||
/** 秒字段匹配列表 */
|
||||
private final List<ValueMatcher> secondMatchers = new ArrayList<>();
|
||||
/** 分字段匹配列表 */
|
||||
private final List<ValueMatcher> minuteMatchers = new ArrayList<>();
|
||||
/** 时字段匹配列表 */
|
||||
private final List<ValueMatcher> hourMatchers = new ArrayList<>();
|
||||
/** 每月几号字段匹配列表 */
|
||||
private final List<ValueMatcher> dayOfMonthMatchers = new ArrayList<>();
|
||||
/** 月字段匹配列表 */
|
||||
private final List<ValueMatcher> monthMatchers = new ArrayList<>();
|
||||
/** 星期字段匹配列表 */
|
||||
private final List<ValueMatcher> dayOfWeekMatchers = new ArrayList<>();
|
||||
/** 年字段匹配列表 */
|
||||
private final List<ValueMatcher> yearMatchers = new ArrayList<>();
|
||||
/** 匹配器个数,取决于复合任务表达式中的单一表达式个数 */
|
||||
private int matcherSize;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param pattern 表达式
|
||||
*/
|
||||
public CronPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
parseGroupPattern(pattern);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------- match start
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
* @param millis 时间毫秒数
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 如果匹配返回 <code>true</code>, 否则返回 <code>false</code>
|
||||
*/
|
||||
public boolean match(long millis, boolean isMatchSecond) {
|
||||
return match(TimeZone.getDefault(), millis, isMatchSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
* @param timezone 时区 {@link TimeZone}
|
||||
* @param millis 时间毫秒数
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 如果匹配返回 <code>true</code>, 否则返回 <code>false</code>
|
||||
*/
|
||||
public boolean match(TimeZone timezone, long millis, boolean isMatchSecond) {
|
||||
final GregorianCalendar calendar = new GregorianCalendar(timezone);
|
||||
calendar.setTimeInMillis(millis);
|
||||
return match(calendar, isMatchSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
* @param calendar 时间
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 如果匹配返回 <code>true</code>, 否则返回 <code>false</code>
|
||||
*/
|
||||
public boolean match(GregorianCalendar calendar, boolean isMatchSecond) {
|
||||
final int second = calendar.get(Calendar.SECOND);
|
||||
final int minute = calendar.get(Calendar.MINUTE);
|
||||
final int hour = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
|
||||
final int month = calendar.get(Calendar.MONTH) + 1;// 月份从1开始
|
||||
final int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 星期从0开始,0和7都表示周日
|
||||
final int year = calendar.get(Calendar.YEAR);
|
||||
|
||||
boolean eval;
|
||||
for (int i = 0; i < matcherSize; i++) {
|
||||
eval = ((false == isMatchSecond) || secondMatchers.get(i).match(second)) // 匹配秒(非秒匹配模式下始终返回true)
|
||||
&& minuteMatchers.get(i).match(minute)// 匹配分
|
||||
&& hourMatchers.get(i).match(hour)// 匹配时
|
||||
&& isMatchDayOfMonth(dayOfMonthMatchers.get(i), dayOfMonth, month, calendar.isLeapYear(year))// 匹配日
|
||||
&& monthMatchers.get(i).match(month) // 匹配月
|
||||
&& dayOfWeekMatchers.get(i).match(dayOfWeek)// 匹配周
|
||||
&& isMatch(yearMatchers, i, year);// 匹配年
|
||||
if (eval) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// --------------------------------------------------------------------------------------- match end
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 是否匹配日(指定月份的第几天)
|
||||
*
|
||||
* @param matcher {@link ValueMatcher}
|
||||
* @param dayOfMonth 日
|
||||
* @param month 月
|
||||
* @param isLeapYear 是否闰年
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private static boolean isMatchDayOfMonth(ValueMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) {
|
||||
return ((matcher instanceof DayOfMonthValueMatcher) //
|
||||
? ((DayOfMonthValueMatcher) matcher).match(dayOfMonth, month, isLeapYear) //
|
||||
: matcher.match(dayOfMonth));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否匹配指定的日期时间位置
|
||||
*
|
||||
* @param matchers 匹配器列表
|
||||
* @param index 位置
|
||||
* @param value 被匹配的值
|
||||
* @return 是否匹配
|
||||
* @since 4.0.2
|
||||
*/
|
||||
private static boolean isMatch(List<ValueMatcher> matchers, int index, int value) {
|
||||
return (matchers.size() <= index) || matchers.get(index).match(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析复合任务表达式
|
||||
*
|
||||
* @param groupPattern 复合表达式
|
||||
*/
|
||||
private void parseGroupPattern(String groupPattern) {
|
||||
List<String> patternList = StrUtil.split(groupPattern, '|');
|
||||
for (String pattern : patternList) {
|
||||
parseSinglePattern(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单一定时任务表达式
|
||||
*
|
||||
* @param pattern 表达式
|
||||
*/
|
||||
private void parseSinglePattern(String pattern) {
|
||||
final String[] parts = pattern.split("\\s");
|
||||
|
||||
int offset = 0;// 偏移量用于兼容Quartz表达式,当表达式有6或7项时,第一项为秒
|
||||
if (parts.length == 6 || parts.length == 7) {
|
||||
offset = 1;
|
||||
} else if (parts.length != 5) {
|
||||
throw new CronException("Pattern [{}] is invalid, it must be 5-7 parts!", pattern);
|
||||
}
|
||||
|
||||
// 秒
|
||||
if (1 == offset) {// 支持秒的表达式
|
||||
try {
|
||||
this.secondMatchers.add(ValueMatcherBuilder.build(parts[0], SECOND_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'second' field error!", pattern);
|
||||
}
|
||||
} else {// 不支持秒的表达式,则第一位按照表达式生成时间的秒数赋值,表示整分匹配
|
||||
this.secondMatchers.add(ValueMatcherBuilder.build(String.valueOf(DateUtil.date().second()), SECOND_VALUE_PARSER));
|
||||
}
|
||||
// 分
|
||||
try {
|
||||
this.minuteMatchers.add(ValueMatcherBuilder.build(parts[offset], MINUTE_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'minute' field error!", pattern);
|
||||
}
|
||||
// 小时
|
||||
try {
|
||||
this.hourMatchers.add(ValueMatcherBuilder.build(parts[1 + offset], HOUR_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'hour' field error!", pattern);
|
||||
}
|
||||
// 每月第几天
|
||||
try {
|
||||
this.dayOfMonthMatchers.add(ValueMatcherBuilder.build(parts[2 + offset], DAY_OF_MONTH_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'day of month' field error!", pattern);
|
||||
}
|
||||
// 月
|
||||
try {
|
||||
this.monthMatchers.add(ValueMatcherBuilder.build(parts[3 + offset], MONTH_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'month' field error!", pattern);
|
||||
}
|
||||
// 星期几
|
||||
try {
|
||||
this.dayOfWeekMatchers.add(ValueMatcherBuilder.build(parts[4 + offset], DAY_OF_WEEK_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'day of week' field error!", pattern);
|
||||
}
|
||||
// 年
|
||||
if (parts.length == 7) {// 支持年的表达式
|
||||
try {
|
||||
this.yearMatchers.add(ValueMatcherBuilder.build(parts[6], YEAR_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'year' field error!", pattern);
|
||||
}
|
||||
} else {// 不支持年的表达式,全部匹配
|
||||
this.yearMatchers.add(new AlwaysTrueValueMatcher());
|
||||
}
|
||||
matcherSize++;
|
||||
}
|
||||
// -------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 定时任务表达式工具类
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class CronPatternUtil {
|
||||
|
||||
/**
|
||||
* 列举指定日期之后(到开始日期对应年年底)内第一个匹配表达式的日期
|
||||
*
|
||||
* @param pattern 表达式
|
||||
* @param start 起始时间
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 日期
|
||||
* @since 4.5.8
|
||||
*/
|
||||
public static Date nextDateAfter(CronPattern pattern, Date start, boolean isMatchSecond) {
|
||||
List<Date> matchedDates = matchedDates(pattern, start.getTime(), DateUtil.endOfYear(start).getTime(), 1, isMatchSecond);
|
||||
if (CollUtil.isNotEmpty(matchedDates)) {
|
||||
return matchedDates.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列举指定日期之后(到开始日期对应年年底)内所有匹配表达式的日期
|
||||
*
|
||||
* @param patternStr 表达式字符串
|
||||
* @param start 起始时间
|
||||
* @param count 列举数量
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 日期列表
|
||||
*/
|
||||
public static List<Date> matchedDates(String patternStr, Date start, int count, boolean isMatchSecond) {
|
||||
return matchedDates(patternStr, start, DateUtil.endOfYear(start), count, isMatchSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列举指定日期范围内所有匹配表达式的日期
|
||||
*
|
||||
* @param patternStr 表达式字符串
|
||||
* @param start 起始时间
|
||||
* @param end 结束时间
|
||||
* @param count 列举数量
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 日期列表
|
||||
*/
|
||||
public static List<Date> matchedDates(String patternStr, Date start, Date end, int count, boolean isMatchSecond) {
|
||||
return matchedDates(patternStr, start.getTime(), end.getTime(), count, isMatchSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列举指定日期范围内所有匹配表达式的日期
|
||||
*
|
||||
* @param patternStr 表达式字符串
|
||||
* @param start 起始时间
|
||||
* @param end 结束时间
|
||||
* @param count 列举数量
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 日期列表
|
||||
*/
|
||||
public static List<Date> matchedDates(String patternStr, long start, long end, int count, boolean isMatchSecond) {
|
||||
return matchedDates(new CronPattern(patternStr), start, end, count, isMatchSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列举指定日期范围内所有匹配表达式的日期
|
||||
*
|
||||
* @param pattern 表达式
|
||||
* @param start 起始时间
|
||||
* @param end 结束时间
|
||||
* @param count 列举数量
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 日期列表
|
||||
*/
|
||||
public static List<Date> matchedDates(CronPattern pattern, long start, long end, int count, boolean isMatchSecond) {
|
||||
Assert.isTrue(start < end, "Start date is later than end !");
|
||||
|
||||
final List<Date> result = new ArrayList<>(count);
|
||||
long step = isMatchSecond ? DateUnit.SECOND.getMillis() : DateUnit.MINUTE.getMillis();
|
||||
for (long i = start; i < end; i += step) {
|
||||
if (pattern.match(i, isMatchSecond)) {
|
||||
result.add(DateUtil.date(i));
|
||||
if (result.size() >= count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 定时任务表达式工具类
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class CronPatternUtil {
|
||||
|
||||
/**
|
||||
* 列举指定日期之后(到开始日期对应年年底)内第一个匹配表达式的日期
|
||||
*
|
||||
* @param pattern 表达式
|
||||
* @param start 起始时间
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 日期
|
||||
* @since 4.5.8
|
||||
*/
|
||||
public static Date nextDateAfter(CronPattern pattern, Date start, boolean isMatchSecond) {
|
||||
List<Date> matchedDates = matchedDates(pattern, start.getTime(), DateUtil.endOfYear(start).getTime(), 1, isMatchSecond);
|
||||
if (CollUtil.isNotEmpty(matchedDates)) {
|
||||
return matchedDates.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列举指定日期之后(到开始日期对应年年底)内所有匹配表达式的日期
|
||||
*
|
||||
* @param patternStr 表达式字符串
|
||||
* @param start 起始时间
|
||||
* @param count 列举数量
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 日期列表
|
||||
*/
|
||||
public static List<Date> matchedDates(String patternStr, Date start, int count, boolean isMatchSecond) {
|
||||
return matchedDates(patternStr, start, DateUtil.endOfYear(start), count, isMatchSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列举指定日期范围内所有匹配表达式的日期
|
||||
*
|
||||
* @param patternStr 表达式字符串
|
||||
* @param start 起始时间
|
||||
* @param end 结束时间
|
||||
* @param count 列举数量
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 日期列表
|
||||
*/
|
||||
public static List<Date> matchedDates(String patternStr, Date start, Date end, int count, boolean isMatchSecond) {
|
||||
return matchedDates(patternStr, start.getTime(), end.getTime(), count, isMatchSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列举指定日期范围内所有匹配表达式的日期
|
||||
*
|
||||
* @param patternStr 表达式字符串
|
||||
* @param start 起始时间
|
||||
* @param end 结束时间
|
||||
* @param count 列举数量
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 日期列表
|
||||
*/
|
||||
public static List<Date> matchedDates(String patternStr, long start, long end, int count, boolean isMatchSecond) {
|
||||
return matchedDates(new CronPattern(patternStr), start, end, count, isMatchSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列举指定日期范围内所有匹配表达式的日期
|
||||
*
|
||||
* @param pattern 表达式
|
||||
* @param start 起始时间
|
||||
* @param end 结束时间
|
||||
* @param count 列举数量
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 日期列表
|
||||
*/
|
||||
public static List<Date> matchedDates(CronPattern pattern, long start, long end, int count, boolean isMatchSecond) {
|
||||
Assert.isTrue(start < end, "Start date is later than end !");
|
||||
|
||||
final List<Date> result = new ArrayList<>(count);
|
||||
long step = isMatchSecond ? DateUnit.SECOND.getMillis() : DateUnit.MINUTE.getMillis();
|
||||
for (long i = start; i < end; i += step) {
|
||||
if (pattern.match(i, isMatchSecond)) {
|
||||
result.add(DateUtil.date(i));
|
||||
if (result.size() >= count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 值匹配,始终返回<code>true</code>
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class AlwaysTrueValueMatcher implements ValueMatcher{
|
||||
|
||||
@Override
|
||||
public boolean match(Integer t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StrUtil.format("[Matcher]: always true.");
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 值匹配,始终返回<code>true</code>
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class AlwaysTrueValueMatcher implements ValueMatcher{
|
||||
|
||||
@Override
|
||||
public boolean match(Integer t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StrUtil.format("[Matcher]: always true.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 将表达式中的数字值列表转换为Boolean数组,匹配时匹配相应数组位
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class BoolArrayValueMatcher implements ValueMatcher{
|
||||
|
||||
private final boolean[] bValues;
|
||||
|
||||
public BoolArrayValueMatcher(List<Integer> intValueList) {
|
||||
bValues = new boolean[Collections.max(intValueList) + 1];
|
||||
for (Integer value : intValueList) {
|
||||
bValues[value] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(Integer value) {
|
||||
if(null == value || value >= bValues.length){
|
||||
return false;
|
||||
}
|
||||
return bValues[value];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StrUtil.format("Matcher:{}", new Object[]{this.bValues});
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 将表达式中的数字值列表转换为Boolean数组,匹配时匹配相应数组位
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class BoolArrayValueMatcher implements ValueMatcher{
|
||||
|
||||
private final boolean[] bValues;
|
||||
|
||||
public BoolArrayValueMatcher(List<Integer> intValueList) {
|
||||
bValues = new boolean[Collections.max(intValueList) + 1];
|
||||
for (Integer value : intValueList) {
|
||||
bValues[value] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(Integer value) {
|
||||
if(null == value || value >= bValues.length){
|
||||
return false;
|
||||
}
|
||||
return bValues[value];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StrUtil.format("Matcher:{}", new Object[]{this.bValues});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import cn.hutool.core.date.Month;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 每月第几天匹配<br>
|
||||
* 考虑每月的天数不同,且存在闰年情况,日匹配单独使用
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class DayOfMonthValueMatcher extends BoolArrayValueMatcher {
|
||||
|
||||
private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param intValueList 匹配的日值
|
||||
*/
|
||||
public DayOfMonthValueMatcher(List<Integer> intValueList) {
|
||||
super(intValueList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定的日期是否匹配当前匹配器
|
||||
*
|
||||
* @param value 被检查的值,此处为日
|
||||
* @param month 实际的月份,从1开始
|
||||
* @param isLeapYear 是否闰年
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public boolean match(int value, int month, boolean isLeapYear) {
|
||||
return (super.match(value) // 在约定日范围内的某一天
|
||||
//匹配器中用户定义了最后一天(32表示最后一天)
|
||||
|| (value > 27 && match(32) && isLastDayOfMonth(value, month, isLeapYear)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为本月最后一天,规则如下:
|
||||
* <pre>
|
||||
* 1、闰年2月匹配是否为29
|
||||
* 2、其它月份是否匹配最后一天的日期(可能为30或者31)
|
||||
* </pre>
|
||||
*
|
||||
* @param value 被检查的值
|
||||
* @param month 月份,从1开始
|
||||
* @param isLeapYear 是否闰年
|
||||
* @return 是否为本月最后一天
|
||||
*/
|
||||
private static boolean isLastDayOfMonth(int value, int month, boolean isLeapYear) {
|
||||
return value == Month.getLastDay(month - 1, isLeapYear);
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import cn.hutool.core.date.Month;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 每月第几天匹配<br>
|
||||
* 考虑每月的天数不同,且存在闰年情况,日匹配单独使用
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class DayOfMonthValueMatcher extends BoolArrayValueMatcher {
|
||||
|
||||
private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param intValueList 匹配的日值
|
||||
*/
|
||||
public DayOfMonthValueMatcher(List<Integer> intValueList) {
|
||||
super(intValueList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定的日期是否匹配当前匹配器
|
||||
*
|
||||
* @param value 被检查的值,此处为日
|
||||
* @param month 实际的月份,从1开始
|
||||
* @param isLeapYear 是否闰年
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public boolean match(int value, int month, boolean isLeapYear) {
|
||||
return (super.match(value) // 在约定日范围内的某一天
|
||||
//匹配器中用户定义了最后一天(32表示最后一天)
|
||||
|| (value > 27 && match(32) && isLastDayOfMonth(value, month, isLeapYear)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为本月最后一天,规则如下:
|
||||
* <pre>
|
||||
* 1、闰年2月匹配是否为29
|
||||
* 2、其它月份是否匹配最后一天的日期(可能为30或者31)
|
||||
* </pre>
|
||||
*
|
||||
* @param value 被检查的值
|
||||
* @param month 月份,从1开始
|
||||
* @param isLeapYear 是否闰年
|
||||
* @return 是否为本月最后一天
|
||||
*/
|
||||
private static boolean isLastDayOfMonth(int value, int month, boolean isLeapYear) {
|
||||
return value == Month.getLastDay(month - 1, isLeapYear);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import cn.hutool.core.lang.Matcher;
|
||||
|
||||
/**
|
||||
* 值匹配器<br>
|
||||
* 用于匹配日期位中对应数字是否匹配
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public interface ValueMatcher extends Matcher<Integer>{
|
||||
|
||||
}
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import cn.hutool.core.lang.Matcher;
|
||||
|
||||
/**
|
||||
* 值匹配器<br>
|
||||
* 用于匹配日期位中对应数字是否匹配
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public interface ValueMatcher extends Matcher<Integer>{
|
||||
|
||||
}
|
||||
|
||||
@@ -1,192 +1,192 @@
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.parser.DayOfMonthValueParser;
|
||||
import cn.hutool.cron.pattern.parser.ValueParser;
|
||||
import cn.hutool.cron.pattern.parser.YearValueParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link ValueMatcher} 构建器,用于构建表达式中每一项的匹配器
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class ValueMatcherBuilder {
|
||||
|
||||
/**
|
||||
* 处理定时任务表达式每个时间字段<br>
|
||||
* 多个时间使用逗号分隔
|
||||
*
|
||||
* @param value 某个时间字段
|
||||
* @param parser 针对这个时间字段的解析器
|
||||
* @return List
|
||||
*/
|
||||
public static ValueMatcher build(String value, ValueParser parser) {
|
||||
if (isMatchAllStr(value)) {
|
||||
//兼容Quartz的"?"表达式,不会出现互斥情况,与"*"作用相同
|
||||
return new AlwaysTrueValueMatcher();
|
||||
}
|
||||
|
||||
List<Integer> values = parseArray(value, parser);
|
||||
if (values.size() == 0) {
|
||||
throw new CronException("Invalid field: [{}]", value);
|
||||
}
|
||||
|
||||
if (parser instanceof DayOfMonthValueParser) {
|
||||
//考虑每月的天数不同,且存在闰年情况,日匹配单独使用
|
||||
return new DayOfMonthValueMatcher(values);
|
||||
}else if(parser instanceof YearValueParser){
|
||||
//考虑年数字太大,不适合boolean数组,单独使用列表遍历匹配
|
||||
return new YearValueMatcher(values);
|
||||
}else {
|
||||
return new BoolArrayValueMatcher(values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数组形式表达式<br>
|
||||
* 处理的形式包括:
|
||||
* <ul>
|
||||
* <li><strong>a</strong> 或 <strong>*</strong></li>
|
||||
* <li><strong>a,b,c,d</strong></li>
|
||||
* </ul>
|
||||
* @param value 子表达式值
|
||||
* @param parser 针对这个字段的解析器
|
||||
* @return 值列表
|
||||
*/
|
||||
private static List<Integer> parseArray(String value, ValueParser parser){
|
||||
final List<Integer> values = new ArrayList<>();
|
||||
|
||||
final List<String> parts = StrUtil.split(value, StrUtil.C_COMMA);
|
||||
for (String part : parts) {
|
||||
CollUtil.addAllIfNotContains(values, parseStep(part, parser));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理间隔形式的表达式<br>
|
||||
* 处理的形式包括:
|
||||
* <ul>
|
||||
* <li><strong>a</strong> 或 <strong>*</strong></li>
|
||||
* <li><strong>a/b</strong> 或 <strong>*/b</strong></li>
|
||||
* <li><strong>a-b/2</strong></li>
|
||||
* </ul>
|
||||
*
|
||||
* @param value 表达式值
|
||||
* @param parser 针对这个时间字段的解析器
|
||||
* @return List
|
||||
*/
|
||||
private static List<Integer> parseStep(String value, ValueParser parser) {
|
||||
final List<String> parts = StrUtil.split(value, StrUtil.C_SLASH);
|
||||
int size = parts.size();
|
||||
|
||||
List<Integer> results;
|
||||
if (size == 1) {// 普通形式
|
||||
results = parseRange(value, -1, parser);
|
||||
} else if (size == 2) {// 间隔形式
|
||||
final int step = parser.parse(parts.get(1));
|
||||
if (step < 1) {
|
||||
throw new CronException("Non positive divisor for field: [{}]", value);
|
||||
}
|
||||
results = parseRange(parts.get(0), step, parser);
|
||||
} else {
|
||||
throw new CronException("Invalid syntax of field: [{}]", value);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理表达式中范围表达式 处理的形式包括:
|
||||
* <ul>
|
||||
* <li>*</li>
|
||||
* <li>2</li>
|
||||
* <li>3-8</li>
|
||||
* <li>8-3</li>
|
||||
* <li>3-3</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param value 范围表达式
|
||||
* @param step 步进
|
||||
* @param parser 针对这个时间字段的解析器
|
||||
* @return List
|
||||
*/
|
||||
private static List<Integer> parseRange(String value, int step, ValueParser parser) {
|
||||
final List<Integer> results = new ArrayList<>();
|
||||
|
||||
// 全部匹配形式
|
||||
if (value.length() <= 2) {
|
||||
//根据步进的第一个数字确定起始时间,类似于 12/3则从12(秒、分等)开始
|
||||
int minValue = parser.getMin();
|
||||
if(false == isMatchAllStr(value)) {
|
||||
minValue = Math.max(minValue, parser.parse(value));
|
||||
}else {
|
||||
//在全匹配模式下,如果步进不存在,表示步进为1
|
||||
if(step < 1) {
|
||||
step = 1;
|
||||
}
|
||||
}
|
||||
if(step > 0) {
|
||||
final int maxValue = parser.getMax();
|
||||
if(minValue > maxValue) {
|
||||
throw new CronException("Invalid value {} > {}", minValue, maxValue);
|
||||
}
|
||||
//有步进
|
||||
for (int i = minValue; i <= maxValue; i+=step) {
|
||||
results.add(i);
|
||||
}
|
||||
} else {
|
||||
//固定时间
|
||||
results.add(minValue);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
//Range模式
|
||||
List<String> parts = StrUtil.split(value, '-');
|
||||
int size = parts.size();
|
||||
if (size == 1) {// 普通值
|
||||
final int v1 = parser.parse(value);
|
||||
if(step > 0) {//类似 20/2的形式
|
||||
NumberUtil.appendRange(v1, parser.getMax(), step, results);
|
||||
}else {
|
||||
results.add(v1);
|
||||
}
|
||||
} else if (size == 2) {// range值
|
||||
final int v1 = parser.parse(parts.get(0));
|
||||
final int v2 = parser.parse(parts.get(1));
|
||||
if(step < 1) {
|
||||
//在range模式下,如果步进不存在,表示步进为1
|
||||
step = 1;
|
||||
}
|
||||
if (v1 < v2) {// 正常范围,例如:2-5
|
||||
NumberUtil.appendRange(v1, v2, step, results);
|
||||
} else if (v1 > v2) {// 逆向范围,反选模式,例如:5-2
|
||||
NumberUtil.appendRange(v1, parser.getMax(), step, results);
|
||||
NumberUtil.appendRange(parser.getMin(), v2, step, results);
|
||||
} else {// v1 == v2,此时与单值模式一致
|
||||
NumberUtil.appendRange(v1, parser.getMax(), step, results);
|
||||
}
|
||||
} else {
|
||||
throw new CronException("Invalid syntax of field: [{}]", value);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为全匹配符<br>
|
||||
* 全匹配符指 * 或者 ?
|
||||
*
|
||||
* @param value 被检查的值
|
||||
* @return 是否为全匹配符
|
||||
* @since 4.1.18
|
||||
*/
|
||||
private static boolean isMatchAllStr(String value) {
|
||||
return (1 == value.length()) && ("*".equals(value) || "?".equals(value));
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.parser.DayOfMonthValueParser;
|
||||
import cn.hutool.cron.pattern.parser.ValueParser;
|
||||
import cn.hutool.cron.pattern.parser.YearValueParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link ValueMatcher} 构建器,用于构建表达式中每一项的匹配器
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class ValueMatcherBuilder {
|
||||
|
||||
/**
|
||||
* 处理定时任务表达式每个时间字段<br>
|
||||
* 多个时间使用逗号分隔
|
||||
*
|
||||
* @param value 某个时间字段
|
||||
* @param parser 针对这个时间字段的解析器
|
||||
* @return List
|
||||
*/
|
||||
public static ValueMatcher build(String value, ValueParser parser) {
|
||||
if (isMatchAllStr(value)) {
|
||||
//兼容Quartz的"?"表达式,不会出现互斥情况,与"*"作用相同
|
||||
return new AlwaysTrueValueMatcher();
|
||||
}
|
||||
|
||||
List<Integer> values = parseArray(value, parser);
|
||||
if (values.size() == 0) {
|
||||
throw new CronException("Invalid field: [{}]", value);
|
||||
}
|
||||
|
||||
if (parser instanceof DayOfMonthValueParser) {
|
||||
//考虑每月的天数不同,且存在闰年情况,日匹配单独使用
|
||||
return new DayOfMonthValueMatcher(values);
|
||||
}else if(parser instanceof YearValueParser){
|
||||
//考虑年数字太大,不适合boolean数组,单独使用列表遍历匹配
|
||||
return new YearValueMatcher(values);
|
||||
}else {
|
||||
return new BoolArrayValueMatcher(values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数组形式表达式<br>
|
||||
* 处理的形式包括:
|
||||
* <ul>
|
||||
* <li><strong>a</strong> 或 <strong>*</strong></li>
|
||||
* <li><strong>a,b,c,d</strong></li>
|
||||
* </ul>
|
||||
* @param value 子表达式值
|
||||
* @param parser 针对这个字段的解析器
|
||||
* @return 值列表
|
||||
*/
|
||||
private static List<Integer> parseArray(String value, ValueParser parser){
|
||||
final List<Integer> values = new ArrayList<>();
|
||||
|
||||
final List<String> parts = StrUtil.split(value, StrUtil.C_COMMA);
|
||||
for (String part : parts) {
|
||||
CollUtil.addAllIfNotContains(values, parseStep(part, parser));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理间隔形式的表达式<br>
|
||||
* 处理的形式包括:
|
||||
* <ul>
|
||||
* <li><strong>a</strong> 或 <strong>*</strong></li>
|
||||
* <li><strong>a/b</strong> 或 <strong>*/b</strong></li>
|
||||
* <li><strong>a-b/2</strong></li>
|
||||
* </ul>
|
||||
*
|
||||
* @param value 表达式值
|
||||
* @param parser 针对这个时间字段的解析器
|
||||
* @return List
|
||||
*/
|
||||
private static List<Integer> parseStep(String value, ValueParser parser) {
|
||||
final List<String> parts = StrUtil.split(value, StrUtil.C_SLASH);
|
||||
int size = parts.size();
|
||||
|
||||
List<Integer> results;
|
||||
if (size == 1) {// 普通形式
|
||||
results = parseRange(value, -1, parser);
|
||||
} else if (size == 2) {// 间隔形式
|
||||
final int step = parser.parse(parts.get(1));
|
||||
if (step < 1) {
|
||||
throw new CronException("Non positive divisor for field: [{}]", value);
|
||||
}
|
||||
results = parseRange(parts.get(0), step, parser);
|
||||
} else {
|
||||
throw new CronException("Invalid syntax of field: [{}]", value);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理表达式中范围表达式 处理的形式包括:
|
||||
* <ul>
|
||||
* <li>*</li>
|
||||
* <li>2</li>
|
||||
* <li>3-8</li>
|
||||
* <li>8-3</li>
|
||||
* <li>3-3</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param value 范围表达式
|
||||
* @param step 步进
|
||||
* @param parser 针对这个时间字段的解析器
|
||||
* @return List
|
||||
*/
|
||||
private static List<Integer> parseRange(String value, int step, ValueParser parser) {
|
||||
final List<Integer> results = new ArrayList<>();
|
||||
|
||||
// 全部匹配形式
|
||||
if (value.length() <= 2) {
|
||||
//根据步进的第一个数字确定起始时间,类似于 12/3则从12(秒、分等)开始
|
||||
int minValue = parser.getMin();
|
||||
if(false == isMatchAllStr(value)) {
|
||||
minValue = Math.max(minValue, parser.parse(value));
|
||||
}else {
|
||||
//在全匹配模式下,如果步进不存在,表示步进为1
|
||||
if(step < 1) {
|
||||
step = 1;
|
||||
}
|
||||
}
|
||||
if(step > 0) {
|
||||
final int maxValue = parser.getMax();
|
||||
if(minValue > maxValue) {
|
||||
throw new CronException("Invalid value {} > {}", minValue, maxValue);
|
||||
}
|
||||
//有步进
|
||||
for (int i = minValue; i <= maxValue; i+=step) {
|
||||
results.add(i);
|
||||
}
|
||||
} else {
|
||||
//固定时间
|
||||
results.add(minValue);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
//Range模式
|
||||
List<String> parts = StrUtil.split(value, '-');
|
||||
int size = parts.size();
|
||||
if (size == 1) {// 普通值
|
||||
final int v1 = parser.parse(value);
|
||||
if(step > 0) {//类似 20/2的形式
|
||||
NumberUtil.appendRange(v1, parser.getMax(), step, results);
|
||||
}else {
|
||||
results.add(v1);
|
||||
}
|
||||
} else if (size == 2) {// range值
|
||||
final int v1 = parser.parse(parts.get(0));
|
||||
final int v2 = parser.parse(parts.get(1));
|
||||
if(step < 1) {
|
||||
//在range模式下,如果步进不存在,表示步进为1
|
||||
step = 1;
|
||||
}
|
||||
if (v1 < v2) {// 正常范围,例如:2-5
|
||||
NumberUtil.appendRange(v1, v2, step, results);
|
||||
} else if (v1 > v2) {// 逆向范围,反选模式,例如:5-2
|
||||
NumberUtil.appendRange(v1, parser.getMax(), step, results);
|
||||
NumberUtil.appendRange(parser.getMin(), v2, step, results);
|
||||
} else {// v1 == v2,此时与单值模式一致
|
||||
NumberUtil.appendRange(v1, parser.getMax(), step, results);
|
||||
}
|
||||
} else {
|
||||
throw new CronException("Invalid syntax of field: [{}]", value);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为全匹配符<br>
|
||||
* 全匹配符指 * 或者 ?
|
||||
*
|
||||
* @param value 被检查的值
|
||||
* @return 是否为全匹配符
|
||||
* @since 4.1.18
|
||||
*/
|
||||
private static boolean isMatchAllStr(String value) {
|
||||
return (1 == value.length()) && ("*".equals(value) || "?".equals(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 年匹配<br>
|
||||
* 考虑年数字太大,不适合boolean数组,单独使用列表遍历匹配
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class YearValueMatcher implements ValueMatcher{
|
||||
|
||||
private final List<Integer> valueList;
|
||||
|
||||
public YearValueMatcher(List<Integer> intValueList) {
|
||||
this.valueList = intValueList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(Integer t) {
|
||||
return valueList.contains(t);
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 年匹配<br>
|
||||
* 考虑年数字太大,不适合boolean数组,单独使用列表遍历匹配
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class YearValueMatcher implements ValueMatcher{
|
||||
|
||||
private final List<Integer> valueList;
|
||||
|
||||
public YearValueMatcher(List<Integer> intValueList) {
|
||||
this.valueList = intValueList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(Integer t) {
|
||||
return valueList.contains(t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 定时任务表达式匹配器,内部使用
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 定时任务表达式匹配器,内部使用
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 定时任务表达式解析,核心为CronPattern
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 定时任务表达式解析,核心为CronPattern
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cron.pattern;
|
||||
@@ -1,26 +1,26 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 每月的几号值处理<br>
|
||||
* 每月最多31天,32和“L”都表示最后一天
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class DayOfMonthValueParser extends SimpleValueParser {
|
||||
|
||||
public DayOfMonthValueParser() {
|
||||
super(1, 31);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
if ("L".equalsIgnoreCase(value) || "32".equals(value)) {// 每月最后一天
|
||||
return 32;
|
||||
} else {
|
||||
return super.parse(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 每月的几号值处理<br>
|
||||
* 每月最多31天,32和“L”都表示最后一天
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class DayOfMonthValueParser extends SimpleValueParser {
|
||||
|
||||
public DayOfMonthValueParser() {
|
||||
super(1, 31);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
if ("L".equalsIgnoreCase(value) || "32".equals(value)) {// 每月最后一天
|
||||
return 32;
|
||||
} else {
|
||||
return super.parse(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 星期值处理<br>
|
||||
* 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class DayOfWeekValueParser extends SimpleValueParser {
|
||||
|
||||
/** Weeks aliases. */
|
||||
private static final String[] ALIASES = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
|
||||
|
||||
public DayOfWeekValueParser() {
|
||||
super(0, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于星期提供转换<br>
|
||||
* 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日
|
||||
*/
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
try {
|
||||
return super.parse(value) % 7;
|
||||
} catch (Exception e) {
|
||||
return parseAlias(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析别名
|
||||
* @param value 别名值
|
||||
* @return 月份int值
|
||||
* @throws CronException 无效别名抛出此异常
|
||||
*/
|
||||
private int parseAlias(String value) throws CronException {
|
||||
if("L".equalsIgnoreCase(value)){
|
||||
//最后一天为星期六
|
||||
return ALIASES.length - 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ALIASES.length; i++) {
|
||||
if (ALIASES[i].equalsIgnoreCase(value)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new CronException("Invalid month alias: {}", value);
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 星期值处理<br>
|
||||
* 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class DayOfWeekValueParser extends SimpleValueParser {
|
||||
|
||||
/** Weeks aliases. */
|
||||
private static final String[] ALIASES = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
|
||||
|
||||
public DayOfWeekValueParser() {
|
||||
super(0, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于星期提供转换<br>
|
||||
* 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日
|
||||
*/
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
try {
|
||||
return super.parse(value) % 7;
|
||||
} catch (Exception e) {
|
||||
return parseAlias(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析别名
|
||||
* @param value 别名值
|
||||
* @return 月份int值
|
||||
* @throws CronException 无效别名抛出此异常
|
||||
*/
|
||||
private int parseAlias(String value) throws CronException {
|
||||
if("L".equalsIgnoreCase(value)){
|
||||
//最后一天为星期六
|
||||
return ALIASES.length - 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ALIASES.length; i++) {
|
||||
if (ALIASES[i].equalsIgnoreCase(value)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new CronException("Invalid month alias: {}", value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 小时值处理
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class HourValueParser extends SimpleValueParser{
|
||||
|
||||
public HourValueParser() {
|
||||
super(0, 23);
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 小时值处理
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class HourValueParser extends SimpleValueParser{
|
||||
|
||||
public HourValueParser() {
|
||||
super(0, 23);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 分钟值处理
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class MinuteValueParser extends SimpleValueParser{
|
||||
|
||||
public MinuteValueParser() {
|
||||
super(0, 59);
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 分钟值处理
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class MinuteValueParser extends SimpleValueParser{
|
||||
|
||||
public MinuteValueParser() {
|
||||
super(0, 59);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 月份值处理
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class MonthValueParser extends SimpleValueParser {
|
||||
|
||||
/** Months aliases. */
|
||||
private static final String[] ALIASES = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" };
|
||||
|
||||
public MonthValueParser() {
|
||||
super(1, 12);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
try {
|
||||
return super.parse(value);
|
||||
} catch (Exception e) {
|
||||
return parseAlias(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析别名
|
||||
* @param value 别名值
|
||||
* @return 月份int值
|
||||
* @throws CronException 无效月别名抛出此异常
|
||||
*/
|
||||
private int parseAlias(String value) throws CronException {
|
||||
for (int i = 0; i < ALIASES.length; i++) {
|
||||
if (ALIASES[i].equalsIgnoreCase(value)) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
throw new CronException("Invalid month alias: {}", value);
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 月份值处理
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class MonthValueParser extends SimpleValueParser {
|
||||
|
||||
/** Months aliases. */
|
||||
private static final String[] ALIASES = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" };
|
||||
|
||||
public MonthValueParser() {
|
||||
super(1, 12);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
try {
|
||||
return super.parse(value);
|
||||
} catch (Exception e) {
|
||||
return parseAlias(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析别名
|
||||
* @param value 别名值
|
||||
* @return 月份int值
|
||||
* @throws CronException 无效月别名抛出此异常
|
||||
*/
|
||||
private int parseAlias(String value) throws CronException {
|
||||
for (int i = 0; i < ALIASES.length; i++) {
|
||||
if (ALIASES[i].equalsIgnoreCase(value)) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
throw new CronException("Invalid month alias: {}", value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 秒值处理
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class SecondValueParser extends MinuteValueParser{
|
||||
}
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 秒值处理
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class SecondValueParser extends MinuteValueParser{
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 简易值转换器。将给定String值转为int
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class SimpleValueParser implements ValueParser {
|
||||
|
||||
/** 最小值(包括) */
|
||||
protected int min;
|
||||
/** 最大值(包括) */
|
||||
protected int max;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param min 最小值(包括)
|
||||
* @param max 最大值(包括)
|
||||
*/
|
||||
public SimpleValueParser(int min, int max) {
|
||||
if(min > max){
|
||||
this.min = max;
|
||||
this.max = min;
|
||||
}else{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
if("L".equalsIgnoreCase(value)){
|
||||
// L表示最大值
|
||||
return max;
|
||||
}
|
||||
|
||||
int i;
|
||||
try {
|
||||
i = Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new CronException(e, "Invalid integer value: '{}'", value);
|
||||
}
|
||||
if (i < min || i > max) {
|
||||
throw new CronException("Value {} out of range: [{} , {}]", i, min, max);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMax() {
|
||||
return this.max;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 简易值转换器。将给定String值转为int
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class SimpleValueParser implements ValueParser {
|
||||
|
||||
/** 最小值(包括) */
|
||||
protected int min;
|
||||
/** 最大值(包括) */
|
||||
protected int max;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param min 最小值(包括)
|
||||
* @param max 最大值(包括)
|
||||
*/
|
||||
public SimpleValueParser(int min, int max) {
|
||||
if(min > max){
|
||||
this.min = max;
|
||||
this.max = min;
|
||||
}else{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
if("L".equalsIgnoreCase(value)){
|
||||
// L表示最大值
|
||||
return max;
|
||||
}
|
||||
|
||||
int i;
|
||||
try {
|
||||
i = Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new CronException(e, "Invalid integer value: '{}'", value);
|
||||
}
|
||||
if (i < min || i > max) {
|
||||
throw new CronException("Value {} out of range: [{} , {}]", i, min, max);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMax() {
|
||||
return this.max;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 值处理接口<br>
|
||||
* 值处理用于限定表达式中相应位置的值范围,并转换表达式值为int值
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public interface ValueParser {
|
||||
|
||||
/**
|
||||
* 处理String值并转为int<br>
|
||||
* 转换包括:
|
||||
* <ol>
|
||||
* <li>数字字符串转为数字</li>
|
||||
* <li>别名转为对应的数字(如月份和星期)</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param value String值
|
||||
* @return int
|
||||
*/
|
||||
int parse(String value);
|
||||
|
||||
/**
|
||||
* 返回最小值
|
||||
*
|
||||
* @return 最小值
|
||||
*/
|
||||
int getMin();
|
||||
|
||||
/**
|
||||
* 返回最大值
|
||||
*
|
||||
* @return 最大值
|
||||
*/
|
||||
int getMax();
|
||||
}
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 值处理接口<br>
|
||||
* 值处理用于限定表达式中相应位置的值范围,并转换表达式值为int值
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public interface ValueParser {
|
||||
|
||||
/**
|
||||
* 处理String值并转为int<br>
|
||||
* 转换包括:
|
||||
* <ol>
|
||||
* <li>数字字符串转为数字</li>
|
||||
* <li>别名转为对应的数字(如月份和星期)</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param value String值
|
||||
* @return int
|
||||
*/
|
||||
int parse(String value);
|
||||
|
||||
/**
|
||||
* 返回最小值
|
||||
*
|
||||
* @return 最小值
|
||||
*/
|
||||
int getMin();
|
||||
|
||||
/**
|
||||
* 返回最大值
|
||||
*
|
||||
* @return 最大值
|
||||
*/
|
||||
int getMax();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 年值处理
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class YearValueParser extends SimpleValueParser{
|
||||
|
||||
public YearValueParser() {
|
||||
super(1970, 2099);
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
/**
|
||||
* 年值处理
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class YearValueParser extends SimpleValueParser{
|
||||
|
||||
public YearValueParser() {
|
||||
super(1970, 2099);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 定时任务表达式解析器,内部使用
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 定时任务表达式解析器,内部使用
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
@@ -1,70 +1,70 @@
|
||||
package cn.hutool.cron.task;
|
||||
|
||||
import cn.hutool.cron.pattern.CronPattern;
|
||||
|
||||
/**
|
||||
* 定时作业,此类除了定义了作业,也定义了作业的执行周期以及ID。
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.4.7
|
||||
*/
|
||||
public class CronTask implements Task{
|
||||
|
||||
private final String id;
|
||||
private CronPattern pattern;
|
||||
private final Task task;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param id ID
|
||||
* @param pattern 表达式
|
||||
* @param task 作业
|
||||
*/
|
||||
public CronTask(String id, CronPattern pattern, Task task) {
|
||||
this.id = id;
|
||||
this.pattern = pattern;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
task.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取作业ID
|
||||
*
|
||||
* @return 作业ID
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表达式
|
||||
*
|
||||
* @return 表达式
|
||||
*/
|
||||
public CronPattern getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置新的定时表达式
|
||||
* @param pattern 表达式
|
||||
* @return this
|
||||
*/
|
||||
public CronTask setPattern(CronPattern pattern){
|
||||
this.pattern = pattern;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始作业
|
||||
*
|
||||
* @return 作业
|
||||
*/
|
||||
public Task getRaw(){
|
||||
return this.task;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.task;
|
||||
|
||||
import cn.hutool.cron.pattern.CronPattern;
|
||||
|
||||
/**
|
||||
* 定时作业,此类除了定义了作业,也定义了作业的执行周期以及ID。
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.4.7
|
||||
*/
|
||||
public class CronTask implements Task{
|
||||
|
||||
private final String id;
|
||||
private CronPattern pattern;
|
||||
private final Task task;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param id ID
|
||||
* @param pattern 表达式
|
||||
* @param task 作业
|
||||
*/
|
||||
public CronTask(String id, CronPattern pattern, Task task) {
|
||||
this.id = id;
|
||||
this.pattern = pattern;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
task.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取作业ID
|
||||
*
|
||||
* @return 作业ID
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表达式
|
||||
*
|
||||
* @return 表达式
|
||||
*/
|
||||
public CronPattern getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置新的定时表达式
|
||||
* @param pattern 表达式
|
||||
* @return this
|
||||
*/
|
||||
public CronTask setPattern(CronPattern pattern){
|
||||
this.pattern = pattern;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始作业
|
||||
*
|
||||
* @return 作业
|
||||
*/
|
||||
public Task getRaw(){
|
||||
return this.task;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,68 @@
|
||||
package cn.hutool.cron.task;
|
||||
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.util.ClassLoaderUtil;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 反射执行任务<br>
|
||||
* 通过传入类名#方法名,通过反射执行相应的方法<br>
|
||||
* 如果是静态方法直接执行,如果是对象方法,需要类有默认的构造方法。
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class InvokeTask implements Task{
|
||||
|
||||
private final Object obj;
|
||||
private final Method method;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param classNameWithMethodName 类名与方法名的字符串表示,方法名和类名使用#隔开或者.隔开
|
||||
*/
|
||||
public InvokeTask(String classNameWithMethodName) {
|
||||
int splitIndex = classNameWithMethodName.lastIndexOf('#');
|
||||
if(splitIndex <= 0){
|
||||
splitIndex = classNameWithMethodName.lastIndexOf('.');
|
||||
}
|
||||
if (splitIndex <= 0) {
|
||||
throw new UtilException("Invalid classNameWithMethodName [{}]!", classNameWithMethodName);
|
||||
}
|
||||
|
||||
//类
|
||||
final String className = classNameWithMethodName.substring(0, splitIndex);
|
||||
if(StrUtil.isBlank(className)) {
|
||||
throw new IllegalArgumentException("Class name is blank !");
|
||||
}
|
||||
final Class<?> clazz = ClassLoaderUtil.loadClass(className);
|
||||
if(null == clazz) {
|
||||
throw new IllegalArgumentException("Load class with name of [" + className + "] fail !");
|
||||
}
|
||||
this.obj = ReflectUtil.newInstanceIfPossible(clazz);
|
||||
|
||||
//方法
|
||||
final String methodName = classNameWithMethodName.substring(splitIndex + 1);
|
||||
if(StrUtil.isBlank(methodName)) {
|
||||
throw new IllegalArgumentException("Method name is blank !");
|
||||
}
|
||||
this.method = ClassUtil.getPublicMethod(clazz, methodName);
|
||||
if(null == this.method) {
|
||||
throw new IllegalArgumentException("No method with name of [" + methodName + "] !");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
ReflectUtil.invoke(this.obj, this.method);
|
||||
} catch (UtilException e) {
|
||||
throw new CronException(e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.task;
|
||||
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.util.ClassLoaderUtil;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 反射执行任务<br>
|
||||
* 通过传入类名#方法名,通过反射执行相应的方法<br>
|
||||
* 如果是静态方法直接执行,如果是对象方法,需要类有默认的构造方法。
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class InvokeTask implements Task{
|
||||
|
||||
private final Object obj;
|
||||
private final Method method;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param classNameWithMethodName 类名与方法名的字符串表示,方法名和类名使用#隔开或者.隔开
|
||||
*/
|
||||
public InvokeTask(String classNameWithMethodName) {
|
||||
int splitIndex = classNameWithMethodName.lastIndexOf('#');
|
||||
if(splitIndex <= 0){
|
||||
splitIndex = classNameWithMethodName.lastIndexOf('.');
|
||||
}
|
||||
if (splitIndex <= 0) {
|
||||
throw new UtilException("Invalid classNameWithMethodName [{}]!", classNameWithMethodName);
|
||||
}
|
||||
|
||||
//类
|
||||
final String className = classNameWithMethodName.substring(0, splitIndex);
|
||||
if(StrUtil.isBlank(className)) {
|
||||
throw new IllegalArgumentException("Class name is blank !");
|
||||
}
|
||||
final Class<?> clazz = ClassLoaderUtil.loadClass(className);
|
||||
if(null == clazz) {
|
||||
throw new IllegalArgumentException("Load class with name of [" + className + "] fail !");
|
||||
}
|
||||
this.obj = ReflectUtil.newInstanceIfPossible(clazz);
|
||||
|
||||
//方法
|
||||
final String methodName = classNameWithMethodName.substring(splitIndex + 1);
|
||||
if(StrUtil.isBlank(methodName)) {
|
||||
throw new IllegalArgumentException("Method name is blank !");
|
||||
}
|
||||
this.method = ClassUtil.getPublicMethod(clazz, methodName);
|
||||
if(null == this.method) {
|
||||
throw new IllegalArgumentException("No method with name of [" + methodName + "] !");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
ReflectUtil.invoke(this.obj, this.method);
|
||||
} catch (UtilException e) {
|
||||
throw new CronException(e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package cn.hutool.cron.task;
|
||||
|
||||
/**
|
||||
* {@link Runnable} 的 {@link Task}包装
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class RunnableTask implements Task{
|
||||
private final Runnable runnable;
|
||||
|
||||
public RunnableTask(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.task;
|
||||
|
||||
/**
|
||||
* {@link Runnable} 的 {@link Task}包装
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class RunnableTask implements Task{
|
||||
private final Runnable runnable;
|
||||
|
||||
public RunnableTask(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
package cn.hutool.cron.task;
|
||||
|
||||
/**
|
||||
* 定时作业接口,通过实现execute方法执行具体的任务
|
||||
* <p>
|
||||
* 作业执行是异步执行,即不同作业、相同作业在不同时间的执行是相互独立的。<br>
|
||||
* 假如前一个作业未完成,下一个调度开始,则不会等待前一个作业,直接执行。<br>
|
||||
* 关于作业的互斥,请自行加锁完成。
|
||||
* </p>
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Task {
|
||||
|
||||
/**
|
||||
* 执行作业
|
||||
* <p>
|
||||
* 作业的具体实现需考虑异常情况,默认情况下任务异常在监听中统一监听处理,如果不加入监听,异常会被忽略<br>
|
||||
* 因此最好自行捕获异常后处理
|
||||
*/
|
||||
void execute();
|
||||
}
|
||||
package cn.hutool.cron.task;
|
||||
|
||||
/**
|
||||
* 定时作业接口,通过实现execute方法执行具体的任务
|
||||
* <p>
|
||||
* 作业执行是异步执行,即不同作业、相同作业在不同时间的执行是相互独立的。<br>
|
||||
* 假如前一个作业未完成,下一个调度开始,则不会等待前一个作业,直接执行。<br>
|
||||
* 关于作业的互斥,请自行加锁完成。
|
||||
* </p>
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Task {
|
||||
|
||||
/**
|
||||
* 执行作业
|
||||
* <p>
|
||||
* 作业的具体实现需考虑异常情况,默认情况下任务异常在监听中统一监听处理,如果不加入监听,异常会被忽略<br>
|
||||
* 因此最好自行捕获异常后处理
|
||||
*/
|
||||
void execute();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 定时任务中作业的抽象封装和实现,包括Runnable实现和反射实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 定时任务中作业的抽象封装和实现,包括Runnable实现和反射实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cron.task;
|
||||
@@ -1,21 +1,21 @@
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.cron.CronUtil;
|
||||
|
||||
public class AddAndRemoveMainTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start(false);
|
||||
CronUtil.getScheduler().clear();
|
||||
String id = CronUtil.schedule("*/2 * * * * *", (Runnable) () -> Console.log("task running : 2s"));
|
||||
ThreadUtil.sleep(3000);
|
||||
CronUtil.remove(id);
|
||||
Console.log("Task Removed");
|
||||
|
||||
CronUtil.schedule("*/3 * * * * *", (Runnable) () -> Console.log("New task add running : 3s"));
|
||||
Console.log("New Task added.");
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.cron.CronUtil;
|
||||
|
||||
public class AddAndRemoveMainTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start(false);
|
||||
CronUtil.getScheduler().clear();
|
||||
String id = CronUtil.schedule("*/2 * * * * *", (Runnable) () -> Console.log("task running : 2s"));
|
||||
ThreadUtil.sleep(3000);
|
||||
CronUtil.remove(id);
|
||||
Console.log("Task Removed");
|
||||
|
||||
CronUtil.schedule("*/3 * * * * *", (Runnable) () -> Console.log("New task add running : 3s"));
|
||||
Console.log("New Task added.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +1,82 @@
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.cron.CronUtil;
|
||||
import cn.hutool.cron.TaskExecutor;
|
||||
import cn.hutool.cron.listener.TaskListener;
|
||||
import cn.hutool.cron.task.Task;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 定时任务样例
|
||||
*/
|
||||
public class CronTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void customCronTest() {
|
||||
CronUtil.schedule("*/2 * * * * *", (Task) () -> Console.log("Task excuted."));
|
||||
|
||||
// 支持秒级别定时任务
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start();
|
||||
|
||||
ThreadUtil.waitForDie();
|
||||
Console.log("Exit.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void cronTest() {
|
||||
// 支持秒级别定时任务
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.getScheduler().setDaemon(false);
|
||||
CronUtil.start();
|
||||
|
||||
ThreadUtil.waitForDie();
|
||||
CronUtil.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void cronWithListenerTest() {
|
||||
CronUtil.getScheduler().addListener(new TaskListener() {
|
||||
@Override
|
||||
public void onStart(TaskExecutor executor) {
|
||||
Console.log("Found task:[{}] start!", executor.getCronTask().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSucceeded(TaskExecutor executor) {
|
||||
Console.log("Found task:[{}] success!", executor.getCronTask().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(TaskExecutor executor, Throwable exception) {
|
||||
Console.error("Found task:[{}] failed!", executor.getCronTask().getId());
|
||||
}
|
||||
});
|
||||
|
||||
// 支持秒级别定时任务
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start();
|
||||
|
||||
ThreadUtil.waitForDie();
|
||||
Console.log("Exit.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void addAndRemoveTest() {
|
||||
String id = CronUtil.schedule("*/2 * * * * *", (Runnable) () -> Console.log("task running : 2s"));
|
||||
|
||||
Console.log(id);
|
||||
CronUtil.remove(id);
|
||||
|
||||
// 支持秒级别定时任务
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start();
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.cron.CronUtil;
|
||||
import cn.hutool.cron.TaskExecutor;
|
||||
import cn.hutool.cron.listener.TaskListener;
|
||||
import cn.hutool.cron.task.Task;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 定时任务样例
|
||||
*/
|
||||
public class CronTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void customCronTest() {
|
||||
CronUtil.schedule("*/2 * * * * *", (Task) () -> Console.log("Task excuted."));
|
||||
|
||||
// 支持秒级别定时任务
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start();
|
||||
|
||||
ThreadUtil.waitForDie();
|
||||
Console.log("Exit.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void cronTest() {
|
||||
// 支持秒级别定时任务
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.getScheduler().setDaemon(false);
|
||||
CronUtil.start();
|
||||
|
||||
ThreadUtil.waitForDie();
|
||||
CronUtil.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void cronWithListenerTest() {
|
||||
CronUtil.getScheduler().addListener(new TaskListener() {
|
||||
@Override
|
||||
public void onStart(TaskExecutor executor) {
|
||||
Console.log("Found task:[{}] start!", executor.getCronTask().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSucceeded(TaskExecutor executor) {
|
||||
Console.log("Found task:[{}] success!", executor.getCronTask().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(TaskExecutor executor, Throwable exception) {
|
||||
Console.error("Found task:[{}] failed!", executor.getCronTask().getId());
|
||||
}
|
||||
});
|
||||
|
||||
// 支持秒级别定时任务
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start();
|
||||
|
||||
ThreadUtil.waitForDie();
|
||||
Console.log("Exit.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void addAndRemoveTest() {
|
||||
String id = CronUtil.schedule("*/2 * * * * *", (Runnable) () -> Console.log("task running : 2s"));
|
||||
|
||||
Console.log(id);
|
||||
CronUtil.remove(id);
|
||||
|
||||
// 支持秒级别定时任务
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.cron.CronUtil;
|
||||
import cn.hutool.cron.task.InvokeTask;
|
||||
|
||||
public class DeamonMainTest {
|
||||
public static void main(String[] args) {
|
||||
// 测试守护线程是否对作业线程有效
|
||||
CronUtil.schedule("*/2 * * * * *", new InvokeTask("cn.hutool.cron.demo.TestJob.doWhileTest"));
|
||||
// 当为守护线程时,stop方法调用后doWhileTest里的循环输出将终止,表示作业线程正常结束
|
||||
// 当非守护线程时,stop方法调用后,不再产生新的作业,原作业正常执行。
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start(true);
|
||||
|
||||
ThreadUtil.sleep(3000);
|
||||
CronUtil.stop();
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.cron.CronUtil;
|
||||
import cn.hutool.cron.task.InvokeTask;
|
||||
|
||||
public class DeamonMainTest {
|
||||
public static void main(String[] args) {
|
||||
// 测试守护线程是否对作业线程有效
|
||||
CronUtil.schedule("*/2 * * * * *", new InvokeTask("cn.hutool.cron.demo.TestJob.doWhileTest"));
|
||||
// 当为守护线程时,stop方法调用后doWhileTest里的循环输出将终止,表示作业线程正常结束
|
||||
// 当非守护线程时,stop方法调用后,不再产生新的作业,原作业正常执行。
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start(true);
|
||||
|
||||
ThreadUtil.sleep(3000);
|
||||
CronUtil.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import cn.hutool.cron.CronUtil;
|
||||
|
||||
/**
|
||||
* 定时任务样例
|
||||
*/
|
||||
public class JobMainTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start(false);
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import cn.hutool.cron.CronUtil;
|
||||
|
||||
/**
|
||||
* 定时任务样例
|
||||
*/
|
||||
public class JobMainTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.start(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
|
||||
/**
|
||||
* 测试定时任务,当触发到定时的时间点时,执行doTest方法
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class TestJob {
|
||||
|
||||
private final String jobId = IdUtil.simpleUUID();
|
||||
|
||||
/**
|
||||
* 执行定时任务内容
|
||||
*/
|
||||
public void doTest() {
|
||||
// String name = Thread.currentThread().getName();
|
||||
Console.log("Test Job {} running... at {}", jobId, DateUtil.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行循环定时任务,测试在定时任务结束时作为deamon线程是否能正常结束
|
||||
*/
|
||||
@SuppressWarnings("InfiniteLoopStatement")
|
||||
public void doWhileTest() {
|
||||
String name = Thread.currentThread().getName();
|
||||
while (true) {
|
||||
Console.log("Job {} while running...", name);
|
||||
ThreadUtil.sleep(2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
|
||||
/**
|
||||
* 测试定时任务,当触发到定时的时间点时,执行doTest方法
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class TestJob {
|
||||
|
||||
private final String jobId = IdUtil.simpleUUID();
|
||||
|
||||
/**
|
||||
* 执行定时任务内容
|
||||
*/
|
||||
public void doTest() {
|
||||
// String name = Thread.currentThread().getName();
|
||||
Console.log("Test Job {} running... at {}", jobId, DateUtil.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行循环定时任务,测试在定时任务结束时作为deamon线程是否能正常结束
|
||||
*/
|
||||
@SuppressWarnings("InfiniteLoopStatement")
|
||||
public void doWhileTest() {
|
||||
String name = Thread.currentThread().getName();
|
||||
while (true) {
|
||||
Console.log("Job {} while running...", name);
|
||||
ThreadUtil.sleep(2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
|
||||
/**
|
||||
* 测试定时任务,当触发到定时的时间点时,执行doTest方法
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class TestJob2 {
|
||||
|
||||
/**
|
||||
* 执行定时任务内容
|
||||
*/
|
||||
public void doTest() {
|
||||
Console.log("TestJob2.doTest开始执行……");
|
||||
ThreadUtil.sleep(20, TimeUnit.SECONDS);
|
||||
Console.log("延迟20s打印testJob2");
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.demo;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
|
||||
/**
|
||||
* 测试定时任务,当触发到定时的时间点时,执行doTest方法
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class TestJob2 {
|
||||
|
||||
/**
|
||||
* 执行定时任务内容
|
||||
*/
|
||||
public void doTest() {
|
||||
Console.log("TestJob2.doTest开始执行……");
|
||||
ThreadUtil.sleep(20, TimeUnit.SECONDS);
|
||||
Console.log("延迟20s打印testJob2");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,161 +1,161 @@
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.cron.CronException;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 定时任务单元测试类
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class CronPatternTest {
|
||||
|
||||
@Test
|
||||
public void matchAllTest() {
|
||||
CronPattern pattern;
|
||||
// 任何时间匹配
|
||||
pattern = new CronPattern("* * * * * *");
|
||||
Assert.assertTrue(pattern.match(DateUtil.current(), true));
|
||||
Assert.assertTrue(pattern.match(DateUtil.current(), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchAllTest2() {
|
||||
// 在5位表达式中,秒部分并不是任意匹配,而是一个固定值
|
||||
// 因此此处匹配就不能匹配秒
|
||||
CronPattern pattern;
|
||||
// 任何时间匹配
|
||||
pattern = new CronPattern("* * * * *");
|
||||
for(int i = 0; i < 1; i++) {
|
||||
Assert.assertTrue(pattern.match(DateUtil.current(), false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cronPatternTest() {
|
||||
CronPattern pattern;
|
||||
|
||||
// 12:11匹配
|
||||
pattern = new CronPattern("39 11 12 * * *");
|
||||
assertMatch(pattern, "12:11:39");
|
||||
|
||||
// 每5分钟匹配,匹配分钟为:[0,5,10,15,20,25,30,35,40,45,50,55]
|
||||
pattern = new CronPattern("39 */5 * * * *");
|
||||
assertMatch(pattern, "12:00:39");
|
||||
assertMatch(pattern, "12:05:39");
|
||||
assertMatch(pattern, "12:10:39");
|
||||
assertMatch(pattern, "12:15:39");
|
||||
assertMatch(pattern, "12:20:39");
|
||||
assertMatch(pattern, "12:25:39");
|
||||
assertMatch(pattern, "12:30:39");
|
||||
assertMatch(pattern, "12:35:39");
|
||||
assertMatch(pattern, "12:40:39");
|
||||
assertMatch(pattern, "12:45:39");
|
||||
assertMatch(pattern, "12:50:39");
|
||||
assertMatch(pattern, "12:55:39");
|
||||
|
||||
// 2:01,3:01,4:01
|
||||
pattern = new CronPattern("39 1 2-4 * * *");
|
||||
assertMatch(pattern, "02:01:39");
|
||||
assertMatch(pattern, "03:01:39");
|
||||
assertMatch(pattern, "04:01:39");
|
||||
|
||||
// 2:01,3:01,4:01
|
||||
pattern = new CronPattern("39 1 2,3,4 * * *");
|
||||
assertMatch(pattern, "02:01:39");
|
||||
assertMatch(pattern, "03:01:39");
|
||||
assertMatch(pattern, "04:01:39");
|
||||
|
||||
// 08-07, 08-06
|
||||
pattern = new CronPattern("39 0 0 6,7 8 *");
|
||||
assertMatch(pattern, "2016-08-07 00:00:39");
|
||||
assertMatch(pattern, "2016-08-06 00:00:39");
|
||||
|
||||
// 别名忽略大小写
|
||||
pattern = new CronPattern("39 0 0 6,7 Aug *");
|
||||
assertMatch(pattern, "2016-08-06 00:00:39");
|
||||
assertMatch(pattern, "2016-08-07 00:00:39");
|
||||
|
||||
pattern = new CronPattern("39 0 0 7 aug *");
|
||||
assertMatch(pattern, "2016-08-07 00:00:39");
|
||||
|
||||
// 星期四
|
||||
pattern = new CronPattern("39 0 0 * * Thu");
|
||||
assertMatch(pattern, "2017-02-09 00:00:39");
|
||||
assertMatch(pattern, "2017-02-09 00:00:39");
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
public void CronPatternTest2() {
|
||||
CronPattern pattern = new CronPattern("0/30 * * * *");
|
||||
Assert.assertTrue(pattern.match(DateUtil.parse("2018-10-09 12:00:00").getTime(), false));
|
||||
Assert.assertTrue(pattern.match(DateUtil.parse("2018-10-09 12:30:00").getTime(), false));
|
||||
|
||||
pattern = new CronPattern("32 * * * *");
|
||||
Assert.assertTrue(pattern.match(DateUtil.parse("2018-10-09 12:32:00").getTime(), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patternTest() {
|
||||
CronPattern pattern = new CronPattern("* 0 4 * * ?");
|
||||
assertMatch(pattern, "2017-02-09 04:00:00");
|
||||
assertMatch(pattern, "2017-02-19 04:00:33");
|
||||
|
||||
// 6位Quartz风格表达式
|
||||
pattern = new CronPattern("* 0 4 * * ?");
|
||||
assertMatch(pattern, "2017-02-09 04:00:00");
|
||||
assertMatch(pattern, "2017-02-19 04:00:33");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangePatternTest() {
|
||||
CronPattern pattern = new CronPattern("* 20/2 * * * ?");
|
||||
assertMatch(pattern, "2017-02-09 04:20:00");
|
||||
assertMatch(pattern, "2017-02-09 05:20:00");
|
||||
assertMatch(pattern, "2017-02-19 04:22:33");
|
||||
|
||||
pattern = new CronPattern("* 2-20/2 * * * ?");
|
||||
assertMatch(pattern, "2017-02-09 04:02:00");
|
||||
assertMatch(pattern, "2017-02-09 05:04:00");
|
||||
assertMatch(pattern, "2017-02-19 04:20:33");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastTest() {
|
||||
// 每月最后一天的任意时间
|
||||
CronPattern pattern = new CronPattern("* * * L * ?");
|
||||
assertMatch(pattern, "2017-07-31 04:20:00");
|
||||
assertMatch(pattern, "2017-02-28 04:20:00");
|
||||
|
||||
// 最后一个月的任意时间
|
||||
pattern = new CronPattern("* * * * L ?");
|
||||
assertMatch(pattern, "2017-12-02 04:20:00");
|
||||
|
||||
// 任意天的最后时间
|
||||
pattern = new CronPattern("L L L * * ?");
|
||||
assertMatch(pattern, "2017-12-02 23:59:59");
|
||||
}
|
||||
|
||||
@Test(expected = CronException.class)
|
||||
public void rangeYearTest() {
|
||||
// year的范围是1970~2099年,超出报错
|
||||
CronPattern pattern = new CronPattern("0/1 * * * 1/1 ? 2020-2120");
|
||||
}
|
||||
|
||||
/**
|
||||
* 表达式是否匹配日期
|
||||
*
|
||||
* @param pattern 表达式
|
||||
* @param date 日期,标准日期时间字符串
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void assertMatch(CronPattern pattern, String date) {
|
||||
Assert.assertTrue(pattern.match(DateUtil.parse(date).getTime(), false));
|
||||
Assert.assertTrue(pattern.match(DateUtil.parse(date).getTime(), true));
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.cron.CronException;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 定时任务单元测试类
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class CronPatternTest {
|
||||
|
||||
@Test
|
||||
public void matchAllTest() {
|
||||
CronPattern pattern;
|
||||
// 任何时间匹配
|
||||
pattern = new CronPattern("* * * * * *");
|
||||
Assert.assertTrue(pattern.match(DateUtil.current(), true));
|
||||
Assert.assertTrue(pattern.match(DateUtil.current(), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchAllTest2() {
|
||||
// 在5位表达式中,秒部分并不是任意匹配,而是一个固定值
|
||||
// 因此此处匹配就不能匹配秒
|
||||
CronPattern pattern;
|
||||
// 任何时间匹配
|
||||
pattern = new CronPattern("* * * * *");
|
||||
for(int i = 0; i < 1; i++) {
|
||||
Assert.assertTrue(pattern.match(DateUtil.current(), false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cronPatternTest() {
|
||||
CronPattern pattern;
|
||||
|
||||
// 12:11匹配
|
||||
pattern = new CronPattern("39 11 12 * * *");
|
||||
assertMatch(pattern, "12:11:39");
|
||||
|
||||
// 每5分钟匹配,匹配分钟为:[0,5,10,15,20,25,30,35,40,45,50,55]
|
||||
pattern = new CronPattern("39 */5 * * * *");
|
||||
assertMatch(pattern, "12:00:39");
|
||||
assertMatch(pattern, "12:05:39");
|
||||
assertMatch(pattern, "12:10:39");
|
||||
assertMatch(pattern, "12:15:39");
|
||||
assertMatch(pattern, "12:20:39");
|
||||
assertMatch(pattern, "12:25:39");
|
||||
assertMatch(pattern, "12:30:39");
|
||||
assertMatch(pattern, "12:35:39");
|
||||
assertMatch(pattern, "12:40:39");
|
||||
assertMatch(pattern, "12:45:39");
|
||||
assertMatch(pattern, "12:50:39");
|
||||
assertMatch(pattern, "12:55:39");
|
||||
|
||||
// 2:01,3:01,4:01
|
||||
pattern = new CronPattern("39 1 2-4 * * *");
|
||||
assertMatch(pattern, "02:01:39");
|
||||
assertMatch(pattern, "03:01:39");
|
||||
assertMatch(pattern, "04:01:39");
|
||||
|
||||
// 2:01,3:01,4:01
|
||||
pattern = new CronPattern("39 1 2,3,4 * * *");
|
||||
assertMatch(pattern, "02:01:39");
|
||||
assertMatch(pattern, "03:01:39");
|
||||
assertMatch(pattern, "04:01:39");
|
||||
|
||||
// 08-07, 08-06
|
||||
pattern = new CronPattern("39 0 0 6,7 8 *");
|
||||
assertMatch(pattern, "2016-08-07 00:00:39");
|
||||
assertMatch(pattern, "2016-08-06 00:00:39");
|
||||
|
||||
// 别名忽略大小写
|
||||
pattern = new CronPattern("39 0 0 6,7 Aug *");
|
||||
assertMatch(pattern, "2016-08-06 00:00:39");
|
||||
assertMatch(pattern, "2016-08-07 00:00:39");
|
||||
|
||||
pattern = new CronPattern("39 0 0 7 aug *");
|
||||
assertMatch(pattern, "2016-08-07 00:00:39");
|
||||
|
||||
// 星期四
|
||||
pattern = new CronPattern("39 0 0 * * Thu");
|
||||
assertMatch(pattern, "2017-02-09 00:00:39");
|
||||
assertMatch(pattern, "2017-02-09 00:00:39");
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
public void CronPatternTest2() {
|
||||
CronPattern pattern = new CronPattern("0/30 * * * *");
|
||||
Assert.assertTrue(pattern.match(DateUtil.parse("2018-10-09 12:00:00").getTime(), false));
|
||||
Assert.assertTrue(pattern.match(DateUtil.parse("2018-10-09 12:30:00").getTime(), false));
|
||||
|
||||
pattern = new CronPattern("32 * * * *");
|
||||
Assert.assertTrue(pattern.match(DateUtil.parse("2018-10-09 12:32:00").getTime(), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patternTest() {
|
||||
CronPattern pattern = new CronPattern("* 0 4 * * ?");
|
||||
assertMatch(pattern, "2017-02-09 04:00:00");
|
||||
assertMatch(pattern, "2017-02-19 04:00:33");
|
||||
|
||||
// 6位Quartz风格表达式
|
||||
pattern = new CronPattern("* 0 4 * * ?");
|
||||
assertMatch(pattern, "2017-02-09 04:00:00");
|
||||
assertMatch(pattern, "2017-02-19 04:00:33");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangePatternTest() {
|
||||
CronPattern pattern = new CronPattern("* 20/2 * * * ?");
|
||||
assertMatch(pattern, "2017-02-09 04:20:00");
|
||||
assertMatch(pattern, "2017-02-09 05:20:00");
|
||||
assertMatch(pattern, "2017-02-19 04:22:33");
|
||||
|
||||
pattern = new CronPattern("* 2-20/2 * * * ?");
|
||||
assertMatch(pattern, "2017-02-09 04:02:00");
|
||||
assertMatch(pattern, "2017-02-09 05:04:00");
|
||||
assertMatch(pattern, "2017-02-19 04:20:33");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastTest() {
|
||||
// 每月最后一天的任意时间
|
||||
CronPattern pattern = new CronPattern("* * * L * ?");
|
||||
assertMatch(pattern, "2017-07-31 04:20:00");
|
||||
assertMatch(pattern, "2017-02-28 04:20:00");
|
||||
|
||||
// 最后一个月的任意时间
|
||||
pattern = new CronPattern("* * * * L ?");
|
||||
assertMatch(pattern, "2017-12-02 04:20:00");
|
||||
|
||||
// 任意天的最后时间
|
||||
pattern = new CronPattern("L L L * * ?");
|
||||
assertMatch(pattern, "2017-12-02 23:59:59");
|
||||
}
|
||||
|
||||
@Test(expected = CronException.class)
|
||||
public void rangeYearTest() {
|
||||
// year的范围是1970~2099年,超出报错
|
||||
CronPattern pattern = new CronPattern("0/1 * * * 1/1 ? 2020-2120");
|
||||
}
|
||||
|
||||
/**
|
||||
* 表达式是否匹配日期
|
||||
*
|
||||
* @param pattern 表达式
|
||||
* @param date 日期,标准日期时间字符串
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void assertMatch(CronPattern pattern, String date) {
|
||||
Assert.assertTrue(pattern.match(DateUtil.parse(date).getTime(), false));
|
||||
Assert.assertTrue(pattern.match(DateUtil.parse(date).getTime(), true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
|
||||
public class CronPatternUtilTest {
|
||||
|
||||
@Test
|
||||
public void matchedDatesTest() {
|
||||
//测试每30秒执行
|
||||
List<Date> matchedDates = CronPatternUtil.matchedDates("0/30 * 8-18 * * ?", DateUtil.parse("2018-10-15 14:33:22"), 5, true);
|
||||
Assert.assertEquals(5, matchedDates.size());
|
||||
Assert.assertEquals("2018-10-15 14:33:30", matchedDates.get(0).toString());
|
||||
Assert.assertEquals("2018-10-15 14:34:00", matchedDates.get(1).toString());
|
||||
Assert.assertEquals("2018-10-15 14:34:30", matchedDates.get(2).toString());
|
||||
Assert.assertEquals("2018-10-15 14:35:00", matchedDates.get(3).toString());
|
||||
Assert.assertEquals("2018-10-15 14:35:30", matchedDates.get(4).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchedDatesTest2() {
|
||||
//测试每小时执行
|
||||
List<Date> matchedDates = CronPatternUtil.matchedDates("0 0 */1 * * *", DateUtil.parse("2018-10-15 14:33:22"), 5, true);
|
||||
Assert.assertEquals(5, matchedDates.size());
|
||||
Assert.assertEquals("2018-10-15 15:00:00", matchedDates.get(0).toString());
|
||||
Assert.assertEquals("2018-10-15 16:00:00", matchedDates.get(1).toString());
|
||||
Assert.assertEquals("2018-10-15 17:00:00", matchedDates.get(2).toString());
|
||||
Assert.assertEquals("2018-10-15 18:00:00", matchedDates.get(3).toString());
|
||||
Assert.assertEquals("2018-10-15 19:00:00", matchedDates.get(4).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchedDatesTest3() {
|
||||
//测试最后一天
|
||||
List<Date> matchedDates = CronPatternUtil.matchedDates("0 0 */1 L * *", DateUtil.parse("2018-10-30 23:33:22"), 5, true);
|
||||
Assert.assertEquals(5, matchedDates.size());
|
||||
Assert.assertEquals("2018-10-31 00:00:00", matchedDates.get(0).toString());
|
||||
Assert.assertEquals("2018-10-31 01:00:00", matchedDates.get(1).toString());
|
||||
Assert.assertEquals("2018-10-31 02:00:00", matchedDates.get(2).toString());
|
||||
Assert.assertEquals("2018-10-31 03:00:00", matchedDates.get(3).toString());
|
||||
Assert.assertEquals("2018-10-31 04:00:00", matchedDates.get(4).toString());
|
||||
}
|
||||
}
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
|
||||
public class CronPatternUtilTest {
|
||||
|
||||
@Test
|
||||
public void matchedDatesTest() {
|
||||
//测试每30秒执行
|
||||
List<Date> matchedDates = CronPatternUtil.matchedDates("0/30 * 8-18 * * ?", DateUtil.parse("2018-10-15 14:33:22"), 5, true);
|
||||
Assert.assertEquals(5, matchedDates.size());
|
||||
Assert.assertEquals("2018-10-15 14:33:30", matchedDates.get(0).toString());
|
||||
Assert.assertEquals("2018-10-15 14:34:00", matchedDates.get(1).toString());
|
||||
Assert.assertEquals("2018-10-15 14:34:30", matchedDates.get(2).toString());
|
||||
Assert.assertEquals("2018-10-15 14:35:00", matchedDates.get(3).toString());
|
||||
Assert.assertEquals("2018-10-15 14:35:30", matchedDates.get(4).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchedDatesTest2() {
|
||||
//测试每小时执行
|
||||
List<Date> matchedDates = CronPatternUtil.matchedDates("0 0 */1 * * *", DateUtil.parse("2018-10-15 14:33:22"), 5, true);
|
||||
Assert.assertEquals(5, matchedDates.size());
|
||||
Assert.assertEquals("2018-10-15 15:00:00", matchedDates.get(0).toString());
|
||||
Assert.assertEquals("2018-10-15 16:00:00", matchedDates.get(1).toString());
|
||||
Assert.assertEquals("2018-10-15 17:00:00", matchedDates.get(2).toString());
|
||||
Assert.assertEquals("2018-10-15 18:00:00", matchedDates.get(3).toString());
|
||||
Assert.assertEquals("2018-10-15 19:00:00", matchedDates.get(4).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchedDatesTest3() {
|
||||
//测试最后一天
|
||||
List<Date> matchedDates = CronPatternUtil.matchedDates("0 0 */1 L * *", DateUtil.parse("2018-10-30 23:33:22"), 5, true);
|
||||
Assert.assertEquals(5, matchedDates.size());
|
||||
Assert.assertEquals("2018-10-31 00:00:00", matchedDates.get(0).toString());
|
||||
Assert.assertEquals("2018-10-31 01:00:00", matchedDates.get(1).toString());
|
||||
Assert.assertEquals("2018-10-31 02:00:00", matchedDates.get(2).toString());
|
||||
Assert.assertEquals("2018-10-31 03:00:00", matchedDates.get(3).toString());
|
||||
Assert.assertEquals("2018-10-31 04:00:00", matchedDates.get(4).toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#------------------------------------------------------------------
|
||||
# 定时任务配置文件
|
||||
# 定时任务表达分为以下几种情况:
|
||||
# 1. 表达式为5位,此时兼容Linux的Crontab模式,第一位匹配分,此时如果为秒匹配模式,则秒部分为固定值(取决于加入表达式时当前时间秒数)
|
||||
# 2. 表达式为6位,此时兼容Quartz模式,第一位匹配秒,但是只有秒匹配模式时秒部分定义才有效
|
||||
# 3. 表达式为7位,此时兼容Quartz模式,第一位匹配秒,最后一位匹配年
|
||||
#------------------------------------------------------------------
|
||||
|
||||
# cn.hutool.cron.demo.TestJob.doTest = */1 * * * * *
|
||||
|
||||
[cn.hutool.cron.demo]
|
||||
# 6位表达式在秒匹配模式下可用,此处表示每秒执行一次
|
||||
# TestJob.doTest = */1 * * * * *
|
||||
# 5位表达式在分匹配模式下可用,此处表示每分钟执行一次
|
||||
# 如果此时为秒匹配模式,则秒部分为固定数字(此秒取决于加入表达式当前时间的秒数)
|
||||
TestJob.doTest = 0/30 * 8-18 * * ?
|
||||
#------------------------------------------------------------------
|
||||
# 定时任务配置文件
|
||||
# 定时任务表达分为以下几种情况:
|
||||
# 1. 表达式为5位,此时兼容Linux的Crontab模式,第一位匹配分,此时如果为秒匹配模式,则秒部分为固定值(取决于加入表达式时当前时间秒数)
|
||||
# 2. 表达式为6位,此时兼容Quartz模式,第一位匹配秒,但是只有秒匹配模式时秒部分定义才有效
|
||||
# 3. 表达式为7位,此时兼容Quartz模式,第一位匹配秒,最后一位匹配年
|
||||
#------------------------------------------------------------------
|
||||
|
||||
# cn.hutool.cron.demo.TestJob.doTest = */1 * * * * *
|
||||
|
||||
[cn.hutool.cron.demo]
|
||||
# 6位表达式在秒匹配模式下可用,此处表示每秒执行一次
|
||||
# TestJob.doTest = */1 * * * * *
|
||||
# 5位表达式在分匹配模式下可用,此处表示每分钟执行一次
|
||||
# 如果此时为秒匹配模式,则秒部分为固定数字(此秒取决于加入表达式当前时间的秒数)
|
||||
TestJob.doTest = 0/30 * 8-18 * * ?
|
||||
TestJob2.doTest = */3 * * * * *
|
||||
Reference in New Issue
Block a user