Spring定时任务源码分析
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
线程调度配置
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
public class ScheduledAnnotationBeanPostProcessor
implements MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {f
我们重点关注下 MergedBeanDefinitionPostProcessor、SmartInitializingSingleton, ApplicationListener这三个通知器。
MergedBeanDefinitionPostProcessor–>BeanPostProcessor–>postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) {
// AOP原因,可能被代理,这里通过aop工具获取到真实Class
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
// nonAnnotatedClasses集合中只记录没有Scheduled和Schedules注解的对象Class
// !contains如果为true则说明没在集合中,可能是没被加入进来,尝试加入,不符合的话就尝试开启Scheduled对象实例
if (!this.nonAnnotatedClasses.contains(targetClass)) {
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
new MethodIntrospector.MetadataLookup<Set<Scheduled>>() {
@Override
public Set<Scheduled> inspect(Method method) {
Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
}
});
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(targetClass);
if (logger.isTraceEnabled()) {
logger.trace("No @Scheduled annotations found on bean class: " + bean.getClass());
}
}
else {
//循环组件对象(Bean)中包含的@Scheduled方法,并开启
// Non-empty set of methods
for (Map.Entry<Method, Set<Scheduled>> entry : annotatedMethods.entrySet()) {
Method method = entry.getKey();
for (Scheduled scheduled : entry.getValue()) {
processScheduled(scheduled, method, bean);
}
}
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
生成任务Runnable线程,根据注解属性不同(initialDelay、fixedDelay、cron(我们会以此属性来分析,其他两个相对简单)),设置不同的trigger、task,并交给taskScheduler执行。ps:taskScheduler是在SmartInitializingSingleton, ApplicationListener的方法中完成赋值。我们后边再讲。
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
Assert.isTrue(method.getParameterTypes().length == 0,
"Only no-arg methods may be annotated with @Scheduled");
//拿到Bean对象中含有@Scheduled的Method实例 ;bean则是method要invoke时传递的obj method.invoke(obj,args) ;一般定时任务的方法入参是Null。
Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
//创建定时任务Runnable实例(下文中有说明)
Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
Set<ScheduledTask> tasks = new LinkedHashSet<ScheduledTask>(4);
// Determine initial delay
long initialDelay = scheduled.initialDelay();
String initialDelayString = scheduled.initialDelayString();
if (StringUtils.hasText(initialDelayString)) {
Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
if (this.embeddedValueResolver != null) {
initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
}
try {
initialDelay = Long.parseLong(initialDelayString);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into integer");
}
}
// cron 属性的检查 如果cron有值。且合法,
// Check cron expression
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
String zone = scheduled.zone();
if (this.embeddedValueResolver != null) {
cron = this.embeddedValueResolver.resolveStringValue(cron);
zone = this.embeddedValueResolver.resolveStringValue(zone);
}
TimeZone timeZone;
if (StringUtils.hasText(zone)) {
timeZone = StringUtils.parseTimeZoneString(zone);
}
else {
timeZone = TimeZone.getDefault();
}
// 添加 到tasks中,并放入到时org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleCronTask(CronTask task)中,条件适宜的情况下,直接调用runnable执行。
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
// At this point we don't need to differentiate between initial delay set or not anymore
if (initialDelay < 0) {
initialDelay = 0;
}
// Check fixed delay
long fixedDelay = scheduled.fixedDelay();
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
}
String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
if (this.embeddedValueResolver != null) {
fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
}
try {
fixedDelay = Long.parseLong(fixedDelayString);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into integer");
}
tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
}
// Check fixed rate
long fixedRate = scheduled.fixedRate();
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
}
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
if (this.embeddedValueResolver != null) {
fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
}
try {
fixedRate = Long.parseLong(fixedRateString);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");
}
tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
}
// Check whether we had any attribute set
Assert.isTrue(processedSchedule, errorMessage);
// Finally register the scheduled tasks
synchronized (this.scheduledTasks) {
Set<ScheduledTask> registeredTasks = this.scheduledTasks.get(bean);
if (registeredTasks == null) {
registeredTasks = new LinkedHashSet<ScheduledTask>(4);
this.scheduledTasks.put(bean, registeredTasks);
}
registeredTasks.addAll(tasks);
}
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException(
"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
}
}
定时方法任务的Runnable对象,传入bean实例(target)和bean中使用@Scheduled注解的方法(method),由taskScheduler开启执行。
public class ScheduledMethodRunnable implements Runnable {
private final Object target;
private final Method method;
//传入bean实例(target)和bean中使用@Scheduled注解的方法(method)
public ScheduledMethodRunnable(Object target, Method method) {
this.target = target;
this.method = method;
}
public ScheduledMethodRunnable(Object target, String methodName) throws NoSuchMethodException {
this.target = target;
this.method = target.getClass().getMethod(methodName);
}
public Object getTarget() {
return this.target;
}
public Method getMethod() {
return this.method;
}
//taskScheduler会调用此处
@Override
public void run() {
try {
ReflectionUtils.makeAccessible(this.method);
this.method.invoke(this.target);
}
catch (InvocationTargetException ex) {
ReflectionUtils.rethrowRuntimeException(ex.getTargetException());
}
catch (IllegalAccessException ex) {
throw new UndeclaredThrowableException(ex);
}
}
}
如果未处理的任务集合中有,且taskScheduler不为空,直接执行,否则依旧放入未处理的任务集合中。ps:从postProcessAfterInitialization过来时,taskScheduler为空。
public ScheduledTask scheduleCronTask(CronTask task) {
ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
boolean newTask = false;
if (scheduledTask == null) {
scheduledTask = new ScheduledTask();
newTask = true;
}
if (this.taskScheduler != null) {
scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
}
else {
addCronTask(task);
this.unresolvedTasks.put(task, scheduledTask);
}
return (newTask ? scheduledTask : null);
}
一般情况下,因为继承了ApplicationContext接口,applicationContext不为空,不会执行finishRegistration();方法
public void afterSingletonsInstantiated() {
// Remove resolved singleton classes from cache
this.nonAnnotatedClasses.clear();
if (this.applicationContext == null) {
// Not running in an ApplicationContext -> register tasks early...
finishRegistration();
}
}
ApplicationEvent的refresh事件触发这里的操作,执行finishRegistration();方法。
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.applicationContext) {
// Running in an ApplicationContext -> register tasks this late...
// giving other ContextRefreshedEvent listeners a chance to perform
// their work at the same time (e.g. Spring Batch's job registration).
finishRegistration();
}
}
注入TaskScheduler,并调用ScheduledTaskRegistrar的afterProperties方法开启一些未处理的定时任务。
private void finishRegistration() {
//第一次进来this.scheduler为空
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
}
if (this.beanFactory instanceof ListableBeanFactory) {
Map<String, SchedulingConfigurer> beans =
((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
List<SchedulingConfigurer> configurers = new ArrayList<SchedulingConfigurer>(beans.values());
AnnotationAwareOrderComparator.sort(configurers);
for (SchedulingConfigurer configurer : configurers) {
configurer.configureTasks(this.registrar);
}
}
// 有task(cron、fixed任务等)且scheduler为空时
if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
try {
// 如果定义的有TaskScheduler类型的Bean,会直接设置进来(根据名字查),后边会根据类型查,再没有就查找ScheduledExecutorService的name和类型的
this.registrar.setTaskScheduler(resolveSchedulerBean(TaskScheduler.class, false));
}
catch (NoUniqueBeanDefinitionException ex) {
try {
this.registrar.setTaskScheduler(resolveSchedulerBean(TaskScheduler.class, true));
}
catch (NoSuchBeanDefinitionException ex2) {
}
}
catch (NoSuchBeanDefinitionException ex) {
// Search for ScheduledExecutorService bean next...
try {
this.registrar.setScheduler(resolveSchedulerBean(ScheduledExecutorService.class, false));
}
catch (NoUniqueBeanDefinitionException ex2) {
try {
this.registrar.setScheduler(resolveSchedulerBean(ScheduledExecutorService.class, true));
}
catch (NoSuchBeanDefinitionException ex3) {
}
}
catch (NoSuchBeanDefinitionException ex2) {
}
}
}
//最后执行 ScheduledTaskRegistrar的afterPropertiesSet方法
this.registrar.afterPropertiesSet();
}
public void afterPropertiesSet() {
scheduleTasks();
}
protected void scheduleTasks() {
//如果为空 则自建处理器
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
if (this.triggerTasks != null) {
for (TriggerTask task : this.triggerTasks) {
addScheduledTask(scheduleTriggerTask(task));
}
}
//处理cron类型的任务
if (this.cronTasks != null) {
for (CronTask task : this.cronTasks) {
addScheduledTask(scheduleCronTask(task));
}
}
if (this.fixedRateTasks != null) {
for (IntervalTask task : this.fixedRateTasks) {
addScheduledTask(scheduleFixedRateTask(task));
}
}
if (this.fixedDelayTasks != null) {
for (IntervalTask task : this.fixedDelayTasks) {
addScheduledTask(scheduleFixedDelayTask(task));
}
}
}
又回到了这里,会执行this.taskScheduler.schedule(task.getRunnable(), task.getTrigger())逻辑。开启任务完成!!!
if (this.taskScheduler != null) {
scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
}
回到 ScheduledAnnotationBeanPostProcessor#processScheduled,我们来看下CronTrigger的逻辑。
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
我们看下CronSequenceGenerator是怎么解析cron表达式的
public CronTrigger(String expression, TimeZone timeZone) {
this.sequenceGenerator = new CronSequenceGenerator(expression, timeZone);
}
下边我们一层层找解析过程。最后发现,cron的前六位依次为 “秒 分 时 天 月 星期占位”。使用说明如下。
private final BitSet months = new BitSet(12);
private final BitSet daysOfMonth = new BitSet(31);
private final BitSet daysOfWeek = new BitSet(7);
private final BitSet hours = new BitSet(24);
private final BitSet minutes = new BitSet(60);
private final BitSet seconds = new BitSet(60);
public CronSequenceGenerator(String expression, TimeZone timeZone) {
this.expression = expression;
this.timeZone = timeZone;
parse(expression);
}
public CronSequenceGenerator(String expression, TimeZone timeZone) {
this.expression = expression;
this.timeZone = timeZone;
parse(expression);
}
// 核心解析过程 注意看 cron的前六位依次对应到时为 “秒 分 时 天 月 星期占位”的bitSet对象中。
private void doParse(String[] fields) {
setNumberHits(this.seconds, fields[0], 0, 60);
setNumberHits(this.minutes, fields[1], 0, 60);
setNumberHits(this.hours, fields[2], 0, 24);
setDaysOfMonth(this.daysOfMonth, fields[3]);
setMonths(this.months, fields[4]);
setDays(this.daysOfWeek, replaceOrdinals(fields[5], "SUN,MON,TUE,WED,THU,FRI,SAT"), 8);
if (this.daysOfWeek.get(7)) {
// Sunday can be represented as 0 or 7
this.daysOfWeek.set(0);
this.daysOfWeek.clear(7);
}
}
// 解析星期相关值(支持0-8,?,*等)
private void setDays(BitSet bits, String field, int max) {
if (field.contains("?")) {
field = "*";
}
setNumberHits(bits, field, 0, max);
}
private void setNumberHits(BitSet bits, String value, int min, int max) {
String[] fields = StringUtils.delimitedListToStringArray(value, ",");
for (String field : fields) {
if (!field.contains("/")) {
// Not an incrementer so it must be a range (possibly empty)
int[] range = getRange(field, min, max);
bits.set(range[0], range[1] + 1);
}
else {
String[] split = StringUtils.delimitedListToStringArray(field, "/");
if (split.length > 2) {
throw new IllegalArgumentException("Incrementer has more than two fields: '" +
field + "' in expression \"" + this.expression + "\"");
}
int[] range = getRange(split[0], min, max);
if (!split[0].contains("-")) {
range[1] = max - 1;
}
int delta = Integer.parseInt(split[1]);
if (delta <= 0) {
throw new IllegalArgumentException("Incrementer delta must be 1 or higher: '" +
field + "' in expression \"" + this.expression + "\"");
}
for (int i = range[0]; i <= range[1]; i += delta) {
bits.set(i);
}
}
}
}
private int[] getRange(String field, int min, int max) {
int[] result = new int[2];
if (field.contains("*")) {
result[0] = min;
result[1] = max - 1;
return result;
}
if (!field.contains("-")) {
result[0] = result[1] = Integer.valueOf(field);
}
else {
String[] split = StringUtils.delimitedListToStringArray(field, "-");
if (split.length > 2) {
throw new IllegalArgumentException("Range has more than two fields: '" +
field + "' in expression \"" + this.expression + "\"");
}
result[0] = Integer.valueOf(split[0]);
result[1] = Integer.valueOf(split[1]);
}
if (result[0] >= max || result[1] >= max) {
throw new IllegalArgumentException("Range exceeds maximum (" + max + "): '" +
field + "' in expression \"" + this.expression + "\"");
}
if (result[0] < min || result[1] < min) {
throw new IllegalArgumentException("Range less than minimum (" + min + "): '" +
field + "' in expression \"" + this.expression + "\"");
}
if (result[0] > result[1]) {
throw new IllegalArgumentException("Invalid inverted range: '" + field +
"' in expression \"" + this.expression + "\"");
}
return result;
}
this.sequenceGenerator.next(date)
定时任务最终会调用此处,此处使用的逻辑为你在注解中cron给定的值 。
public Date next(Date date) {
/*
The plan:
1 Start with whole second (rounding up if necessary)
2 If seconds match move on, otherwise find the next match:
2.1 If next match is in the next minute then roll forwards
3 If minute matches move on, otherwise find the next match
3.1 If next match is in the next hour then roll forwards
3.2 Reset the seconds and go to 2
4 If hour matches move on, otherwise find the next match
4.1 If next match is in the next day then roll forwards,
4.2 Reset the minutes and seconds and go to 2
*/
Calendar calendar = new GregorianCalendar();
calendar.setTimeZone(this.timeZone);
calendar.setTime(date);
// First, just reset the milliseconds and try to calculate from there...
calendar.set(Calendar.MILLISECOND, 0);
long originalTimestamp = calendar.getTimeInMillis();
doNext(calendar, calendar.get(Calendar.YEAR));
if (calendar.getTimeInMillis() == originalTimestamp) {
// We arrived at the original timestamp - round up to the next whole second and try again...
calendar.add(Calendar.SECOND, 1);
doNext(calendar, calendar.get(Calendar.YEAR));
}
return calendar.getTime();
}
分析使用的Spring版本为4.3.18。代码上如有出入,请自行区分。最后如有不对,欢迎指正。 作者:ricky QQ交流群:244930845。