|
|
|
@ -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(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|