1、基础

一、例子

下面是一个简单的例子,它表示每分钟执行一次


<dependency>
    <groupId>it.sauronsoftware.cron4j</groupId>
    <artifactId>cron4j</artifactId>
    <version>2.2.5</version>
</dependency>

public class Test {

    public static void main(String[] args) {
        //创建调度器
        Scheduler scheduler = new Scheduler();
        //设置需要调度的任务
        scheduler.schedule("*/1 * * * *", new Task() {
            @Override
            public void execute(TaskExecutionContext context) throws RuntimeException {
                System.out.println("hello world!");
            }
        });
        //启动调度
        scheduler.start();

    }

}

cron4j的cron表达式分别表示 分 时 日 月 周

二、类介绍

2.1 TaskCollector

/**
 * @author Carlo Pelliccia
 * @since 2.0
 */
public interface TaskCollector {

	public TaskTable getTasks();
}

TaskCollector是一个接口,它表示任务收集器,顾名思义就是存储多个需要调度任务的储存器,接口方法getTasks用于获取任务,内部通过两个list集合分别记录cron表达式和
具体的Task,它有两个实现MemoryTaskCollector,FileTaskCollector,其中FileTaskCollector中储存的是File对象,FileTaskCollector我们不会去研究它,我们主要研究一
下MemoryTaskCollector

//表示任务个数
private int size = 0;

//cron表达式
private ArrayList patterns = new ArrayList();

//Task列表
private ArrayList tasks = new ArrayList();

//cron4j生成的唯一id
private ArrayList ids = new ArrayList();

public synchronized String add(SchedulingPattern pattern, Task task) {
    //生成唯一id
	String id = GUIDGenerator.generate();
	//加入任务
	patterns.add(pattern);
	tasks.add(task);
	ids.add(id);
	return id;
}

集合patterns,tasks,tasks是通过索引下标一一对应的,其实我个人觉得可以封装成一个对象

2.2 TaskTable

//模式集合,用于存储cron表达式
private ArrayList patterns = new ArrayList();

//任务集合,用于存储任务对象
private ArrayList tasks = new ArrayList();

//获取SchedulingPattern,SchedulingPattern是用于解析了cron表达式的调度匹配模式
public SchedulingPattern getSchedulingPattern(int index)
			throws IndexOutOfBoundsException {
	return (SchedulingPattern) patterns.get(index);
}

2.3 ValueParser

//将cron表达式中解析出来的时间值转化成int
public int parse(String value) throws Exception;

//获取对应时间的最小值,比如分钟解析器,那么最小值就是0
public int getMinValue();

//获取对应时间的最大值,比如分钟的59,小时的23
public int getMaxValue();

对应的实现类目前有MinuteValueParser,HourValueParser,DayOfMonthValueParser,MonthValueParser,DayOfWeekValueParser
具体的实现比较简单,此处不做过多的介绍

2.4 SchedulingPattern

//用于解析分钟的解析器
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();

//被解析的原始cron表达式
private String asString;

//记录用于匹配分的匹配器
protected ArrayList minuteMatchers = new ArrayList();

//记录用于匹配小时的匹配器
protected ArrayList hourMatchers = new ArrayList();

//记录用于匹配日期的匹配器
protected ArrayList dayOfMonthMatchers = new ArrayList();

//用于匹配月份的匹配器
protected ArrayList monthMatchers = new ArrayList();

//用于匹配周的匹配器
protected ArrayList dayOfWeekMatchers = new ArrayList();

2.4.1 构造器
//pattern是传入的cron表达式
public SchedulingPattern(String pattern) throws InvalidPatternException {
	this.asString = pattern;
	//多个cron表达式通过 | 分割,表示或的关系,只要满足其中一个cron表达式即可
	StringTokenizer st1 = new StringTokenizer(pattern, "|");
	if (st1.countTokens() < 1) {
		throw new InvalidPatternException("invalid pattern: \"" + pattern + "\"");
	}
	while (st1.hasMoreTokens()) {
		String localPattern = st1.nextToken();
		//cron表达式可以通过空格和table符分割,比如 */1 * * * *
		StringTokenizer st2 = new StringTokenizer(localPattern, " \t");
		//必须要有5个时间值,分 时 日 月 周
		if (st2.countTokens() != 5) {
			throw new InvalidPatternException("invalid pattern: \"" + localPattern + "\"");
		}
		try {
		    //使用分钟解析器解析 分 
			minuteMatchers.add(buildValueMatcher(st2.nextToken(), MINUTE_VALUE_PARSER));
		} catch (Exception e) {
			throw new InvalidPatternException("invalid pattern \""
					+ localPattern + "\". Error parsing minutes field: "
					+ e.getMessage() + ".");
		}
		try {
		    //使用小时解析器解析 时 
			hourMatchers.add(buildValueMatcher(st2.nextToken(), HOUR_VALUE_PARSER));
		} catch (Exception e) {
			throw new InvalidPatternException("invalid pattern \""
					+ localPattern + "\". Error parsing hours field: "
					+ e.getMessage() + ".");
		}
		try {
		    //使用日期解析器解析 日期
			dayOfMonthMatchers.add(buildValueMatcher(st2.nextToken(), DAY_OF_MONTH_VALUE_PARSER));
		} catch (Exception e) {
			throw new InvalidPatternException("invalid pattern \""
					+ localPattern
					+ "\". Error parsing days of month field: "
					+ e.getMessage() + ".");
		}
		try {
		    //使用月份解析器解析 月份
			monthMatchers.add(buildValueMatcher(st2.nextToken(), MONTH_VALUE_PARSER));
		} catch (Exception e) {
			throw new InvalidPatternException("invalid pattern \""
					+ localPattern + "\". Error parsing months field: "
					+ e.getMessage() + ".");
		}
		try {
		    //使用周解析器解析 周
			dayOfWeekMatchers.add(buildValueMatcher(st2.nextToken(), DAY_OF_WEEK_VALUE_PARSER));
		} catch (Exception e) {
			throw new InvalidPatternException("invalid pattern \""
					+ localPattern
					+ "\". Error parsing days of week field: "
					+ e.getMessage() + ".");
		}
		//匹配器个数,这个个数指的是cron表达式的个数 */1 * * * * | */2 * * * * 表示两个
		matcherSize++;
	}
}

SchedulingPattern的构造器将传入的cron表达式进行解析,把 分 时 日 月 周 封装到对应类型的匹配器中,以便后面进行匹配

2.4.2 构建匹配器

从SchedulingPattern的构造器中我们可以看到,分 时 日 月 周 各个时间字段的解析都是通过一个叫做的buildValueMatcher的方法进行处理的

private ValueMatcher buildValueMatcher(String str, ValueParser parser)
			throws Exception {
	//如果当前时间字段只有一个字符并且是*的话,那么表示匹配任意时间
	//直接返回一个总是匹配的matcher
	if (str.length() == 1 && str.equals("*")) {
		return new AlwaysTrueValueMatcher();
	}
	
	ArrayList values = new ArrayList();
	//如果不是*,那么可能是逗号分割的时间值,现在我们假设要解析的cron表达式为 1-6,6-0 1,23 * * *
	//这个表达式表示每天的凌晨1点和23点的每分钟执行
	StringTokenizer st = new StringTokenizer(str, ",");
	while (st.hasMoreTokens()) {
	    //假设此时我们解析的是分这个字段,也就是 1-6,6-0
	    //那么此时获取的element的值为1-6
		String element = st.nextToken();
		ArrayList local;
		try {
		    //解析1-6获取到一个范围集合 1,2,3,4,5,6
			local = parseListElement(element, parser);
		} catch (Exception e) {
			throw new Exception("invalid field \"" + str
					+ "\", invalid element \"" + element + "\", "
					+ e.getMessage());
		}
		//循环将上面1-6范围的时间值合并到大的范围集合,如果把分这个时间字段全部进行解析了之后
		//values集合的值应为 0 - 59
		for (Iterator i = local.iterator(); i.hasNext();) {
			Object value = i.next();
			//排除重复的,比如第一次解析1-6时,values集合中已经存在一个6了,那么下次解析6-0时就不会将6添加进去
			if (!values.contains(value)) {
				values.add(value);
			}
		}
	}
	if (values.size() == 0) {
		throw new Exception("invalid field \"" + str + "\"");
	}
	//日期的与其他的匹配器稍微不同,因为存在闰年与平年之分
	if (parser == DAY_OF_MONTH_VALUE_PARSER) {
		return new DayOfMonthValueMatcher(values);
	} else {
	    //构建匹配器
		return new IntArrayValueMatcher(values);
	}
}
2.4.3 解析时间范围
private ArrayList parseListElement(String str, ValueParser parser)
			throws Exception {
	//时间字段可能存在/,表示每隔多少时间执行一次
	StringTokenizer st = new StringTokenizer(str, "/");
	int size = st.countTokens();
	if (size < 1 || size > 2) {
		throw new Exception("syntax error");
	}
	ArrayList values;
	try {
		values = parseRange(st.nextToken(), parser);
	} catch (Exception e) {
		throw new Exception("invalid range, " + e.getMessage());
	}
	//如果有两个元素,那么表示存在 /
	if (size == 2) {
		String dStr = st.nextToken();
		int div;
		try {
		    //解析 / 后面的值
			div = Integer.parseInt(dStr);
		} catch (NumberFormatException e) {
			throw new Exception("invalid divisor \"" + dStr + "\"");
		}
		if (div < 1) {
			throw new Exception("non positive divisor \"" + div + "\"");
		}
		ArrayList values2 = new ArrayList();
		//每隔div执行一次,将符合的时间存入,然后返回values2集合
		for (int i = 0; i < values.size(); i += div) {
			values2.add(values.get(i));
		}
		return values2;
	} else {
		return values;
	}
}

上面这个方法将时间字段解析成范围,通过list存储,如果存在 / (表示没隔多少就执行一次),那么将范围值通过/后面这个值进行间隔,将间隔后的集合返回,下面我们再
看看是怎么解析范围值的

private ArrayList parseRange(String str, ValueParser parser)
			throws Exception {
	//如果是 * ,表示任意值
	if (str.equals("*")) {
	    //范围为 min - max,如果是 小时解析器,那么 就是 0 - 23
		int min = parser.getMinValue();
		int max = parser.getMaxValue();
		ArrayList values = new ArrayList();
		for (int i = min; i <= max; i++) {
			values.add(new Integer(i));
		}
		return values;
	}
	//时间字段可能是 - 分割的
	StringTokenizer st = new StringTokenizer(str, "-");
	int size = st.countTokens();
	if (size < 1 || size > 2) {
		throw new Exception("syntax error");
	}
	String v1Str = st.nextToken();
	int v1;
	try {
	    //解析为int
		v1 = parser.parse(v1Str);
	} catch (Exception e) {
		throw new Exception("invalid value \"" + v1Str + "\", "
				+ e.getMessage());
	}
	//size等于表示当前时间字段不是通过 - 分割的
	if (size == 1) {
		ArrayList values = new ArrayList();
		values.add(new Integer(v1));
		return values;
	} else {
		String v2Str = st.nextToken();
		int v2;
		try {
		    //解析 - 后面的值
			v2 = parser.parse(v2Str);
		} catch (Exception e) {
			throw new Exception("invalid value \"" + v2Str + "\", "
					+ e.getMessage());
		}
		ArrayList values = new ArrayList();
		//范围为 v1 - v2
		if (v1 < v2) {
			for (int i = v1; i <= v2; i++) {
				values.add(new Integer(i));
			}
			//如果v1大于v2,那么表示 v1 - max , min - v2
		} else if (v1 > v2) {
			int min = parser.getMinValue();
			int max = parser.getMaxValue();
			for (int i = v1; i <= max; i++) {
				values.add(new Integer(i));
			}
			for (int i = min; i <= v2; i++) {
				values.add(new Integer(i));
			}
		} else {
			// v1 == v2
			values.add(new Integer(v1));
		}
		return values;
	}
}

2.5 ValueMatcher

用于匹配值

public boolean match(int value);

下面分析下 ValueMatcher其中的一个实现IntArrayValueMatcher

class IntArrayValueMatcher implements ValueMatcher {

	//这个就是SchedulingPattern解析后的时间范围
	private int[] values;
        
    //。。。。。。省略部分代码
	
	public boolean match(int value) {
		for (int i = 0; i < values.length; i++) {
			if (values[i] == value) {
				return true;
			}
		}
		return false;
	}

}

只要对应的时间字段能够匹配到上面范围内的值,那么返回true

看到这我想象大家大概可以猜到它是怎么匹配表达式的了吧,就是获取一个时间的 分 时 日 月 周这5个时间去匹配对应时间类型的范围中是否包含

2.6 Scheduler

//生成唯一id
private String guid = GUIDGenerator.generate();

//时间区域
private TimeZone timezone = null;

//调度线程是否设置为守护线程
private boolean daemon = false;

//表示调度是否已经开始,如果没有开始,任务不能被执行
private boolean started = false;

//用于记录收集器的集合
private ArrayList collectors = new ArrayList();

//内存收集器,通常我们使用的就是这个收集器
private MemoryTaskCollector memoryTaskCollector = new MemoryTaskCollector();

//文件任务收集器,用于存放File对象
private FileTaskCollector fileTaskCollector = new FileTaskCollector();

//用于注册监听器
private ArrayList listeners = new ArrayList();

//检查时间匹配线程,在cron4j中由于cron表达式最小的时候为分,那么这个线程每分钟检查一次
private TimerThread timer = null;

//当前正在启动的执行任务的线程
private ArrayList launchers = null;

//记录执行任务及上下文的集合
private ArrayList executors = null;

//用于同步的对象
private Object lock = new Object();

Scheduler是启动调度任务的入口,我们在下一节分析其具体的启动过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值