clean history

This commit is contained in:
Looly
2019-08-14 10:02:32 +08:00
commit 6b011af032
1215 changed files with 159913 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
package cn.hutool.cron;
import cn.hutool.core.util.StrUtil;
/**
* 定时任务异常
* @author xiaoleilu
*/
public class CronException extends RuntimeException{
private static final long serialVersionUID = 8247610319171014183L;
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);
}
}

View File

@@ -0,0 +1,72 @@
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;
/**
* 定时任务计时器<br>
* 计时器线程每隔一分钟检查一次任务列表一旦匹配到执行对应的Task
* @author Looly
*
*/
public class CronTimer extends Thread{
private static final Log log = LogFactory.get();
/** 定时单元:秒 */
private long TIMER_UNIT_SECOND = DateUnit.SECOND.getMillis();
/** 定时单元:分 */
private long TIMER_UNIT_MINUTE = DateUnit.MINUTE.getMillis();
/** 定时任务是否已经被强制关闭 */
private boolean isStoped;
private Scheduler scheduler;
/**
* 构造
* @param scheduler {@link Scheduler}
*/
public CronTimer(Scheduler scheduler) {
this.scheduler = scheduler;
}
@Override
public void run() {
final long timerUnit = this.scheduler.matchSecond ? TIMER_UNIT_SECOND : TIMER_UNIT_MINUTE;
long thisTime = System.currentTimeMillis();
long nextTime;
long sleep;
while(false == isStoped){
//下一时间计算是按照上一个执行点开始时间计算的
nextTime = ((thisTime / timerUnit) + 1) * timerUnit;
sleep = nextTime - System.currentTimeMillis();
if (sleep > 0 && false == ThreadUtil.safeSleep(sleep)) {
//等待直到下一个时间点如果被中断直接退出Timer
break;
}
//执行点,时间记录为执行开始的时间,而非结束时间
thisTime = System.currentTimeMillis();
spawnLauncher(thisTime);
}
log.debug("Hutool-cron timer stoped.");
}
/**
* 关闭定时器
*/
synchronized public void stopTimer() {
this.isStoped = true;
ThreadUtil.interupt(this, true);
}
/**
* 启动匹配
* @param millis 当前时间
*/
private void spawnLauncher(final long millis){
this.scheduler.taskLauncherManager.spawnLauncher(millis);
}
}

View File

@@ -0,0 +1,205 @@
package cn.hutool.cron;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.resource.NoResourceException;
import cn.hutool.cron.pattern.CronPattern;
import cn.hutool.cron.task.Task;
import cn.hutool.setting.Setting;
import cn.hutool.setting.SettingRuntimeException;
/**
* 定时任务工具类<br>
* 此工具持有一个全局{@link Scheduler},所有定时任务在同一个调度器中执行<br>
* {@link #setMatchSecond(boolean)} 方法用于定义是否使用秒匹配模式如果为true则定时任务表达式中的第一位为秒否则为分默认是分
*
* @author xiaoleilu
*
*/
public class CronUtil {
/** Crontab配置文件 */
public static final String CRONTAB_CONFIG_PATH = "config/cron.setting";
public static final String CRONTAB_CONFIG_PATH2 = "cron.setting";
private static final Lock lock = new ReentrantLock();
private static final Scheduler scheduler = new Scheduler();
private static Setting crontabSetting;
/**
* 自定义定时任务配置文件
*
* @param cronSetting 定时任务配置文件
*/
public static void setCronSetting(Setting cronSetting) {
crontabSetting = cronSetting;
}
/**
* 自定义定时任务配置文件路径
*
* @param cronSettingPath 定时任务配置文件路径(相对绝对都可)
*/
public static void setCronSetting(String cronSettingPath) {
try {
crontabSetting = new Setting(cronSettingPath, Setting.DEFAULT_CHARSET, false);
} catch (SettingRuntimeException | NoResourceException e) {
// ignore setting file parse error and no config error
}
}
/**
* 设置是否支持秒匹配<br>
* 此方法用于定义是否使用秒匹配模式如果为true则定时任务表达式中的第一位为秒否则为分默认是分<br>
*
* @param isMatchSecond <code>true</code>支持,<code>false</code>不支持
*/
public static void setMatchSecond(boolean isMatchSecond) {
scheduler.setMatchSecond(isMatchSecond);
}
/**
* 加入定时任务
*
* @param schedulingPattern 定时任务执行时间的crontab表达式
* @param task 任务
* @return 定时任务ID
*/
public static String schedule(String schedulingPattern, Task task) {
return scheduler.schedule(schedulingPattern, task);
}
/**
* 加入定时任务
*
* @param id 定时任务ID
* @param schedulingPattern 定时任务执行时间的crontab表达式
* @param task 任务
* @return 定时任务ID
* @since 3.3.0
*/
public static String schedule(String id, String schedulingPattern, Task task) {
scheduler.schedule(id, schedulingPattern, task);
return id;
}
/**
* 加入定时任务
*
* @param schedulingPattern 定时任务执行时间的crontab表达式
* @param task 任务
* @return 定时任务ID
*/
public static String schedule(String schedulingPattern, Runnable task) {
return scheduler.schedule(schedulingPattern, task);
}
/**
* 批量加入配置文件中的定时任务
*
* @param cronSetting 定时任务设置文件
*/
public static void schedule(Setting cronSetting) {
scheduler.schedule(cronSetting);
}
/**
* 移除任务
*
* @param schedulerId 任务ID
*/
public static void remove(String schedulerId) {
scheduler.deschedule(schedulerId);
}
/**
* 移除Task
*
* @param id Task的ID
* @param pattern {@link CronPattern}
* @since 4.0.10
*/
public static void updatePattern(String id, CronPattern pattern) {
scheduler.updatePattern(id, pattern);
}
/**
* @return 获得Scheduler对象
*/
public static Scheduler getScheduler() {
return scheduler;
}
/**
* 开始,非守护线程模式
*
* @see #start(boolean)
*/
public static void start() {
start(false);
}
/**
* 开始
*
* @param isDeamon 是否以守护线程方式启动如果为true则在调用{@link #stop()}方法后执行的定时任务立即结束,否则等待执行完毕才结束。
*/
synchronized public static void start(boolean isDeamon) {
if (scheduler.isStarted()) {
throw new UtilException("Scheduler has been started, please stop it first!");
}
lock.lock();
try {
if (null == crontabSetting) {
// 尝试查找config/cron.setting
setCronSetting(CRONTAB_CONFIG_PATH);
}
// 尝试查找cron.setting
if (null == crontabSetting) {
setCronSetting(CRONTAB_CONFIG_PATH2);
}
} finally {
lock.unlock();
}
schedule(crontabSetting);
scheduler.start(isDeamon);
}
/**
* 重新启动定时任务<br>
* 此方法会清除动态加载的任务,重新启动后,守护线程与否与之前保持一致
*/
public static void restart() {
lock.lock();
try {
if (null != crontabSetting) {
//重新读取配置文件
crontabSetting.load();
}
if (scheduler.isStarted()) {
//关闭并清除已有任务
scheduler.stop(true);
}
} finally {
lock.unlock();
}
//重新加载任务
schedule(crontabSetting);
//重新启动
scheduler.start();
}
/**
* 停止
*/
public static void stop() {
scheduler.stop();
}
}

View File

@@ -0,0 +1,436 @@
package cn.hutool.cron;
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;
import cn.hutool.core.collection.CollectionUtil;
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;
/**
* 任务调度器<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 {
private Lock lock = new ReentrantLock();
/** 时区 */
private TimeZone timezone;
/** 是否已经启动 */
private boolean started = false;
/** 是否支持秒匹配 */
protected boolean matchSecond = false;
/** 是否为守护线程 */
protected boolean daemon;
/** 定时器 */
private CronTimer timer;
/** 定时任务表 */
protected TaskTable taskTable = new TaskTable(this);
/** 启动器管理器 */
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.timezone = timezone;
return this;
}
/**
* 获得时区,默认为 {@link TimeZone#getDefault()}
*
* @return 时区
*/
public TimeZone getTimeZone() {
return timezone != null ? timezone : TimeZone.getDefault();
}
/**
* 设置是否为守护线程<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 isDeamon() {
return this.daemon;
}
/**
* 是否支持秒匹配
*
* @return <code>true</code>使用,<code>false</code>不使用
*/
public boolean isMatchSecond() {
return this.matchSecond;
}
/**
* 设置是否支持秒匹配,默认不使用
*
* @param isMatchSecond <code>true</code>支持,<code>false</code>不支持
* @return this
*/
public Scheduler setMatchSecond(boolean isMatchSecond) {
this.matchSecond = 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 (CollectionUtil.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
*
* @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
*
* @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
*
* @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;
}
/**
* 获得指定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(this);
return this;
}
// -------------------------------------------------------------------- shcedule end
/**
* @return 是否已经启动
*/
public boolean isStarted() {
return this.started;
}
/**
* 启动
*
* @param isDeamon 是否以守护线程方式启动如果为true则在调用{@link #stop()}方法后执行的定时任务立即结束,否则等待执行完毕才结束。
* @return this
*/
public Scheduler start(boolean isDeamon) {
this.daemon = isDeamon;
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>
* 此方法调用后会将定时器进程立即结束,如果为守护线程模式,则正在执行的作业也会自动结束,否则作业线程将在执行完成后结束。
*
* @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;
}
}

View File

@@ -0,0 +1,41 @@
package cn.hutool.cron;
import cn.hutool.cron.task.Task;
/**
* 作业执行器<br>
* 执行具体的作业,执行完毕销毁
* @author Looly
*
*/
public class TaskExecutor implements Runnable{
private Scheduler scheduler;
private Task task;
/**
* 获得任务对象
* @return 任务对象
*/
public Task getTask() {
return task;
}
public TaskExecutor(Scheduler scheduler, Task 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);
}
}
}

View File

@@ -0,0 +1,72 @@
package cn.hutool.cron;
import java.util.ArrayList;
import java.util.List;
import cn.hutool.cron.task.Task;
/**
* 作业执行管理器<br>
* 负责管理作业的启动、停止等
*
* @author Looly
* @since 3.0.1
*/
public class TaskExecutorManager {
protected Scheduler scheduler;
/** 执行器列表 */
private List<TaskExecutor> executors = new ArrayList<>();
public TaskExecutorManager(Scheduler scheduler) {
this.scheduler = scheduler;
}
/**
* 启动 TaskExecutor
*
* @param task {@link Task}
* @return {@link TaskExecutor}
*/
public TaskExecutor spawnExecutor(Task 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;
}
/**
* 执行器执行完毕调用此方法,将执行器从执行器列表移除
*
* @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;
}
}

View File

@@ -0,0 +1,29 @@
package cn.hutool.cron;
/**
* 作业启动器<br>
* 负责检查<strong>TaskTable</strong>是否有匹配到此时运行的Task<br>
* 检查完毕后启动器结束
*
* @author Looly
*
*/
public class TaskLauncher implements Runnable{
private Scheduler scheduler;
private long millis;
public TaskLauncher(Scheduler scheduler, long millis) {
this.scheduler = scheduler;
this.millis = millis;
}
@Override
public void run() {
//匹配秒部分由用户定义决定,始终不匹配年
scheduler.taskTable.executeTaskIfMatchInternal(millis);
//结束通知
scheduler.taskLauncherManager.notifyLauncherCompleted(this);
}
}

View File

@@ -0,0 +1,64 @@
package cn.hutool.cron;
import java.util.ArrayList;
import java.util.List;
/**
* 作业启动管理器
*
* @author looly
*
*/
public class TaskLauncherManager {
protected Scheduler scheduler;
/** 启动器列表 */
protected 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;
}
}

View File

@@ -0,0 +1,223 @@
package cn.hutool.cron;
import java.util.ArrayList;
import java.util.TimeZone;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import cn.hutool.cron.pattern.CronPattern;
import cn.hutool.cron.task.Task;
/**
* 定时任务表<br>
* 任务表将ID、表达式、任务一一对应定时任务执行过程中会周期性检查定时任务表中的所有任务表达式匹配情况从而执行其对应的任务<br>
* 任务的添加、移除使用读写锁保证线程安全性
*
* @author Looly
*
*/
public class TaskTable {
private ReadWriteLock lock = new ReentrantReadWriteLock();
private Scheduler scheduler;
private TimeZone timezone;
private ArrayList<String> ids = new ArrayList<>();
private ArrayList<CronPattern> patterns = new ArrayList<>();
private ArrayList<Task> tasks = new ArrayList<>();
private int size;
/**
* 构造
*
* @param scheduler {@link Scheduler}
*/
public TaskTable(Scheduler scheduler) {
this.scheduler = scheduler;
this.timezone = scheduler.getTimeZone();
}
/**
* 新增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();
try {
writeLock.lock();
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;
}
/**
* 移除Task
*
* @param id Task的ID
*/
public void remove(String id) {
final Lock writeLock = lock.writeLock();
try {
writeLock.lock();
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();
try {
writeLock.lock();
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();
try {
readLock.lock();
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();
try {
readLock.lock();
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 millis 时间毫秒
*/
public void executeTaskIfMatch(long millis) {
final Lock readLock = lock.readLock();
try {
readLock.lock();
executeTaskIfMatchInternal(millis);
} finally {
readLock.unlock();
}
}
/**
* 如果时间匹配则执行相应的Task无锁
*
* @param millis 时间毫秒
* @since 3.1.1
*/
protected void executeTaskIfMatchInternal(long millis) {
for (int i = 0; i < size; i++) {
if (patterns.get(i).match(timezone, millis, this.scheduler.matchSecond)) {
this.scheduler.taskExecutorManager.spawnExecutor(tasks.get(i));
}
}
}
}

View File

@@ -0,0 +1,27 @@
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) {
}
}

View File

@@ -0,0 +1,32 @@
package cn.hutool.cron.listener;
import cn.hutool.cron.TaskExecutor;
/**
* 定时任务监听接口<br>
* 通过实现此接口,实现对定时任务的各个环节做监听
* @author Looly
*
*/
public interface TaskListener {
/**
* 定时任务启动时触发
* @param executor {@link TaskExecutor}
*/
public void onStart(TaskExecutor executor);
/**
* 任务成功结束时触发
*
* @param executor {@link TaskExecutor}
*/
public void onSucceeded(TaskExecutor executor);
/**
* 任务启动失败时触发
*
* @param executor {@link TaskExecutor}
* @param exception 异常
*/
public void onFailed(TaskExecutor executor, Throwable exception);
}

View File

@@ -0,0 +1,88 @@
package cn.hutool.cron.listener;
import java.util.ArrayList;
import java.util.List;
import cn.hutool.cron.TaskExecutor;
import cn.hutool.log.StaticLog;
/**
* 监听调度器,统一管理监听
* @author Looly
*
*/
public class TaskListenerManager {
private 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();
for (int i = 0; i < size; i++) {
TaskListener listenerl = listeners.get(i);
listenerl.onStart(executor);
}
}
}
/**
* 通知所有监听任务启动器成功结束
* @param executor {@link TaskExecutor}
*/
public void notifyTaskSucceeded(TaskExecutor executor) {
synchronized (listeners) {
int size = listeners.size();
for (int i = 0; i < size; i++) {
TaskListener listenerl = listeners.get(i);
listenerl.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 (int i = 0; i < size; i++) {
TaskListener listenerl = listeners.get(i);
listenerl.onFailed(executor, exception);
}
}else{
StaticLog.error(exception, exception.getMessage());
}
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* 定时任务执行监听接口及部分实现
*
* @author looly
*
*/
package cn.hutool.cron.listener;

View File

@@ -0,0 +1,7 @@
/**
* 定时任务模块提供类Crontab表达式的定时任务实现参考了Cron4j同时可以支持秒级别的定时任务定义和年的定义同时兼容Crontab、Cron4j、Quartz表达式
*
* @author looly
*
*/
package cn.hutool.cron;

View File

@@ -0,0 +1,297 @@
package cn.hutool.cron.pattern;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
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;
/**
* 定时任务表达式<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>时才会匹配秒部分
* 当isMatchYear为<code>true</code>时才会匹配年部分
* 默认都是关闭的
* </pre>
*
* 对于每一个子表达式,同样支持以下形式:
* <ul>
* <li><strong>*</strong>:表示匹配这个位置所有的时间</li>
* <li><strong>?</strong>:表示匹配这个位置任意的时间(与"*"作用一致)</li>
* <li><strong>*&#47;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>
* 间隔(/ &gt; 区间(- &gt; 列表(,
* </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>*&#47;2 * * * *</strong>:每两小时执行</li>
* <li><strong>* 12 * * *</strong>12点的每分钟执行</li>
* <li><strong>59 11 * * 1,2</strong>每周一和周二的11:59执行</li>
* <li><strong>3-18&#47;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 String pattern;
/** 秒字段匹配列表 */
private List<ValueMatcher> secondMatchers = new ArrayList<>();
/** 分字段匹配列表 */
private List<ValueMatcher> minuteMatchers = new ArrayList<>();
/** 时字段匹配列表 */
private List<ValueMatcher> hourMatchers = new ArrayList<>();
/** 每月几号字段匹配列表 */
private List<ValueMatcher> dayOfMonthMatchers = new ArrayList<>();
/** 月字段匹配列表 */
private List<ValueMatcher> monthMatchers = new ArrayList<>();
/** 星期字段匹配列表 */
private List<ValueMatcher> dayOfWeekMatchers = new ArrayList<>();
/** 年字段匹配列表 */
private List<ValueMatcher> yearMatchers = new ArrayList<>();
/** 匹配器个数,取决于复合任务表达式中的单一表达式个数 */
private int matcherSize;
/**
* 构造
*
* @see CronPattern
*
* @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 = (isMatchSecond ? secondMatchers.get(i).match(second) : true) // 匹配秒非秒匹配模式下始终返回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) : true;
}
/**
* 解析复合任务表达式
*
* @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[0 + 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.secondMatchers.add(new AlwaysTrueValueMatcher());
}
matcherSize++;
}
// -------------------------------------------------------------------------------------- Private method end
}

View File

@@ -0,0 +1,103 @@
package cn.hutool.cron.pattern;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
/**
* 定时任务表达式工具类
*
* @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;
}
}

View File

@@ -0,0 +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.");
}
}

View File

@@ -0,0 +1,36 @@
package cn.hutool.cron.pattern.matcher;
import java.util.Collections;
import java.util.List;
import cn.hutool.core.util.StrUtil;
/**
* 将表达式中的数字值列表转换为Boolean数组匹配时匹配相应数组位
* @author Looly
*
*/
public class BoolArrayValueMatcher implements ValueMatcher{
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:{}", (Object)this.bValues);
}
}

View File

@@ -0,0 +1,58 @@
package cn.hutool.cron.pattern.matcher;
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 实际的月份
* @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 月份
* @param isLeapYear 是否闰年
* @return 是否为本月最后一天
*/
private static boolean isLastDayOfMonth(int value, int month, boolean isLeapYear) {
if (isLeapYear && month == 2) {
return value == 29;
} else {
return value == LAST_DAYS[month - 1];
}
}
}

View File

@@ -0,0 +1,13 @@
package cn.hutool.cron.pattern.matcher;
import cn.hutool.core.lang.Matcher;
/**
* 值匹配器<br>
* 用于匹配日期位中对应数字是否匹配
* @author Looly
*
*/
public interface ValueMatcher extends Matcher<Integer>{
}

View File

@@ -0,0 +1,196 @@
package cn.hutool.cron.pattern.matcher;
import java.util.ArrayList;
import java.util.List;
import cn.hutool.core.collection.CollectionUtil;
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;
/**
* {@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) {
CollectionUtil.addAllIfNotContains(values, parseStep(part, parser));
}
return values;
}
/**
* 处理间隔形式的表达式<br>
* 处理的形式包括:
* <ul>
* <li><strong>a</strong> 或 <strong>*</strong></li>
* <li><strong>a&#47;b</strong> 或 <strong>*&#47;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此时与单值模式一致
if(step > 0) {//类似 20/2的形式
NumberUtil.appendRange(v1, parser.getMax(), step, results);
}else {
results.add(v1);
}
}
} 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));
}
}

View File

@@ -0,0 +1,23 @@
package cn.hutool.cron.pattern.matcher;
import java.util.List;
/**
* 年匹配<br>
* 考虑年数字太大不适合boolean数组单独使用列表遍历匹配
* @author Looly
*
*/
public class YearValueMatcher implements ValueMatcher{
private List<Integer> valueList;
public YearValueMatcher(List<Integer> intValueList) {
this.valueList = intValueList;
}
@Override
public boolean match(Integer t) {
return valueList.contains(t);
}
}

View File

@@ -0,0 +1,7 @@
/**
* 定时任务表达式匹配器,内部使用
*
* @author looly
*
*/
package cn.hutool.cron.pattern.matcher;

View File

@@ -0,0 +1,7 @@
/**
* 定时任务表达式解析核心为CronPattern
*
* @author looly
*
*/
package cn.hutool.cron.pattern;

View File

@@ -0,0 +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 (value.equalsIgnoreCase("L") || value.equals("32")) {// 每月最后一天
return 32;
} else {
return super.parse(value);
}
}
}

View File

@@ -0,0 +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(value.equalsIgnoreCase("L")){
//最后一天为星期六
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);
}
}

View File

@@ -0,0 +1,14 @@
package cn.hutool.cron.pattern.parser;
/**
* 小时值处理
* @author Looly
*
*/
public class HourValueParser extends SimpleValueParser{
public HourValueParser() {
super(0, 23);
}
}

View File

@@ -0,0 +1,14 @@
package cn.hutool.cron.pattern.parser;
/**
* 分钟值处理
* @author Looly
*
*/
public class MinuteValueParser extends SimpleValueParser{
public MinuteValueParser() {
super(0, 59);
}
}

View File

@@ -0,0 +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);
}
}

View File

@@ -0,0 +1,9 @@
package cn.hutool.cron.pattern.parser;
/**
* 秒值处理
* @author Looly
*
*/
public class SecondValueParser extends MinuteValueParser{
}

View File

@@ -0,0 +1,50 @@
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;
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 {
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;
}
}

View File

@@ -0,0 +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
*/
public int parse(String value);
/**
* 返回最小值
*
* @return 最小值
*/
public int getMin();
/**
* 返回最大值
*
* @return 最大值
*/
public int getMax();
}

View File

@@ -0,0 +1,14 @@
package cn.hutool.cron.pattern.parser;
/**
* 年值处理
* @author Looly
*
*/
public class YearValueParser extends SimpleValueParser{
public YearValueParser() {
super(1970, 2099);
}
}

View File

@@ -0,0 +1,7 @@
/**
* 定时任务表达式解析器,内部使用
*
* @author looly
*
*/
package cn.hutool.cron.pattern.parser;

View File

@@ -0,0 +1,69 @@
package cn.hutool.cron.task;
import java.lang.reflect.Method;
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;
/**
* 反射执行任务<br>
* 通过传入类名#方法名,通过反射执行相应的方法<br>
* 如果是静态方法直接执行,如果是对象方法,需要类有默认的构造方法。
*
* @author Looly
*
*/
public class InvokeTask implements Task{
private Class<?> clazz;
private Object obj;
private 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 !");
}
this.clazz = ClassLoaderUtil.loadClass(className);
if(null == this.clazz) {
throw new IllegalArgumentException("Load class with name of [" + className + "] fail !");
}
this.obj = ReflectUtil.newInstanceIfPossible(this.clazz);
//方法
final String methodName = classNameWithMethodName.substring(splitIndex + 1);
if(StrUtil.isBlank(methodName)) {
throw new IllegalArgumentException("Method name is blank !");
}
this.method = ClassUtil.getPublicMethod(this.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, new Object[]{});
} catch (UtilException e) {
throw new CronException(e.getCause());
}
}
}

View File

@@ -0,0 +1,19 @@
package cn.hutool.cron.task;
/**
* {@link Runnable} 的 {@link Task}包装
* @author Looly
*
*/
public class RunnableTask implements Task{
private Runnable runnable;
public RunnableTask(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void execute() {
runnable.run();
}
}

View File

@@ -0,0 +1,14 @@
package cn.hutool.cron.task;
/**
* 定时作业接口通过实现execute方法执行具体的任务<br>
* @author Looly
*
*/
public interface Task {
/**
* 执行作业
*/
public void execute();
}

View File

@@ -0,0 +1,7 @@
/**
* 定时任务中作业的抽象封装和实现包括Runnable实现和反射实现
*
* @author looly
*
*/
package cn.hutool.cron.task;