Mark Fisher
16 years ago
3 changed files with 414 additions and 0 deletions
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
/* |
||||
* Copyright 2002-2009 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.scheduling.annotation; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
/** |
||||
* Annotation that marks a method to be scheduled. Exactly one of the |
||||
* <code>cron</code>, <code>fixedDelay</code>, or <code>fixedRate</code> |
||||
* attributes must be provided. |
||||
* |
||||
* <p>The annotated method must expect no arguments and have a |
||||
* <code>void</code> return type. |
||||
* |
||||
* @author Mark Fisher |
||||
* @since 3.0 |
||||
* @see ScheduledAnnotationBeanPostProcessor |
||||
*/ |
||||
@Target(ElementType.METHOD) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Documented |
||||
public @interface Scheduled { |
||||
|
||||
String cron() default ""; |
||||
|
||||
long fixedDelay() default -1; |
||||
|
||||
long fixedRate() default -1; |
||||
|
||||
} |
@ -0,0 +1,147 @@
@@ -0,0 +1,147 @@
|
||||
/* |
||||
* Copyright 2002-2009 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.scheduling.annotation; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import org.springframework.aop.support.AopUtils; |
||||
import org.springframework.beans.BeansException; |
||||
import org.springframework.beans.factory.DisposableBean; |
||||
import org.springframework.beans.factory.config.BeanPostProcessor; |
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.context.event.ContextRefreshedEvent; |
||||
import org.springframework.core.Ordered; |
||||
import org.springframework.core.annotation.AnnotationUtils; |
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar; |
||||
import org.springframework.scheduling.support.MethodInvokingRunnable; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ReflectionUtils; |
||||
import org.springframework.util.ReflectionUtils.MethodCallback; |
||||
|
||||
/** |
||||
* Bean post-processor that registers methods annotated with |
||||
* {@link Scheduled @Scheduled} to be invoked by a TaskScheduler according |
||||
* to the fixedRate, fixedDelay, or cron expression provided via the annotation. |
||||
* |
||||
* @author Mark Fisher |
||||
* @since 3.0 |
||||
* @see Scheduled |
||||
*/ |
||||
public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered, |
||||
ApplicationListener<ContextRefreshedEvent>, DisposableBean { |
||||
|
||||
private Object scheduler; |
||||
|
||||
private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar(); |
||||
|
||||
private final Map<Runnable, String> cronTasks = new HashMap<Runnable, String>(); |
||||
|
||||
private final Map<Runnable, Long> fixedDelayTasks = new HashMap<Runnable, Long>(); |
||||
|
||||
private final Map<Runnable, Long> fixedRateTasks = new HashMap<Runnable, Long>(); |
||||
|
||||
|
||||
/** |
||||
* Set the {@link org.springframework.scheduling.TaskScheduler} that will |
||||
* invoke the scheduled methods or a |
||||
* {@link java.util.concurrent.ScheduledExecutorService} to be wrapped |
||||
* within an instance of |
||||
* {@link org.springframework.scheduling.concurrent.ConcurrentTaskScheduler}. |
||||
*/ |
||||
public void setScheduler(Object scheduler) { |
||||
this.scheduler = scheduler; |
||||
} |
||||
|
||||
public int getOrder() { |
||||
return LOWEST_PRECEDENCE; |
||||
} |
||||
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { |
||||
return bean; |
||||
} |
||||
|
||||
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException { |
||||
Class<?> targetClass = AopUtils.getTargetClass(bean); |
||||
if (targetClass == null) { |
||||
return 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) { |
||||
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."); |
||||
MethodInvokingRunnable runnable = new MethodInvokingRunnable(); |
||||
runnable.setTargetObject(bean); |
||||
runnable.setTargetMethod(method.getName()); |
||||
runnable.setArguments(new Object[0]); |
||||
try { |
||||
runnable.prepare(); |
||||
} |
||||
catch (Exception e) { |
||||
throw new IllegalStateException("failed to prepare task", e); |
||||
} |
||||
boolean processedSchedule = false; |
||||
String errorMessage = "Exactly one of 'cron', 'fixedDelay', or 'fixedRate' is required."; |
||||
String cron = annotation.cron(); |
||||
if (!"".equals(cron)) { |
||||
processedSchedule = true; |
||||
cronTasks.put(runnable, cron); |
||||
} |
||||
long fixedDelay = annotation.fixedDelay(); |
||||
if (fixedDelay >= 0) { |
||||
Assert.isTrue(!processedSchedule, errorMessage); |
||||
processedSchedule = true; |
||||
fixedDelayTasks.put(runnable, new Long(fixedDelay)); |
||||
} |
||||
long fixedRate = annotation.fixedRate(); |
||||
if (fixedRate >= 0) { |
||||
Assert.isTrue(!processedSchedule, errorMessage); |
||||
processedSchedule = true; |
||||
fixedRateTasks.put(runnable, new Long(fixedRate)); |
||||
} |
||||
Assert.isTrue(processedSchedule, errorMessage); |
||||
} |
||||
} |
||||
}); |
||||
return bean; |
||||
} |
||||
|
||||
@Override |
||||
public void onApplicationEvent(ContextRefreshedEvent event) { |
||||
if (scheduler != null) { |
||||
this.registrar.setScheduler(scheduler); |
||||
} |
||||
this.registrar.setCronTasks(this.cronTasks); |
||||
this.registrar.setFixedDelayTasks(this.fixedDelayTasks); |
||||
this.registrar.setFixedRateTasks(this.fixedRateTasks); |
||||
this.registrar.afterPropertiesSet(); |
||||
} |
||||
|
||||
@Override |
||||
public void destroy() throws Exception { |
||||
if (this.registrar != null) { |
||||
this.registrar.destroy(); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,219 @@
@@ -0,0 +1,219 @@
|
||||
/* |
||||
* Copyright 2002-2009 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.scheduling.annotation; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.beans.DirectFieldAccessor; |
||||
import org.springframework.beans.factory.BeanCreationException; |
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.beans.factory.support.RootBeanDefinition; |
||||
import org.springframework.context.support.StaticApplicationContext; |
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar; |
||||
import org.springframework.scheduling.support.MethodInvokingRunnable; |
||||
|
||||
/** |
||||
* @author Mark Fisher |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public class ScheduledAnnotationBeanPostProcessorTests { |
||||
|
||||
@Test |
||||
public void fixedDelayTask() { |
||||
StaticApplicationContext context = new StaticApplicationContext(); |
||||
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); |
||||
BeanDefinition targetDefinition = new RootBeanDefinition( |
||||
ScheduledAnnotationBeanPostProcessorTests.FixedDelayTestBean.class); |
||||
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"); |
||||
Map<Runnable, Long> fixedDelayTasks = (Map<Runnable, Long>) |
||||
new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks"); |
||||
assertEquals(1, fixedDelayTasks.size()); |
||||
MethodInvokingRunnable runnable = (MethodInvokingRunnable) fixedDelayTasks.keySet().iterator().next(); |
||||
Object targetObject = runnable.getTargetObject(); |
||||
String targetMethod = runnable.getTargetMethod(); |
||||
assertEquals(target, targetObject); |
||||
assertEquals("fixedDelay", targetMethod); |
||||
assertEquals(new Long(5000), fixedDelayTasks.values().iterator().next()); |
||||
} |
||||
|
||||
@Test |
||||
public void fixedRateTask() { |
||||
StaticApplicationContext context = new StaticApplicationContext(); |
||||
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); |
||||
BeanDefinition targetDefinition = new RootBeanDefinition( |
||||
ScheduledAnnotationBeanPostProcessorTests.FixedRateTestBean.class); |
||||
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"); |
||||
Map<Runnable, Long> fixedRateTasks = (Map<Runnable, Long>) |
||||
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks"); |
||||
assertEquals(1, fixedRateTasks.size()); |
||||
MethodInvokingRunnable runnable = (MethodInvokingRunnable) fixedRateTasks.keySet().iterator().next(); |
||||
Object targetObject = runnable.getTargetObject(); |
||||
String targetMethod = runnable.getTargetMethod(); |
||||
assertEquals(target, targetObject); |
||||
assertEquals("fixedRate", targetMethod); |
||||
assertEquals(new Long(3000), fixedRateTasks.values().iterator().next()); |
||||
} |
||||
|
||||
@Test |
||||
public void cronTask() { |
||||
StaticApplicationContext context = new StaticApplicationContext(); |
||||
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); |
||||
BeanDefinition targetDefinition = new RootBeanDefinition( |
||||
ScheduledAnnotationBeanPostProcessorTests.CronTestBean.class); |
||||
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"); |
||||
Map<Runnable, Long> cronTasks = (Map<Runnable, Long>) |
||||
new DirectFieldAccessor(registrar).getPropertyValue("cronTasks"); |
||||
assertEquals(1, cronTasks.size()); |
||||
MethodInvokingRunnable runnable = (MethodInvokingRunnable) cronTasks.keySet().iterator().next(); |
||||
Object targetObject = runnable.getTargetObject(); |
||||
String targetMethod = runnable.getTargetMethod(); |
||||
assertEquals(target, targetObject); |
||||
assertEquals("cron", targetMethod); |
||||
assertEquals("*/7 * * * * ?", cronTasks.values().iterator().next()); |
||||
} |
||||
|
||||
@Test(expected = BeanCreationException.class) |
||||
public void emptyAnnotation() { |
||||
StaticApplicationContext context = new StaticApplicationContext(); |
||||
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); |
||||
BeanDefinition targetDefinition = new RootBeanDefinition( |
||||
ScheduledAnnotationBeanPostProcessorTests.EmptyAnnotationTestBean.class); |
||||
context.registerBeanDefinition("postProcessor", processorDefinition); |
||||
context.registerBeanDefinition("target", targetDefinition); |
||||
context.refresh(); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void invalidCron() { |
||||
StaticApplicationContext context = new StaticApplicationContext(); |
||||
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); |
||||
BeanDefinition targetDefinition = new RootBeanDefinition( |
||||
ScheduledAnnotationBeanPostProcessorTests.InvalidCronTestBean.class); |
||||
context.registerBeanDefinition("postProcessor", processorDefinition); |
||||
context.registerBeanDefinition("target", targetDefinition); |
||||
context.refresh(); |
||||
} |
||||
|
||||
@Test(expected = BeanCreationException.class) |
||||
public void nonVoidReturnType() { |
||||
StaticApplicationContext context = new StaticApplicationContext(); |
||||
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); |
||||
BeanDefinition targetDefinition = new RootBeanDefinition( |
||||
ScheduledAnnotationBeanPostProcessorTests.NonVoidReturnTypeTestBean.class); |
||||
context.registerBeanDefinition("postProcessor", processorDefinition); |
||||
context.registerBeanDefinition("target", targetDefinition); |
||||
context.refresh(); |
||||
} |
||||
|
||||
@Test(expected = BeanCreationException.class) |
||||
public void nonEmptyParamList() { |
||||
StaticApplicationContext context = new StaticApplicationContext(); |
||||
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); |
||||
BeanDefinition targetDefinition = new RootBeanDefinition( |
||||
ScheduledAnnotationBeanPostProcessorTests.NonEmptyParamListTestBean.class); |
||||
context.registerBeanDefinition("postProcessor", processorDefinition); |
||||
context.registerBeanDefinition("target", targetDefinition); |
||||
context.refresh(); |
||||
} |
||||
|
||||
|
||||
private static class FixedDelayTestBean { |
||||
|
||||
@Scheduled(fixedDelay=5000) |
||||
public void fixedDelay() { |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
private static class FixedRateTestBean { |
||||
|
||||
@Scheduled(fixedRate=3000) |
||||
public void fixedRate() { |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
private static class CronTestBean { |
||||
|
||||
@Scheduled(cron="*/7 * * * * ?") |
||||
public void cron() { |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
private static class EmptyAnnotationTestBean { |
||||
|
||||
@Scheduled |
||||
public void invalid() { |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
private static class InvalidCronTestBean { |
||||
|
||||
@Scheduled(cron="abc") |
||||
public void invalid() { |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
private static class NonVoidReturnTypeTestBean { |
||||
|
||||
@Scheduled(fixedRate=3000) |
||||
public String invalid() { |
||||
return "oops"; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
private static class NonEmptyParamListTestBean { |
||||
|
||||
@Scheduled(fixedRate=3000) |
||||
public void invalid(String oops) { |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue