Browse Source

Initial commit of @Scheduled annotation and ScheduledAnnotationBeanPostProcessor.

conversation
Mark Fisher 16 years ago
parent
commit
2c7463262c
  1. 48
      org.springframework.context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
  2. 147
      org.springframework.context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
  3. 219
      org.springframework.context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java

48
org.springframework.context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java

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

147
org.springframework.context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java

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

219
org.springframework.context/src/test/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessorTests.java

@ -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…
Cancel
Save