Merge branch '3.2.x' into cleanup-3.2.x

* 3.2.x:
  Promote use of @PostConstruct and @PreDestroy
  @Scheduled provides String variants of fixedDelay, fixedRate, initialDelay for placeholder support
  Further preparations for 3.2.2
  @Scheduled provides String variants of fixedDelay, fixedRate, initialDelay for placeholder support
  Folded a FactoryBean-specific check into predictBeanType now
  Fix Assert.instanceOf exception message
  Allow for ordering of mixed AspectJ before/after advices
  Minor javadoc and source layout polishing
  Fixed documentation for "depends-on" attribute
  "depends-on" attribute on lang namespace element actually respected at runtime now
  Allow for ordering of mixed AspectJ before/after advices
This commit is contained in:
Phillip Webb
2013-02-07 17:37:13 -08:00
25 changed files with 577 additions and 220 deletions

View File

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

View File

@@ -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<String, SchedulingConfigurer> 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<String, ? super Object> schedulers = new HashMap<String, Object>();
schedulers.putAll(applicationContext.getBeansOfType(TaskScheduler.class));
@@ -199,7 +256,6 @@ public class ScheduledAnnotationBeanPostProcessor
"configureTasks() callback. Found the following beans: " + schedulers.keySet());
}
}
this.registrar.afterPropertiesSet();
}

View File

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

View File

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

View File

@@ -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.
@@ -26,6 +26,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionDefaults;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.scripting.support.ScriptFactoryPostProcessor;
@@ -64,6 +65,8 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
private static final String DEPENDENCY_CHECK_ATTRIBUTE = "dependency-check";
private static final String DEPENDS_ON_ATTRIBUTE = "depends-on";
private static final String INIT_METHOD_ATTRIBUTE = "init-method";
private static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method";
@@ -138,6 +141,13 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
String dependencyCheck = element.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
bd.setDependencyCheck(parserContext.getDelegate().getDependencyCheck(dependencyCheck));
// Parse depends-on list of bean names.
String dependsOn = element.getAttribute(DEPENDS_ON_ATTRIBUTE);
if (StringUtils.hasLength(dependsOn)) {
bd.setDependsOn(StringUtils.tokenizeToStringArray(
dependsOn, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
// Retrieve the defaults for bean definitions within this parser context
BeanDefinitionDefaults beanDefinitionDefaults = parserContext.getDelegate().getBeanDefinitionDefaults();

View File

@@ -82,7 +82,7 @@
<xsd:annotation>
<xsd:documentation source="java:org.springframework.core.io.Resource"><![CDATA[
The resource containing the script for the dynamic language-backed bean.
Examples might be '/WEB-INF/scripts/Anais.groovy', 'classpath:Nin.bsh', etc.
]]></xsd:documentation>
</xsd:annotation>
@@ -137,13 +137,13 @@
<xsd:attribute name="depends-on" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The names of the beans that this bean depends on being initialized.
The bean factory will guarantee that these beans get initialized
before this bean.
The names of the beans that this bean depends on being initialized.
The bean factory will guarantee that these beans get initialized
before this bean.
Note that dependencies are normally expressed through bean properties.
This property should just be necessary other kinds of dependencies
like statics (*ugh*) or database preparation on startup.
Note that dependencies are normally expressed through bean properties.
This property should just be necessary for other kinds of dependencies
like statics (*ugh*) or database preparation on startup.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -82,7 +82,7 @@
<xsd:annotation>
<xsd:documentation source="java:org.springframework.core.io.Resource"><![CDATA[
The resource containing the script for the dynamic language-backed bean.
Examples might be '/WEB-INF/scripts/Anais.groovy', 'classpath:Nin.bsh', etc.
]]></xsd:documentation>
</xsd:annotation>
@@ -127,13 +127,13 @@
<xsd:attribute name="depends-on" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The names of the beans that this bean depends on being initialized.
The bean factory will guarantee that these beans get initialized
before this bean.
The names of the beans that this bean depends on being initialized.
The bean factory will guarantee that these beans get initialized
before this bean.
Note that dependencies are normally expressed through bean properties.
This property should just be necessary other kinds of dependencies
like statics (*ugh*) or database preparation on startup.
Note that dependencies are normally expressed through bean properties.
This property should just be necessary for other kinds of dependencies
like statics (*ugh*) or database preparation on startup.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -147,13 +147,13 @@
<xsd:attribute name="depends-on" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The names of the beans that this bean depends on being initialized.
The bean factory will guarantee that these beans get initialized
before this bean.
The names of the beans that this bean depends on being initialized.
The bean factory will guarantee that these beans get initialized
before this bean.
Note that dependencies are normally expressed through bean properties.
This property should just be necessary other kinds of dependencies
like statics (*ugh*) or database preparation on startup.
Note that dependencies are normally expressed through bean properties.
This property should just be necessary for other kinds of dependencies
like statics (*ugh*) or database preparation on startup.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -147,13 +147,13 @@
<xsd:attribute name="depends-on" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The names of the beans that this bean depends on being initialized.
The bean factory will guarantee that these beans get initialized
before this bean.
The names of the beans that this bean depends on being initialized.
The bean factory will guarantee that these beans get initialized
before this bean.
Note that dependencies are normally expressed through bean properties.
This property should just be necessary other kinds of dependencies
like statics (*ugh*) or database preparation on startup.
Note that dependencies are normally expressed through bean properties.
This property should just be necessary for other kinds of dependencies
like statics (*ugh*) or database preparation on startup.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -0,0 +1,132 @@
/*
* 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.
* 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.context.annotation;
import java.util.Map;
import org.junit.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
/**
* Unit tests for SPR-8954, in which a custom {@link InstantiationAwareBeanPostProcessor}
* forces the predicted type of a FactoryBean, effectively preventing retrieval of the
* bean from calls to #getBeansOfType(FactoryBean.class). The implementation of
* {@link AbstractBeanFactory#isFactoryBean(String, RootBeanDefinition)} now ensures
* that not only the predicted bean type is considered, but also the original bean
* definition's beanClass.
*
* @author Chris Beams
* @author Oliver Gierke
*/
public class ConfigurationClassSpr8954Tests {
@Test
public void repro() {
AnnotationConfigApplicationContext bf = new AnnotationConfigApplicationContext();
bf.registerBeanDefinition("fooConfig", new RootBeanDefinition(FooConfig.class));
bf.getBeanFactory().addBeanPostProcessor(new PredictingBPP());
bf.refresh();
assertThat(bf.getBean("foo"), instanceOf(Foo.class));
assertThat(bf.getBean("&foo"), instanceOf(FooFactoryBean.class));
assertThat(bf.isTypeMatch("&foo", FactoryBean.class), is(true));
@SuppressWarnings("rawtypes")
Map<String, FactoryBean> fbBeans = bf.getBeansOfType(FactoryBean.class);
assertThat(1, equalTo(fbBeans.size()));
assertThat("&foo", equalTo(fbBeans.keySet().iterator().next()));
Map<String, AnInterface> aiBeans = bf.getBeansOfType(AnInterface.class);
assertThat(1, equalTo(aiBeans.size()));
assertThat("&foo", equalTo(aiBeans.keySet().iterator().next()));
}
@Test
public void findsBeansByTypeIfNotInstantiated() {
AnnotationConfigApplicationContext bf = new AnnotationConfigApplicationContext();
bf.registerBeanDefinition("fooConfig", new RootBeanDefinition(FooConfig.class));
bf.getBeanFactory().addBeanPostProcessor(new PredictingBPP());
bf.refresh();
assertThat(bf.isTypeMatch("&foo", FactoryBean.class), is(true));
@SuppressWarnings("rawtypes")
Map<String, FactoryBean> fbBeans = bf.getBeansOfType(FactoryBean.class);
assertThat(1, equalTo(fbBeans.size()));
assertThat("&foo", equalTo(fbBeans.keySet().iterator().next()));
Map<String, AnInterface> aiBeans = bf.getBeansOfType(AnInterface.class);
assertThat(1, equalTo(aiBeans.size()));
assertThat("&foo", equalTo(aiBeans.keySet().iterator().next()));
}
static class FooConfig {
@Bean FooFactoryBean foo() {
return new FooFactoryBean();
}
}
static class FooFactoryBean implements FactoryBean<Foo>, AnInterface {
@Override
public Foo getObject() throws Exception {
return new Foo();
}
@Override
public Class<?> getObjectType() {
return Foo.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
interface AnInterface {
}
static class Foo {
}
interface PredictedType {
}
static class PredictedTypeImpl implements PredictedType {
}
static class PredictingBPP extends InstantiationAwareBeanPostProcessorAdapter {
@Override
public Class<?> predictBeanType(Class<?> beanClass, String beanName) {
return FactoryBean.class.isAssignableFrom(beanClass) ?
PredictedType.class : null;
}
}
}

View File

@@ -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<IntervalTask> fixedDelayTasks = (List<IntervalTask>)
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<IntervalTask> fixedRateTasks = (List<IntervalTask>)
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 {}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 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.
@@ -487,7 +487,7 @@ public class CronTriggerTests {
assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context2));
}
@Test(expected=IllegalStateException.class)
@Test(expected = IllegalArgumentException.class)
public void testNonExistentSpecificDate() throws Exception {
// TODO: maybe try and detect this as a special case in parser?
CronTrigger trigger = new CronTrigger("0 0 0 31 6 *", timeZone);

View File

@@ -16,31 +16,22 @@
package org.springframework.scripting.groovy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import groovy.lang.DelegatingMetaClass;
import groovy.lang.GroovyObject;
import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.Map;
import groovy.lang.DelegatingMetaClass;
import groovy.lang.GroovyObject;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.target.dynamic.Refreshable;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.NestedRuntimeException;
@@ -56,6 +47,12 @@ import org.springframework.scripting.support.ScriptFactoryPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.tests.Assume;
import org.springframework.tests.TestGroup;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.util.ObjectUtils;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
import static org.mockito.Mockito.mock;
/**
* @author Rob Harrop
@@ -350,7 +347,9 @@ public class GroovyScriptFactoryTests {
@Test
public void testInlineScriptFromTag() throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("groovy-with-xsd.xml", getClass());
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("groovy-with-xsd.xml", getClass());
BeanDefinition bd = ctx.getBeanFactory().getBeanDefinition("calculator");
assertTrue(ObjectUtils.containsElement(bd.getDependsOn(), "messenger"));
Calculator calculator = (Calculator) ctx.getBean("calculator");
assertNotNull(calculator);
assertFalse(calculator instanceof Refreshable);