@Scheduled provides String variants of fixedDelay, fixedRate, initialDelay for placeholder support
Issue: SPR-8067
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -64,18 +64,41 @@ public @interface Scheduled {
|
||||
*/
|
||||
long fixedDelay() default -1;
|
||||
|
||||
/**
|
||||
* Execute the annotated method with a fixed period between the end
|
||||
* of the last invocation and the start of the next.
|
||||
* @return the delay in milliseconds as a String value, e.g. a placeholder
|
||||
* @since 3.2.2
|
||||
*/
|
||||
String fixedDelayString() default "";
|
||||
|
||||
/**
|
||||
* Execute the annotated method with a fixed period between invocations.
|
||||
* @return the period in milliseconds
|
||||
*/
|
||||
long fixedRate() default -1;
|
||||
|
||||
/**
|
||||
* Execute the annotated method with a fixed period between invocations.
|
||||
* @return the period in milliseconds as a String value, e.g. a placeholder
|
||||
* @since 3.2.2
|
||||
*/
|
||||
String fixedRateString() default "";
|
||||
|
||||
/**
|
||||
* Number of milliseconds to delay before the first execution of a
|
||||
* {@link #fixedRate()} or {@link #fixedDelay()} task.
|
||||
* @return the initial delay in milliseconds
|
||||
* @since 3.2
|
||||
*/
|
||||
long initialDelay() default 0;
|
||||
long initialDelay() default -1;
|
||||
|
||||
/**
|
||||
* Number of milliseconds to delay before the first execution of a
|
||||
* {@link #fixedRate()} or {@link #fixedDelay()} task.
|
||||
* @return the initial delay in milliseconds as a String value, e.g. a placeholder
|
||||
* @since 3.2.2
|
||||
*/
|
||||
String initialDelayString() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.scheduling.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -111,53 +110,115 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
Scheduled annotation = AnnotationUtils.getAnnotation(method, Scheduled.class);
|
||||
if (annotation != null) {
|
||||
Assert.isTrue(void.class.equals(method.getReturnType()),
|
||||
"Only void-returning methods may be annotated with @Scheduled.");
|
||||
Assert.isTrue(method.getParameterTypes().length == 0,
|
||||
"Only no-arg methods may be annotated with @Scheduled.");
|
||||
if (AopUtils.isJdkDynamicProxy(bean)) {
|
||||
try {
|
||||
// found a @Scheduled method on the target class for this JDK proxy -> is it
|
||||
// also present on the proxy itself?
|
||||
method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
|
||||
try {
|
||||
Assert.isTrue(void.class.equals(method.getReturnType()),
|
||||
"Only void-returning methods may be annotated with @Scheduled");
|
||||
Assert.isTrue(method.getParameterTypes().length == 0,
|
||||
"Only no-arg methods may be annotated with @Scheduled");
|
||||
if (AopUtils.isJdkDynamicProxy(bean)) {
|
||||
try {
|
||||
// found a @Scheduled method on the target class for this JDK proxy -> is it
|
||||
// also present on the proxy itself?
|
||||
method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
|
||||
}
|
||||
catch (SecurityException ex) {
|
||||
ReflectionUtils.handleReflectionException(ex);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"@Scheduled method '%s' found on bean target class '%s', " +
|
||||
"but not found in any interface(s) for bean JDK proxy. Either " +
|
||||
"pull the method up to an interface or switch to subclass (CGLIB) " +
|
||||
"proxies by setting proxy-target-class/proxyTargetClass " +
|
||||
"attribute to 'true'", method.getName(), targetClass.getSimpleName()));
|
||||
}
|
||||
}
|
||||
catch (SecurityException ex) {
|
||||
ReflectionUtils.handleReflectionException(ex);
|
||||
Runnable runnable = new ScheduledMethodRunnable(bean, method);
|
||||
boolean processedSchedule = false;
|
||||
String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
|
||||
// Determine initial delay
|
||||
long initialDelay = annotation.initialDelay();
|
||||
String initialDelayString = annotation.initialDelayString();
|
||||
if (!"".equals(initialDelayString)) {
|
||||
Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
|
||||
if (embeddedValueResolver != null) {
|
||||
initialDelayString = embeddedValueResolver.resolveStringValue(initialDelayString);
|
||||
}
|
||||
try {
|
||||
initialDelay = Integer.parseInt(initialDelayString);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into integer");
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"@Scheduled method '%s' found on bean target class '%s', " +
|
||||
"but not found in any interface(s) for bean JDK proxy. Either " +
|
||||
"pull the method up to an interface or switch to subclass (CGLIB) " +
|
||||
"proxies by setting proxy-target-class/proxyTargetClass " +
|
||||
"attribute to 'true'", method.getName(), targetClass.getSimpleName()));
|
||||
// Check cron expression
|
||||
String cron = annotation.cron();
|
||||
if (!"".equals(cron)) {
|
||||
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
|
||||
processedSchedule = true;
|
||||
if (embeddedValueResolver != null) {
|
||||
cron = embeddedValueResolver.resolveStringValue(cron);
|
||||
}
|
||||
registrar.addCronTask(new CronTask(runnable, cron));
|
||||
}
|
||||
}
|
||||
Runnable runnable = new ScheduledMethodRunnable(bean, method);
|
||||
boolean processedSchedule = false;
|
||||
String errorMessage = "Exactly one of the 'cron', 'fixedDelay', or 'fixedRate' attributes is required.";
|
||||
String cron = annotation.cron();
|
||||
if (!"".equals(cron)) {
|
||||
processedSchedule = true;
|
||||
if (embeddedValueResolver != null) {
|
||||
cron = embeddedValueResolver.resolveStringValue(cron);
|
||||
// At this point we don't need to differentiate between initial delay set or not anymore
|
||||
if (initialDelay < 0) {
|
||||
initialDelay = 0;
|
||||
}
|
||||
registrar.addCronTask(new CronTask(runnable, cron));
|
||||
// Check fixed delay
|
||||
long fixedDelay = annotation.fixedDelay();
|
||||
if (fixedDelay >= 0) {
|
||||
Assert.isTrue(!processedSchedule, errorMessage);
|
||||
processedSchedule = true;
|
||||
registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
|
||||
}
|
||||
String fixedDelayString = annotation.fixedDelayString();
|
||||
if (!"".equals(fixedDelayString)) {
|
||||
Assert.isTrue(!processedSchedule, errorMessage);
|
||||
processedSchedule = true;
|
||||
if (embeddedValueResolver != null) {
|
||||
fixedDelayString = embeddedValueResolver.resolveStringValue(fixedDelayString);
|
||||
}
|
||||
try {
|
||||
fixedDelay = Integer.parseInt(fixedDelayString);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into integer");
|
||||
}
|
||||
registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
|
||||
}
|
||||
// Check fixed rate
|
||||
long fixedRate = annotation.fixedRate();
|
||||
if (fixedRate >= 0) {
|
||||
Assert.isTrue(!processedSchedule, errorMessage);
|
||||
processedSchedule = true;
|
||||
registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
|
||||
}
|
||||
String fixedRateString = annotation.fixedRateString();
|
||||
if (!"".equals(fixedRateString)) {
|
||||
Assert.isTrue(!processedSchedule, errorMessage);
|
||||
processedSchedule = true;
|
||||
if (embeddedValueResolver != null) {
|
||||
fixedRateString = embeddedValueResolver.resolveStringValue(fixedRateString);
|
||||
}
|
||||
try {
|
||||
fixedRate = Integer.parseInt(fixedRateString);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");
|
||||
}
|
||||
registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
|
||||
}
|
||||
// Check whether we had any attribute set
|
||||
Assert.isTrue(processedSchedule, errorMessage);
|
||||
}
|
||||
long initialDelay = annotation.initialDelay();
|
||||
long fixedDelay = annotation.fixedDelay();
|
||||
if (fixedDelay >= 0) {
|
||||
Assert.isTrue(!processedSchedule, errorMessage);
|
||||
processedSchedule = true;
|
||||
registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new IllegalStateException(
|
||||
"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
|
||||
}
|
||||
long fixedRate = annotation.fixedRate();
|
||||
if (fixedRate >= 0) {
|
||||
Assert.isTrue(!processedSchedule, errorMessage);
|
||||
processedSchedule = true;
|
||||
registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
|
||||
}
|
||||
Assert.isTrue(processedSchedule, errorMessage);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -168,18 +229,14 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
if (event.getApplicationContext() != this.applicationContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, SchedulingConfigurer> configurers =
|
||||
this.applicationContext.getBeansOfType(SchedulingConfigurer.class);
|
||||
|
||||
if (this.scheduler != null) {
|
||||
this.registrar.setScheduler(this.scheduler);
|
||||
}
|
||||
|
||||
for (SchedulingConfigurer configurer : configurers.values()) {
|
||||
configurer.configureTasks(this.registrar);
|
||||
}
|
||||
|
||||
if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
|
||||
Map<String, ? super Object> schedulers = new HashMap<String, Object>();
|
||||
schedulers.putAll(applicationContext.getBeansOfType(TaskScheduler.class));
|
||||
@@ -199,7 +256,6 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
"configureTasks() callback. Found the following beans: " + schedulers.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
this.registrar.afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -44,8 +44,8 @@ public class IntervalTask extends Task {
|
||||
*/
|
||||
public IntervalTask(Runnable runnable, long interval, long initialDelay) {
|
||||
super(runnable);
|
||||
this.initialDelay = initialDelay;
|
||||
this.interval = interval;
|
||||
this.initialDelay = initialDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,10 +59,11 @@ public class IntervalTask extends Task {
|
||||
|
||||
|
||||
public long getInterval() {
|
||||
return interval;
|
||||
return this.interval;
|
||||
}
|
||||
|
||||
public long getInitialDelay() {
|
||||
return initialDelay;
|
||||
return this.initialDelay;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -69,6 +69,7 @@ public class CronSequenceGenerator {
|
||||
|
||||
private final TimeZone timeZone;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a {@link CronSequenceGenerator} from the pattern provided.
|
||||
* @param expression a space-separated list of time fields
|
||||
@@ -81,6 +82,7 @@ public class CronSequenceGenerator {
|
||||
parse(expression);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the next {@link Date} in the sequence matching the Cron pattern and
|
||||
* after the value provided. The return value will have a whole number of
|
||||
@@ -135,7 +137,8 @@ public class CronSequenceGenerator {
|
||||
int updateMinute = findNext(this.minutes, minute, calendar, Calendar.MINUTE, Calendar.HOUR_OF_DAY, resets);
|
||||
if (minute == updateMinute) {
|
||||
resets.add(Calendar.MINUTE);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
doNext(calendar, dot);
|
||||
}
|
||||
|
||||
@@ -143,7 +146,8 @@ public class CronSequenceGenerator {
|
||||
int updateHour = findNext(this.hours, hour, calendar, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_WEEK, resets);
|
||||
if (hour == updateHour) {
|
||||
resets.add(Calendar.HOUR_OF_DAY);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
doNext(calendar, dot);
|
||||
}
|
||||
|
||||
@@ -152,7 +156,8 @@ public class CronSequenceGenerator {
|
||||
int updateDayOfMonth = findNextDay(calendar, this.daysOfMonth, dayOfMonth, daysOfWeek, dayOfWeek, resets);
|
||||
if (dayOfMonth == updateDayOfMonth) {
|
||||
resets.add(Calendar.DAY_OF_MONTH);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
doNext(calendar, dot);
|
||||
}
|
||||
|
||||
@@ -160,7 +165,8 @@ public class CronSequenceGenerator {
|
||||
int updateMonth = findNext(this.months, month, calendar, Calendar.MONTH, Calendar.YEAR, resets);
|
||||
if (month != updateMonth) {
|
||||
if (calendar.get(Calendar.YEAR) - dot > 4) {
|
||||
throw new IllegalStateException("Invalid cron expression led to runaway search for next trigger");
|
||||
throw new IllegalArgumentException("Invalid cron expression \"" + this.expression +
|
||||
"\" led to runaway search for next trigger");
|
||||
}
|
||||
doNext(calendar, dot);
|
||||
}
|
||||
@@ -181,7 +187,7 @@ public class CronSequenceGenerator {
|
||||
reset(calendar, resets);
|
||||
}
|
||||
if (count >= max) {
|
||||
throw new IllegalStateException("Overflow in day for expression=" + this.expression);
|
||||
throw new IllegalArgumentException("Overflow in day for expression \"" + this.expression + "\"");
|
||||
}
|
||||
return dayOfMonth;
|
||||
}
|
||||
@@ -222,7 +228,8 @@ public class CronSequenceGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
// Parsing logic invoked by the constructor.
|
||||
|
||||
// Parsing logic invoked by the constructor
|
||||
|
||||
/**
|
||||
* Parse the given pattern expression.
|
||||
@@ -230,8 +237,8 @@ public class CronSequenceGenerator {
|
||||
private void parse(String expression) throws IllegalArgumentException {
|
||||
String[] fields = StringUtils.tokenizeToStringArray(expression, " ");
|
||||
if (fields.length != 6) {
|
||||
throw new IllegalArgumentException(String.format(""
|
||||
+ "cron expression must consist of 6 fields (found %d in %s)", fields.length, expression));
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));
|
||||
}
|
||||
setNumberHits(this.seconds, fields[0], 0, 60);
|
||||
setNumberHits(this.minutes, fields[1], 0, 60);
|
||||
@@ -296,10 +303,12 @@ public class CronSequenceGenerator {
|
||||
// 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 {
|
||||
}
|
||||
else {
|
||||
String[] split = StringUtils.delimitedListToStringArray(field, "/");
|
||||
if (split.length > 2) {
|
||||
throw new IllegalArgumentException("Incrementer has more than two fields: " + field);
|
||||
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("-")) {
|
||||
@@ -322,19 +331,23 @@ public class CronSequenceGenerator {
|
||||
}
|
||||
if (!field.contains("-")) {
|
||||
result[0] = result[1] = Integer.valueOf(field);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
String[] split = StringUtils.delimitedListToStringArray(field, "-");
|
||||
if (split.length > 2) {
|
||||
throw new IllegalArgumentException("Range has more than two fields: " + field);
|
||||
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);
|
||||
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);
|
||||
throw new IllegalArgumentException("Range less than minimum (" + min + "): '" +
|
||||
field + "' in expression \"" + this.expression + "\"");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user