From 9255d3038ff4bfbc7530eec74e551613ebd03725 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 8 Feb 2013 00:58:39 +0100 Subject: [PATCH] @Scheduled provides String variants of fixedDelay, fixedRate, initialDelay for placeholder support Issue: SPR-8067 --- .../scheduling/annotation/Scheduled.java | 27 +++- .../ScheduledAnnotationBeanPostProcessor.java | 152 ++++++++++++------ .../scheduling/config/IntervalTask.java | 9 +- .../support/CronSequenceGenerator.java | 43 +++-- ...duledAnnotationBeanPostProcessorTests.java | 114 ++++++++++--- 5 files changed, 255 insertions(+), 90 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java index dea788ce08..383e4adc5d 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java @@ -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 ""; } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java index f5f236d2f9..754afc5bd6 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java @@ -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 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 schedulers = new HashMap(); schedulers.putAll(applicationContext.getBeansOfType(TaskScheduler.class)); @@ -199,7 +256,6 @@ public class ScheduledAnnotationBeanPostProcessor "configureTasks() callback. Found the following beans: " + schedulers.keySet()); } } - this.registrar.afterPropertiesSet(); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/IntervalTask.java b/spring-context/src/main/java/org/springframework/scheduling/config/IntervalTask.java index 4b39927e8f..567f9fdbfe 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/config/IntervalTask.java +++ b/spring-context/src/main/java/org/springframework/scheduling/config/IntervalTask.java @@ -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; } + } diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java index b3530ebf78..5f5cf4e280 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java @@ -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; } diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java index e100da3d47..91bf0f697e 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Properties; import org.junit.Test; + import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.config.BeanDefinition; @@ -52,8 +53,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { public void fixedDelayTask() { StaticApplicationContext context = new StaticApplicationContext(); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.FixedDelayTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(FixedDelayTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -106,8 +106,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { public void fixedRateTaskWithInitialDelay() { StaticApplicationContext context = new StaticApplicationContext(); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.FixedRateWithInitialDelayTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithInitialDelayTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -162,8 +161,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { public void metaAnnotationWithFixedRate() { StaticApplicationContext context = new StaticApplicationContext(); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.MetaAnnotationFixedRateTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(MetaAnnotationFixedRateTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); context.refresh(); @@ -211,7 +209,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } @Test - public void propertyPlaceholderWithCronExpression() { + public void propertyPlaceholderWithCron() { String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; StaticApplicationContext context = new StaticApplicationContext(); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); @@ -219,8 +217,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { Properties properties = new Properties(); properties.setProperty("schedules.businessHours", businessHoursCronExpression); placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); - BeanDefinition targetDefinition = new RootBeanDefinition( - ScheduledAnnotationBeanPostProcessorTests.PropertyPlaceholderTestBean.class); + BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithCronTestBean.class); context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); @@ -242,6 +239,70 @@ public class ScheduledAnnotationBeanPostProcessorTests { assertEquals(businessHoursCronExpression, task.getExpression()); } + @Test + public void propertyPlaceholderWithFixedDelay() { + StaticApplicationContext context = new StaticApplicationContext(); + BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); + BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertyPlaceholderConfigurer.class); + Properties properties = new Properties(); + properties.setProperty("fixedDelay", "5000"); + properties.setProperty("initialDelay", "1000"); + placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); + BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedDelayTestBean.class); + context.registerBeanDefinition("placeholder", placeholderDefinition); + context.registerBeanDefinition("postProcessor", processorDefinition); + context.registerBeanDefinition("target", targetDefinition); + context.refresh(); + Object postProcessor = context.getBean("postProcessor"); + Object target = context.getBean("target"); + ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) + new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); + @SuppressWarnings("unchecked") + List fixedDelayTasks = (List) + new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks"); + assertEquals(1, fixedDelayTasks.size()); + IntervalTask task = fixedDelayTasks.get(0); + ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); + Object targetObject = runnable.getTarget(); + Method targetMethod = runnable.getMethod(); + assertEquals(target, targetObject); + assertEquals("fixedDelay", targetMethod.getName()); + assertEquals(1000L, task.getInitialDelay()); + assertEquals(5000L, task.getInterval()); + } + + @Test + public void propertyPlaceholderWithFixedRate() { + StaticApplicationContext context = new StaticApplicationContext(); + BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); + BeanDefinition placeholderDefinition = new RootBeanDefinition(PropertyPlaceholderConfigurer.class); + Properties properties = new Properties(); + properties.setProperty("fixedRate", "3000"); + properties.setProperty("initialDelay", "1000"); + placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); + BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithFixedRateTestBean.class); + context.registerBeanDefinition("placeholder", placeholderDefinition); + context.registerBeanDefinition("postProcessor", processorDefinition); + context.registerBeanDefinition("target", targetDefinition); + context.refresh(); + Object postProcessor = context.getBean("postProcessor"); + Object target = context.getBean("target"); + ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar) + new DirectFieldAccessor(postProcessor).getPropertyValue("registrar"); + @SuppressWarnings("unchecked") + List fixedRateTasks = (List) + new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); + assertEquals(1, fixedRateTasks.size()); + IntervalTask task = fixedRateTasks.get(0); + ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable(); + Object targetObject = runnable.getTarget(); + Method targetMethod = runnable.getMethod(); + assertEquals(target, targetObject); + assertEquals("fixedRate", targetMethod.getName()); + assertEquals(1000L, task.getInitialDelay()); + assertEquals(3000L, task.getInterval()); + } + @Test public void propertyPlaceholderForMetaAnnotation() { String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; @@ -285,7 +346,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { context.refresh(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = BeanCreationException.class) public void invalidCron() throws Throwable { StaticApplicationContext context = new StaticApplicationContext(); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); @@ -293,12 +354,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { ScheduledAnnotationBeanPostProcessorTests.InvalidCronTestBean.class); context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("target", targetDefinition); - try { - context.refresh(); - fail("expected exception"); - } catch (BeanCreationException ex) { - throw ex.getRootCause(); - } + context.refresh(); } @Test(expected = BeanCreationException.class) @@ -342,7 +398,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { static class FixedRateWithInitialDelayTestBean { - @Scheduled(initialDelay=1000, fixedRate=3000) + @Scheduled(fixedRate=3000, initialDelay=1000) public void fixedRate() { } } @@ -395,13 +451,13 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - @Scheduled(fixedRate = 5000) + @Scheduled(fixedRate=5000) @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) private static @interface EveryFiveSeconds {} - @Scheduled(cron = "0 0 * * * ?") + @Scheduled(cron="0 0 * * * ?") @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) private static @interface Hourly {} @@ -423,7 +479,7 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - static class PropertyPlaceholderTestBean { + static class PropertyPlaceholderWithCronTestBean { @Scheduled(cron = "${schedules.businessHours}") public void x() { @@ -431,7 +487,23 @@ public class ScheduledAnnotationBeanPostProcessorTests { } - @Scheduled(cron = "${schedules.businessHours}") + static class PropertyPlaceholderWithFixedDelayTestBean { + + @Scheduled(fixedDelayString="${fixedDelay}", initialDelayString="${initialDelay}") + public void fixedDelay() { + } + } + + + static class PropertyPlaceholderWithFixedRateTestBean { + + @Scheduled(fixedRateString="${fixedRate}", initialDelayString="${initialDelay}") + public void fixedRate() { + } + } + + + @Scheduled(cron="${schedules.businessHours}") @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) private static @interface BusinessHours {}