|
|
|
|
@@ -110,120 +110,20 @@ public class ScheduledAnnotationBeanPostProcessor
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object postProcessAfterInitialization(final Object bean, String beanName) {
|
|
|
|
|
final Class<?> targetClass = AopUtils.getTargetClass(bean);
|
|
|
|
|
Class<?> targetClass = AopUtils.getTargetClass(bean);
|
|
|
|
|
ReflectionUtils.doWithMethods(targetClass, new MethodCallback() {
|
|
|
|
|
@Override
|
|
|
|
|
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
|
|
|
|
Scheduled annotation = AnnotationUtils.getAnnotation(method, Scheduled.class);
|
|
|
|
|
if (annotation != null) {
|
|
|
|
|
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()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 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));
|
|
|
|
|
}
|
|
|
|
|
// 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 = 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);
|
|
|
|
|
Schedules schedules = AnnotationUtils.getAnnotation(method, Schedules.class);
|
|
|
|
|
if (schedules != null) {
|
|
|
|
|
for (Scheduled scheduled : schedules.value()) {
|
|
|
|
|
processScheduled(scheduled, method, bean);
|
|
|
|
|
}
|
|
|
|
|
catch (IllegalArgumentException ex) {
|
|
|
|
|
throw new IllegalStateException(
|
|
|
|
|
"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Scheduled scheduled = AnnotationUtils.getAnnotation(method, Scheduled.class);
|
|
|
|
|
if (scheduled != null) {
|
|
|
|
|
processScheduled(scheduled, method, bean);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -231,23 +131,146 @@ public class ScheduledAnnotationBeanPostProcessor
|
|
|
|
|
return bean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
|
|
|
|
|
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(), method.getDeclaringClass().getSimpleName()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 = scheduled.initialDelay();
|
|
|
|
|
String initialDelayString = scheduled.initialDelayString();
|
|
|
|
|
if (!"".equals(initialDelayString)) {
|
|
|
|
|
Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
|
|
|
|
|
if (this.embeddedValueResolver != null) {
|
|
|
|
|
initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
initialDelay = Integer.parseInt(initialDelayString);
|
|
|
|
|
}
|
|
|
|
|
catch (NumberFormatException ex) {
|
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
|
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into integer");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check cron expression
|
|
|
|
|
String cron = scheduled.cron();
|
|
|
|
|
if (!"".equals(cron)) {
|
|
|
|
|
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
|
|
|
|
|
processedSchedule = true;
|
|
|
|
|
if (this.embeddedValueResolver != null) {
|
|
|
|
|
cron = this.embeddedValueResolver.resolveStringValue(cron);
|
|
|
|
|
}
|
|
|
|
|
this.registrar.addCronTask(new CronTask(runnable, cron));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
this.registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
|
|
|
|
|
}
|
|
|
|
|
String fixedDelayString = scheduled.fixedDelayString();
|
|
|
|
|
if (!"".equals(fixedDelayString)) {
|
|
|
|
|
Assert.isTrue(!processedSchedule, errorMessage);
|
|
|
|
|
processedSchedule = true;
|
|
|
|
|
if (this.embeddedValueResolver != null) {
|
|
|
|
|
fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
fixedDelay = Integer.parseInt(fixedDelayString);
|
|
|
|
|
}
|
|
|
|
|
catch (NumberFormatException ex) {
|
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
|
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into integer");
|
|
|
|
|
}
|
|
|
|
|
this.registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check fixed rate
|
|
|
|
|
long fixedRate = scheduled.fixedRate();
|
|
|
|
|
if (fixedRate >= 0) {
|
|
|
|
|
Assert.isTrue(!processedSchedule, errorMessage);
|
|
|
|
|
processedSchedule = true;
|
|
|
|
|
this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
|
|
|
|
|
}
|
|
|
|
|
String fixedRateString = scheduled.fixedRateString();
|
|
|
|
|
if (!"".equals(fixedRateString)) {
|
|
|
|
|
Assert.isTrue(!processedSchedule, errorMessage);
|
|
|
|
|
processedSchedule = true;
|
|
|
|
|
if (this.embeddedValueResolver != null) {
|
|
|
|
|
fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
fixedRate = Integer.parseInt(fixedRateString);
|
|
|
|
|
}
|
|
|
|
|
catch (NumberFormatException ex) {
|
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
|
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");
|
|
|
|
|
}
|
|
|
|
|
this.registrar.addFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check whether we had any attribute set
|
|
|
|
|
Assert.isTrue(processedSchedule, errorMessage);
|
|
|
|
|
}
|
|
|
|
|
catch (IllegalArgumentException ex) {
|
|
|
|
|
throw new IllegalStateException(
|
|
|
|
|
"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onApplicationEvent(ContextRefreshedEvent event) {
|
|
|
|
|
if (event.getApplicationContext() != this.applicationContext) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Map<String, SchedulingConfigurer> configurers =
|
|
|
|
|
this.applicationContext.getBeansOfType(SchedulingConfigurer.class);
|
|
|
|
|
|
|
|
|
|
if (this.scheduler != null) {
|
|
|
|
|
this.registrar.setScheduler(this.scheduler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Map<String, SchedulingConfigurer> configurers =
|
|
|
|
|
this.applicationContext.getBeansOfType(SchedulingConfigurer.class);
|
|
|
|
|
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));
|
|
|
|
|
schedulers.putAll(applicationContext.getBeansOfType(ScheduledExecutorService.class));
|
|
|
|
|
schedulers.putAll(this.applicationContext.getBeansOfType(TaskScheduler.class));
|
|
|
|
|
schedulers.putAll(this.applicationContext.getBeansOfType(ScheduledExecutorService.class));
|
|
|
|
|
if (schedulers.size() == 0) {
|
|
|
|
|
// do nothing -> fall back to default scheduler
|
|
|
|
|
}
|
|
|
|
|
@@ -263,14 +286,13 @@ public class ScheduledAnnotationBeanPostProcessor
|
|
|
|
|
"configureTasks() callback. Found the following beans: " + schedulers.keySet());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.registrar.afterPropertiesSet();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void destroy() throws Exception {
|
|
|
|
|
if (this.registrar != null) {
|
|
|
|
|
this.registrar.destroy();
|
|
|
|
|
}
|
|
|
|
|
public void destroy() {
|
|
|
|
|
this.registrar.destroy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|