cn.hutool.cron.Scheduler Maven / Gradle / Ivy
package cn.hutool.cron;
import cn.hutool.core.map.MapUtil;
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;
/**
* 任务调度器
*
* 调度器启动流程:
*
*
* 启动Timer =》 启动TaskLauncher =》 启动TaskExecutor
*
*
* 调度器关闭流程:
*
*
* 关闭Timer =》 关闭所有运行中的TaskLauncher =》 关闭所有运行中的TaskExecutor
*
*
* 其中:
*
*
* TaskLauncher:定时器每分钟调用一次(如果{@link Scheduler#isMatchSecond()}为{@code true}每秒调用一次),
* 负责检查TaskTable是否有匹配到此时间运行的Task
*
*
*
* TaskExecutor:TaskLauncher匹配成功后,触发TaskExecutor执行具体的作业,执行完毕销毁
*
*
* @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();
}
/**
* 设置是否为守护线程
* 如果为true,则在调用{@link #stop()}方法后执行的定时任务立即结束,否则等待执行完毕才结束。默认非守护线程
* 如果用户调用{@link #setThreadExecutor(ExecutorService)}自定义线程池则此参数无效
*
* @param on {@code true}为守护线程,否则非守护线程
* @return this
* @throws CronException 定时任务已经启动抛出此异常
*/
public Scheduler setDaemon(boolean on) throws CronException {
lock.lock();
try {
checkStarted();
this.daemon = on;
} finally {
lock.unlock();
}
return this;
}
/**
* 设置自定义线程池
* 自定义线程池时须考虑方法执行的线程是否为守护线程
*
* @param threadExecutor 自定义线程池
* @return this
* @throws CronException 定时任务已经启动抛出此异常
* @since 5.7.10
*/
public Scheduler setThreadExecutor(ExecutorService threadExecutor) throws CronException {
lock.lock();
try {
checkStarted();
this.threadExecutor = threadExecutor;
} finally {
lock.unlock();
}
return this;
}
/**
* 是否为守护线程
*
* @return 是否为守护线程
*/
public boolean isDaemon() {
return this.daemon;
}
/**
* 是否支持秒匹配
*
* @return {@code true}使用,{@code false}不使用
*/
public boolean isMatchSecond() {
return this.config.isMatchSecond();
}
/**
* 设置是否支持秒匹配,默认不使用
*
* @param isMatchSecond {@code true}支持,{@code false}不支持
* @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
/**
* 批量加入配置文件中的定时任务
* 配置文件格式为: xxx.xxx.xxx.Class.method = * * * * *
*
* @param cronSetting 定时任务设置文件
* @return this
*/
public Scheduler schedule(Setting cronSetting) {
if (MapUtil.isNotEmpty(cronSetting)) {
String group;
for (Entry> groupedEntry : cronSetting.getGroupedMap().entrySet()) {
group = groupedEntry.getKey();
for (Entry 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 {
// issue#I5E7BM@Gitee,自定义ID避免重复从配置文件加载
schedule("id_" + jobClass, 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) {
descheduleWithStatus(id);
return this;
}
/**
* 移除Task,并返回是否移除成功
*
* @param id Task的ID
* @return 是否移除成功,{@code false}表示未找到对应ID的任务
* @since 5.7.17
*/
public boolean descheduleWithStatus(String id) {
return this.taskTable.remove(id);
}
/**
* 更新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 {
checkStarted();
if(null == this.threadExecutor){
// 无界线程池,确保每一个需要执行的线程都可以及时运行,同时复用已有线程避免线程重复创建
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;
}
/**
* 停止定时任务
* 此方法调用后会将定时器进程立即结束,如果为守护线程模式,则正在执行的作业也会自动结束,否则作业线程将在执行完成后结束。
* 此方法并不会清除任务表中的任务,请调用{@link #clear()} 方法清空任务或者使用{@link #stop(boolean)}方法可选是否清空
*
* @return this
*/
public Scheduler stop() {
return stop(false);
}
/**
* 停止定时任务
* 此方法调用后会将定时器进程立即结束,如果为守护线程模式,则正在执行的作业也会自动结束,否则作业线程将在执行完成后结束。
*
* @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;
}
/**
* 检查定时任务是否已经启动
*
* @throws CronException 已经启动则抛出此异常
*/
private void checkStarted() throws CronException{
if (this.started) {
throw new CronException("Scheduler already started!");
}
}
}