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
commit c4f79bb997
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,8 +16,8 @@
package org.springframework.aop.aspectj.autoproxy; package org.springframework.aop.aspectj.autoproxy;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.aopalliance.aop.Advice; import org.aopalliance.aop.Advice;
@ -67,29 +67,24 @@ public class AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProx
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected List<Advisor> sortAdvisors(List<Advisor> advisors) { protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
// build list for sorting
List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors = List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors =
new LinkedList<PartiallyComparableAdvisorHolder>(); new ArrayList<PartiallyComparableAdvisorHolder>(advisors.size());
for (Advisor element : advisors) { for (Advisor element : advisors) {
partiallyComparableAdvisors.add( partiallyComparableAdvisors.add(
new PartiallyComparableAdvisorHolder(element, DEFAULT_PRECEDENCE_COMPARATOR)); new PartiallyComparableAdvisorHolder(element, DEFAULT_PRECEDENCE_COMPARATOR));
} }
// sort it
List<PartiallyComparableAdvisorHolder> sorted = List<PartiallyComparableAdvisorHolder> sorted =
PartialOrder.sort(partiallyComparableAdvisors); PartialOrder.sort(partiallyComparableAdvisors);
if (sorted == null) { if (sorted != null) {
// TODO: work harder to give a better error message here. List<Advisor> result = new ArrayList<Advisor>(advisors.size());
throw new IllegalArgumentException("Advice precedence circularity error"); for (PartiallyComparableAdvisorHolder pcAdvisor : sorted) {
result.add(pcAdvisor.getAdvisor());
}
return result;
} }
else {
// extract results again return super.sortAdvisors(advisors);
List<Advisor> result = new LinkedList<Advisor>();
for (PartiallyComparableAdvisorHolder pcAdvisor : sorted) {
result.add(pcAdvisor.getAdvisor());
} }
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -53,7 +53,6 @@ class AspectJPrecedenceComparator implements Comparator {
private static final int HIGHER_PRECEDENCE = -1; private static final int HIGHER_PRECEDENCE = -1;
private static final int SAME_PRECEDENCE = 0; private static final int SAME_PRECEDENCE = 0;
private static final int LOWER_PRECEDENCE = 1; private static final int LOWER_PRECEDENCE = 1;
private static final int NOT_COMPARABLE = 0;
private final Comparator<? super Advisor> advisorComparator; private final Comparator<? super Advisor> advisorComparator;
@ -85,21 +84,11 @@ class AspectJPrecedenceComparator implements Comparator {
Advisor advisor1 = (Advisor) o1; Advisor advisor1 = (Advisor) o1;
Advisor advisor2 = (Advisor) o2; Advisor advisor2 = (Advisor) o2;
int advisorPrecedence = this.advisorComparator.compare(advisor1, advisor2);
boolean oneOrOtherIsAfterAdvice = if (advisorPrecedence == SAME_PRECEDENCE && declaredInSameAspect(advisor1, advisor2)) {
(AspectJAopUtils.isAfterAdvice(advisor1) || AspectJAopUtils.isAfterAdvice(advisor2)); advisorPrecedence = comparePrecedenceWithinAspect(advisor1, advisor2);
boolean oneOrOtherIsBeforeAdvice =
(AspectJAopUtils.isBeforeAdvice(advisor1) || AspectJAopUtils.isBeforeAdvice(advisor2));
if (oneOrOtherIsAfterAdvice && oneOrOtherIsBeforeAdvice) {
return NOT_COMPARABLE;
}
else {
int advisorPrecedence = this.advisorComparator.compare(advisor1, advisor2);
if (advisorPrecedence == SAME_PRECEDENCE && declaredInSameAspect(advisor1, advisor2)) {
advisorPrecedence = comparePrecedenceWithinAspect(advisor1, advisor2);
}
return advisorPrecedence;
} }
return advisorPrecedence;
} }
private int comparePrecedenceWithinAspect(Advisor advisor1, Advisor advisor2) { private int comparePrecedenceWithinAspect(Advisor advisor1, Advisor advisor2) {

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,12 +16,11 @@
package org.springframework.aop.aspectj.autoproxy; package org.springframework.aop.aspectj.autoproxy;
import static org.junit.Assert.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.aop.Advisor; import org.springframework.aop.Advisor;
import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.BeforeAdvice; import org.springframework.aop.BeforeAdvice;
@ -35,11 +34,13 @@ import org.springframework.aop.aspectj.AspectJMethodBeforeAdvice;
import org.springframework.aop.aspectj.AspectJPointcutAdvisor; import org.springframework.aop.aspectj.AspectJPointcutAdvisor;
import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.DefaultPointcutAdvisor;
import static org.junit.Assert.*;
/** /**
* @author Adrian Colyer * @author Adrian Colyer
* @author Chris Beams * @author Chris Beams
*/ */
public final class AspectJPrecedenceComparatorTests { public class AspectJPrecedenceComparatorTests {
private static final int HIGH_PRECEDENCE_ADVISOR_ORDER = 100; private static final int HIGH_PRECEDENCE_ADVISOR_ORDER = 100;
private static final int LOW_PRECEDENCE_ADVISOR_ORDER = 200; private static final int LOW_PRECEDENCE_ADVISOR_ORDER = 200;
@ -89,7 +90,7 @@ public final class AspectJPrecedenceComparatorTests {
public void testSameAspectOneOfEach() { public void testSameAspectOneOfEach() {
Advisor advisor1 = createAspectJAfterAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someAspect"); Advisor advisor1 = createAspectJAfterAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someAspect");
Advisor advisor2 = createAspectJBeforeAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, LATE_ADVICE_DECLARATION_ORDER, "someAspect"); Advisor advisor2 = createAspectJBeforeAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, LATE_ADVICE_DECLARATION_ORDER, "someAspect");
assertEquals("advisor1 and advisor2 not comparable", 0, this.comparator.compare(advisor1, advisor2)); assertEquals("advisor1 and advisor2 not comparable", 1, this.comparator.compare(advisor1, advisor2));
} }
@Test @Test

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -586,9 +586,10 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
for (BeanPostProcessor bp : getBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
Class processedType = ibp.predictBeanType(beanClass, beanName); Class predictedType = ibp.predictBeanType(beanClass, beanName);
if (processedType != null) { if (predictedType != null && (typesToMatch.length > 1 ||
return processedType; !FactoryBean.class.equals(typesToMatch[0]) || FactoryBean.class.isAssignableFrom(predictedType))) {
return predictedType;
} }
} }
} }

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -497,18 +497,21 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
// Retrieve corresponding bean definition. // Retrieve corresponding bean definition.
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
Class[] typesToMatch = (FactoryBean.class.equals(typeToMatch) ?
new Class[] {typeToMatch} : new Class[] {FactoryBean.class, typeToMatch});
// Check decorated bean definition, if any: We assume it'll be easier // Check decorated bean definition, if any: We assume it'll be easier
// to determine the decorated bean's type than the proxy's type. // to determine the decorated bean's type than the proxy's type.
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) { if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) {
RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd); RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd);
Class<?> targetClass = predictBeanType(dbd.getBeanName(), tbd, FactoryBean.class, typeToMatch); Class<?> targetClass = predictBeanType(dbd.getBeanName(), tbd, typesToMatch);
if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) { if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) {
return typeToMatch.isAssignableFrom(targetClass); return typeToMatch.isAssignableFrom(targetClass);
} }
} }
Class<?> beanClass = predictBeanType(beanName, mbd, FactoryBean.class, typeToMatch); Class<?> beanClass = predictBeanType(beanName, mbd, typesToMatch);
if (beanClass == null) { if (beanClass == null) {
return false; return false;
} }
@ -1332,9 +1335,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
* @param mbd the corresponding bean definition * @param mbd the corresponding bean definition
*/ */
protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) { protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) {
Class<?> predictedType = predictBeanType(beanName, mbd, FactoryBean.class); Class<?> beanClass = predictBeanType(beanName, mbd, FactoryBean.class);
return (predictedType != null && FactoryBean.class.isAssignableFrom(predictedType)) || return (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass));
(mbd.hasBeanClass() && FactoryBean.class.isAssignableFrom(mbd.getBeanClass()));
} }
/** /**

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,11 +16,11 @@
package org.springframework.beans.factory.xml; package org.springframework.beans.factory.xml;
import org.w3c.dom.Document;
import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.w3c.dom.Document;
/** /**
* SPI for parsing an XML document that contains Spring bean definitions. * SPI for parsing an XML document that contains Spring bean definitions.
* Used by XmlBeanDefinitionReader for actually parsing a DOM document. * Used by XmlBeanDefinitionReader for actually parsing a DOM document.
@ -38,20 +38,21 @@ import org.w3c.dom.Document;
public interface BeanDefinitionDocumentReader { public interface BeanDefinitionDocumentReader {
/** /**
* Read bean definitions from the given DOM document, * Set the Environment to use when reading bean definitions.
* and register them with the given bean factory. * <p>Used for evaluating profile information to determine whether a
* {@code <beans/>} document/element should be included or ignored.
*/
void setEnvironment(Environment environment);
/**
* Read bean definitions from the given DOM document and
* register them with the registry in the given reader context.
* @param doc the DOM document * @param doc the DOM document
* @param readerContext the current context of the reader. Includes the resource being parsed * @param readerContext the current context of the reader
* (includes the target registry and the resource being parsed)
* @throws BeanDefinitionStoreException in case of parsing errors * @throws BeanDefinitionStoreException in case of parsing errors
*/ */
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
throws BeanDefinitionStoreException; throws BeanDefinitionStoreException;
/**
* Set the Environment to use when reading bean definitions. Used for evaluating
* profile information to determine whether a {@code <beans/>} document/element should
* be included or omitted.
*/
void setEnvironment(Environment environment);
} }

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -72,16 +72,15 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume
public static final String RESOURCE_ATTRIBUTE = "resource"; public static final String RESOURCE_ATTRIBUTE = "resource";
/** @see org.springframework.context.annotation.Profile */
public static final String PROFILE_ATTRIBUTE = "profile"; public static final String PROFILE_ATTRIBUTE = "profile";
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
private XmlReaderContext readerContext;
private Environment environment; private Environment environment;
private XmlReaderContext readerContext;
private BeanDefinitionParserDelegate delegate; private BeanDefinitionParserDelegate delegate;
@ -104,13 +103,12 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume
*/ */
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext; this.readerContext = readerContext;
logger.debug("Loading bean definitions"); logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement(); Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root); doRegisterBeanDefinitions(root);
} }
/** /**
* Register each bean definition within the given root {@code <beans/>} element. * Register each bean definition within the given root {@code <beans/>} element.
* @throws IllegalStateException if {@code <beans profile="..."} attribute is present * @throws IllegalStateException if {@code <beans profile="..."} attribute is present
@ -120,21 +118,22 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume
protected void doRegisterBeanDefinitions(Element root) { protected void doRegisterBeanDefinitions(Element root) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) { if (StringUtils.hasText(profileSpec)) {
Assert.state(this.environment != null, "environment property must not be null"); Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!this.environment.acceptsProfiles(specifiedProfiles)) { if (!this.environment.acceptsProfiles(specifiedProfiles)) {
return; return;
} }
} }
// any nested <beans> elements will cause recursion in this method. In // Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly, // order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create // keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes, // the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference. // then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one. // this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate; BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createHelper(readerContext, root, parent); this.delegate = createHelper(this.readerContext, root, parent);
preProcessXml(root); preProcessXml(root);
parseBeanDefinitions(root, this.delegate); parseBeanDefinitions(root, this.delegate);
@ -143,7 +142,9 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume
this.delegate = parent; this.delegate = parent;
} }
protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) { protected BeanDefinitionParserDelegate createHelper(
XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext, environment); BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext, environment);
delegate.initDefaults(root, parentDelegate); delegate.initDefaults(root, parentDelegate);
return delegate; return delegate;

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +22,8 @@ import static org.junit.Assert.*;
import java.util.Map; import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
@ -49,6 +51,8 @@ public class Spr8954Tests {
assertThat(bf.getBean("foo"), instanceOf(Foo.class)); assertThat(bf.getBean("foo"), instanceOf(Foo.class));
assertThat(bf.getBean("&foo"), instanceOf(FooFactoryBean.class)); assertThat(bf.getBean("&foo"), instanceOf(FooFactoryBean.class));
assertThat(bf.isTypeMatch("&foo", FactoryBean.class), is(true));
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
Map<String, FactoryBean> fbBeans = bf.getBeansOfType(FactoryBean.class); Map<String, FactoryBean> fbBeans = bf.getBeansOfType(FactoryBean.class);
assertThat(1, equalTo(fbBeans.size())); assertThat(1, equalTo(fbBeans.size()));
@ -59,6 +63,25 @@ public class Spr8954Tests {
assertThat("&foo", equalTo(aiBeans.keySet().iterator().next())); assertThat("&foo", equalTo(aiBeans.keySet().iterator().next()));
} }
@Test
public void findsBeansByTypeIfNotInstantiated() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerBeanDefinition("foo", new RootBeanDefinition(FooFactoryBean.class));
bf.addBeanPostProcessor(new PredictingBPP());
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 FooFactoryBean implements FactoryBean<Foo>, AnInterface { static class FooFactoryBean implements FactoryBean<Foo>, AnInterface {
@Override @Override
@ -84,7 +107,9 @@ public class Spr8954Tests {
} }
interface PredictedType { interface PredictedType {
}
static class PredictedTypeImpl implements PredictedType {
} }
static class PredictingBPP extends InstantiationAwareBeanPostProcessorAdapter { static class PredictingBPP extends InstantiationAwareBeanPostProcessorAdapter {
@ -92,8 +117,8 @@ public class Spr8954Tests {
@Override @Override
public Class<?> predictBeanType(Class<?> beanClass, String beanName) { public Class<?> predictBeanType(Class<?> beanClass, String beanName) {
return FactoryBean.class.isAssignableFrom(beanClass) ? return FactoryBean.class.isAssignableFrom(beanClass) ?
PredictedType.class : PredictedType.class : null;
super.predictBeanType(beanClass, beanName);
} }
} }
} }

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; 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. * Execute the annotated method with a fixed period between invocations.
* @return the period in milliseconds * @return the period in milliseconds
*/ */
long fixedRate() default -1; 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 * Number of milliseconds to delay before the first execution of a
* {@link #fixedRate()} or {@link #fixedDelay()} task. * {@link #fixedRate()} or {@link #fixedDelay()} task.
* @return the initial delay in milliseconds * @return the initial delay in milliseconds
* @since 3.2 * @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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,7 +17,6 @@
package org.springframework.scheduling.annotation; package org.springframework.scheduling.annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@ -111,53 +110,115 @@ public class ScheduledAnnotationBeanPostProcessor
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Scheduled annotation = AnnotationUtils.getAnnotation(method, Scheduled.class); Scheduled annotation = AnnotationUtils.getAnnotation(method, Scheduled.class);
if (annotation != null) { if (annotation != null) {
Assert.isTrue(void.class.equals(method.getReturnType()), try {
"Only void-returning methods may be annotated with @Scheduled."); Assert.isTrue(void.class.equals(method.getReturnType()),
Assert.isTrue(method.getParameterTypes().length == 0, "Only void-returning methods may be annotated with @Scheduled");
"Only no-arg methods may be annotated with @Scheduled."); Assert.isTrue(method.getParameterTypes().length == 0,
if (AopUtils.isJdkDynamicProxy(bean)) { "Only no-arg methods may be annotated with @Scheduled");
try { if (AopUtils.isJdkDynamicProxy(bean)) {
// found a @Scheduled method on the target class for this JDK proxy -> is it try {
// also present on the proxy itself? // found a @Scheduled method on the target class for this JDK proxy -> is it
method = bean.getClass().getMethod(method.getName(), method.getParameterTypes()); // 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) { Runnable runnable = new ScheduledMethodRunnable(bean, method);
ReflectionUtils.handleReflectionException(ex); 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) { // Check cron expression
throw new IllegalStateException(String.format( String cron = annotation.cron();
"@Scheduled method '%s' found on bean target class '%s', " + if (!"".equals(cron)) {
"but not found in any interface(s) for bean JDK proxy. Either " + Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
"pull the method up to an interface or switch to subclass (CGLIB) " + processedSchedule = true;
"proxies by setting proxy-target-class/proxyTargetClass " + if (embeddedValueResolver != null) {
"attribute to 'true'", method.getName(), targetClass.getSimpleName())); cron = embeddedValueResolver.resolveStringValue(cron);
}
registrar.addCronTask(new CronTask(runnable, cron));
} }
} // At this point we don't need to differentiate between initial delay set or not anymore
Runnable runnable = new ScheduledMethodRunnable(bean, method); if (initialDelay < 0) {
boolean processedSchedule = false; initialDelay = 0;
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);
} }
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(); catch (IllegalArgumentException ex) {
long fixedDelay = annotation.fixedDelay(); throw new IllegalStateException(
if (fixedDelay >= 0) { "Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
registrar.addFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay));
} }
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) { if (event.getApplicationContext() != this.applicationContext) {
return; return;
} }
Map<String, SchedulingConfigurer> configurers = Map<String, SchedulingConfigurer> configurers =
this.applicationContext.getBeansOfType(SchedulingConfigurer.class); this.applicationContext.getBeansOfType(SchedulingConfigurer.class);
if (this.scheduler != null) { if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler); this.registrar.setScheduler(this.scheduler);
} }
for (SchedulingConfigurer configurer : configurers.values()) { for (SchedulingConfigurer configurer : configurers.values()) {
configurer.configureTasks(this.registrar); configurer.configureTasks(this.registrar);
} }
if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) { if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
Map<String, ? super Object> schedulers = new HashMap<String, Object>(); Map<String, ? super Object> schedulers = new HashMap<String, Object>();
schedulers.putAll(applicationContext.getBeansOfType(TaskScheduler.class)); schedulers.putAll(applicationContext.getBeansOfType(TaskScheduler.class));
@ -199,7 +256,6 @@ public class ScheduledAnnotationBeanPostProcessor
"configureTasks() callback. Found the following beans: " + schedulers.keySet()); "configureTasks() callback. Found the following beans: " + schedulers.keySet());
} }
} }
this.registrar.afterPropertiesSet(); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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) { public IntervalTask(Runnable runnable, long interval, long initialDelay) {
super(runnable); super(runnable);
this.initialDelay = initialDelay;
this.interval = interval; this.interval = interval;
this.initialDelay = initialDelay;
} }
/** /**
@ -59,10 +59,11 @@ public class IntervalTask extends Task {
public long getInterval() { public long getInterval() {
return interval; return this.interval;
} }
public long getInitialDelay() { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; private final TimeZone timeZone;
/** /**
* Construct a {@link CronSequenceGenerator} from the pattern provided. * Construct a {@link CronSequenceGenerator} from the pattern provided.
* @param expression a space-separated list of time fields * @param expression a space-separated list of time fields
@ -81,6 +82,7 @@ public class CronSequenceGenerator {
parse(expression); parse(expression);
} }
/** /**
* Get the next {@link Date} in the sequence matching the Cron pattern and * 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 * 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); int updateMinute = findNext(this.minutes, minute, calendar, Calendar.MINUTE, Calendar.HOUR_OF_DAY, resets);
if (minute == updateMinute) { if (minute == updateMinute) {
resets.add(Calendar.MINUTE); resets.add(Calendar.MINUTE);
} else { }
else {
doNext(calendar, dot); 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); int updateHour = findNext(this.hours, hour, calendar, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_WEEK, resets);
if (hour == updateHour) { if (hour == updateHour) {
resets.add(Calendar.HOUR_OF_DAY); resets.add(Calendar.HOUR_OF_DAY);
} else { }
else {
doNext(calendar, dot); doNext(calendar, dot);
} }
@ -152,7 +156,8 @@ public class CronSequenceGenerator {
int updateDayOfMonth = findNextDay(calendar, this.daysOfMonth, dayOfMonth, daysOfWeek, dayOfWeek, resets); int updateDayOfMonth = findNextDay(calendar, this.daysOfMonth, dayOfMonth, daysOfWeek, dayOfWeek, resets);
if (dayOfMonth == updateDayOfMonth) { if (dayOfMonth == updateDayOfMonth) {
resets.add(Calendar.DAY_OF_MONTH); resets.add(Calendar.DAY_OF_MONTH);
} else { }
else {
doNext(calendar, dot); doNext(calendar, dot);
} }
@ -160,7 +165,8 @@ public class CronSequenceGenerator {
int updateMonth = findNext(this.months, month, calendar, Calendar.MONTH, Calendar.YEAR, resets); int updateMonth = findNext(this.months, month, calendar, Calendar.MONTH, Calendar.YEAR, resets);
if (month != updateMonth) { if (month != updateMonth) {
if (calendar.get(Calendar.YEAR) - dot > 4) { 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); doNext(calendar, dot);
} }
@ -181,7 +187,7 @@ public class CronSequenceGenerator {
reset(calendar, resets); reset(calendar, resets);
} }
if (count >= max) { 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; 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. * Parse the given pattern expression.
@ -230,8 +237,8 @@ public class CronSequenceGenerator {
private void parse(String expression) throws IllegalArgumentException { private void parse(String expression) throws IllegalArgumentException {
String[] fields = StringUtils.tokenizeToStringArray(expression, " "); String[] fields = StringUtils.tokenizeToStringArray(expression, " ");
if (fields.length != 6) { if (fields.length != 6) {
throw new IllegalArgumentException(String.format("" throw new IllegalArgumentException(String.format(
+ "cron expression must consist of 6 fields (found %d in %s)", fields.length, expression)); "Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));
} }
setNumberHits(this.seconds, fields[0], 0, 60); setNumberHits(this.seconds, fields[0], 0, 60);
setNumberHits(this.minutes, fields[1], 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) // Not an incrementer so it must be a range (possibly empty)
int[] range = getRange(field, min, max); int[] range = getRange(field, min, max);
bits.set(range[0], range[1] + 1); bits.set(range[0], range[1] + 1);
} else { }
else {
String[] split = StringUtils.delimitedListToStringArray(field, "/"); String[] split = StringUtils.delimitedListToStringArray(field, "/");
if (split.length > 2) { 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); int[] range = getRange(split[0], min, max);
if (!split[0].contains("-")) { if (!split[0].contains("-")) {
@ -322,19 +331,23 @@ public class CronSequenceGenerator {
} }
if (!field.contains("-")) { if (!field.contains("-")) {
result[0] = result[1] = Integer.valueOf(field); result[0] = result[1] = Integer.valueOf(field);
} else { }
else {
String[] split = StringUtils.delimitedListToStringArray(field, "-"); String[] split = StringUtils.delimitedListToStringArray(field, "-");
if (split.length > 2) { 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[0] = Integer.valueOf(split[0]);
result[1] = Integer.valueOf(split[1]); result[1] = Integer.valueOf(split[1]);
} }
if (result[0] >= max || result[1] >= max) { 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) { 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; 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.BeanDefinitionDefaults;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; 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.ParserContext;
import org.springframework.beans.factory.xml.XmlReaderContext; import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.scripting.support.ScriptFactoryPostProcessor; 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 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 INIT_METHOD_ATTRIBUTE = "init-method";
private static final String DESTROY_METHOD_ATTRIBUTE = "destroy-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); String dependencyCheck = element.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
bd.setDependencyCheck(parserContext.getDelegate().getDependencyCheck(dependencyCheck)); 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 // Retrieve the defaults for bean definitions within this parser context
BeanDefinitionDefaults beanDefinitionDefaults = parserContext.getDelegate().getBeanDefinitionDefaults(); BeanDefinitionDefaults beanDefinitionDefaults = parserContext.getDelegate().getBeanDefinitionDefaults();

View File

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

View File

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

View File

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

View File

@ -147,13 +147,13 @@
<xsd:attribute name="depends-on" type="xsd:string"> <xsd:attribute name="depends-on" type="xsd:string">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
The names of the beans that this bean depends on being initialized. The names of the beans that this bean depends on being initialized.
The bean factory will guarantee that these beans get initialized The bean factory will guarantee that these beans get initialized
before this bean. before this bean.
Note that dependencies are normally expressed through bean properties. Note that dependencies are normally expressed through bean properties.
This property should just be necessary other kinds of dependencies This property should just be necessary for other kinds of dependencies
like statics (*ugh*) or database preparation on startup. like statics (*ugh*) or database preparation on startup.
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </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 java.util.Properties;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
@ -52,8 +53,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
public void fixedDelayTask() { public void fixedDelayTask() {
StaticApplicationContext context = new StaticApplicationContext(); StaticApplicationContext context = new StaticApplicationContext();
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition( BeanDefinition targetDefinition = new RootBeanDefinition(FixedDelayTestBean.class);
ScheduledAnnotationBeanPostProcessorTests.FixedDelayTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
@ -106,8 +106,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
public void fixedRateTaskWithInitialDelay() { public void fixedRateTaskWithInitialDelay() {
StaticApplicationContext context = new StaticApplicationContext(); StaticApplicationContext context = new StaticApplicationContext();
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition( BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateWithInitialDelayTestBean.class);
ScheduledAnnotationBeanPostProcessorTests.FixedRateWithInitialDelayTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
@ -162,8 +161,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
public void metaAnnotationWithFixedRate() { public void metaAnnotationWithFixedRate() {
StaticApplicationContext context = new StaticApplicationContext(); StaticApplicationContext context = new StaticApplicationContext();
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition( BeanDefinition targetDefinition = new RootBeanDefinition(MetaAnnotationFixedRateTestBean.class);
ScheduledAnnotationBeanPostProcessorTests.MetaAnnotationFixedRateTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
context.refresh(); context.refresh();
@ -211,7 +209,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Test @Test
public void propertyPlaceholderWithCronExpression() { public void propertyPlaceholderWithCron() {
String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; String businessHoursCronExpression = "0 0 9-17 * * MON-FRI";
StaticApplicationContext context = new StaticApplicationContext(); StaticApplicationContext context = new StaticApplicationContext();
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
@ -219,8 +217,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty("schedules.businessHours", businessHoursCronExpression); properties.setProperty("schedules.businessHours", businessHoursCronExpression);
placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties); placeholderDefinition.getPropertyValues().addPropertyValue("properties", properties);
BeanDefinition targetDefinition = new RootBeanDefinition( BeanDefinition targetDefinition = new RootBeanDefinition(PropertyPlaceholderWithCronTestBean.class);
ScheduledAnnotationBeanPostProcessorTests.PropertyPlaceholderTestBean.class);
context.registerBeanDefinition("placeholder", placeholderDefinition); context.registerBeanDefinition("placeholder", placeholderDefinition);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
@ -242,6 +239,70 @@ public class ScheduledAnnotationBeanPostProcessorTests {
assertEquals(businessHoursCronExpression, task.getExpression()); 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 @Test
public void propertyPlaceholderForMetaAnnotation() { public void propertyPlaceholderForMetaAnnotation() {
String businessHoursCronExpression = "0 0 9-17 * * MON-FRI"; String businessHoursCronExpression = "0 0 9-17 * * MON-FRI";
@ -285,7 +346,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
context.refresh(); context.refresh();
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = BeanCreationException.class)
public void invalidCron() throws Throwable { public void invalidCron() throws Throwable {
StaticApplicationContext context = new StaticApplicationContext(); StaticApplicationContext context = new StaticApplicationContext();
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class); BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
@ -293,12 +354,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
ScheduledAnnotationBeanPostProcessorTests.InvalidCronTestBean.class); ScheduledAnnotationBeanPostProcessorTests.InvalidCronTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition); context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition); context.registerBeanDefinition("target", targetDefinition);
try { context.refresh();
context.refresh();
fail("expected exception");
} catch (BeanCreationException ex) {
throw ex.getRootCause();
}
} }
@Test(expected = BeanCreationException.class) @Test(expected = BeanCreationException.class)
@ -342,7 +398,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
static class FixedRateWithInitialDelayTestBean { static class FixedRateWithInitialDelayTestBean {
@Scheduled(initialDelay=1000, fixedRate=3000) @Scheduled(fixedRate=3000, initialDelay=1000)
public void fixedRate() { public void fixedRate() {
} }
} }
@ -395,13 +451,13 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
@Scheduled(fixedRate = 5000) @Scheduled(fixedRate=5000)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
private static @interface EveryFiveSeconds {} private static @interface EveryFiveSeconds {}
@Scheduled(cron = "0 0 * * * ?") @Scheduled(cron="0 0 * * * ?")
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
private static @interface Hourly {} private static @interface Hourly {}
@ -423,7 +479,7 @@ public class ScheduledAnnotationBeanPostProcessorTests {
} }
static class PropertyPlaceholderTestBean { static class PropertyPlaceholderWithCronTestBean {
@Scheduled(cron = "${schedules.businessHours}") @Scheduled(cron = "${schedules.businessHours}")
public void x() { 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) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
private static @interface BusinessHours {} 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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)); assertEquals(calendar.getTime(), date = trigger.nextExecutionTime(context2));
} }
@Test(expected=IllegalStateException.class) @Test(expected = IllegalArgumentException.class)
public void testNonExistentSpecificDate() throws Exception { public void testNonExistentSpecificDate() throws Exception {
// TODO: maybe try and detect this as a special case in parser? // TODO: maybe try and detect this as a special case in parser?
CronTrigger trigger = new CronTrigger("0 0 0 31 6 *", timeZone); CronTrigger trigger = new CronTrigger("0 0 0 31 6 *", timeZone);

View File

@ -16,31 +16,22 @@
package org.springframework.scripting.groovy; 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.io.FileNotFoundException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import groovy.lang.DelegatingMetaClass;
import groovy.lang.GroovyObject;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.AopUtils;
import org.springframework.aop.target.dynamic.Refreshable; 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.BeanCreationException;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.NestedRuntimeException; import org.springframework.core.NestedRuntimeException;
@ -56,6 +47,12 @@ import org.springframework.scripting.support.ScriptFactoryPostProcessor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.tests.Assume; import org.springframework.tests.Assume;
import org.springframework.tests.TestGroup; 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 * @author Rob Harrop
@ -350,7 +347,9 @@ public class GroovyScriptFactoryTests {
@Test @Test
public void testInlineScriptFromTag() throws Exception { 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"); Calculator calculator = (Calculator) ctx.getBean("calculator");
assertNotNull(calculator); assertNotNull(calculator);
assertFalse(calculator instanceof Refreshable); assertFalse(calculator instanceof Refreshable);

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 * 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 * use this file except in compliance with the License. You may obtain a copy of
@ -334,8 +334,9 @@ public abstract class Assert {
public static void isInstanceOf(Class type, Object obj, String message) { public static void isInstanceOf(Class type, Object obj, String message) {
notNull(type, "Type to check against must not be null"); notNull(type, "Type to check against must not be null");
if (!type.isInstance(obj)) { if (!type.isInstance(obj)) {
throw new IllegalArgumentException(message + throw new IllegalArgumentException(
". Object of class [" + (obj != null ? obj.getClass().getName() : "null") + (StringUtils.hasLength(message) ? message + " " : "") +
"Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
"] must be an instance of " + type); "] must be an instance of " + type);
} }
} }

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,7 +24,9 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
/** /**
* Unit tests for the {@link Assert} class. * Unit tests for the {@link Assert} class.
@ -36,6 +38,9 @@ import org.junit.Test;
*/ */
public class AssertTests { public class AssertTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void instanceOf() { public void instanceOf() {
final Set<?> set = new HashSet<Object>(); final Set<?> set = new HashSet<Object>();
@ -43,6 +48,22 @@ public class AssertTests {
Assert.isInstanceOf(HashMap.class, set); Assert.isInstanceOf(HashMap.class, set);
} }
@Test
public void instanceOfNoMessage() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Object of class [java.lang.Object] must be an instance " +
"of interface java.util.Set");
Assert.isInstanceOf(Set.class, new Object(), null);
}
@Test
public void instanceOfMessage() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Custom message. Object of class [java.lang.Object] must " +
"be an instance of interface java.util.Set");
Assert.isInstanceOf(Set.class, new Object(), "Custom message.");
}
@Test @Test
public void isNullDoesNotThrowExceptionIfArgumentIsNullWithMessage() { public void isNullDoesNotThrowExceptionIfArgumentIsNullWithMessage() {
Assert.isNull(null, "Bla"); Assert.isNull(null, "Bla");

View File

@ -6,10 +6,13 @@ http://www.springsource.org
Changes in version 3.2.2 (2013-03-07) Changes in version 3.2.2 (2013-03-07)
-------------------------------------- --------------------------------------
* official support for Hibernate 4.2 * official support for Hibernate 4.2 (SPR-10255)
* ConfigurationClassPostProcessor consistently uses ClassLoader, not loading core JDK annotations via ASM (SPR-10249) * ConfigurationClassPostProcessor consistently uses ClassLoader, not loading core JDK annotations via ASM (SPR-10249)
* ConfigurationClassPostProcessor allows for overriding of scoped-proxy bean definitions (SPR-10265) * ConfigurationClassPostProcessor allows for overriding of scoped-proxy bean definitions (SPR-10265)
* "depends-on" attribute on lang namespace element actually respected at runtime now (SPR-8625)
* allow for ordering of mixed AspectJ before/after advices (SPR-9438)
* added "maximumAutoGrowSize" property to SpelParserConfiguration (SPR-10229) * added "maximumAutoGrowSize" property to SpelParserConfiguration (SPR-10229)
* @Scheduled provides String variants of fixedDelay, fixedRate, initialDelay for placeholder support (SPR-8067)
* SQLErrorCodeSQLExceptionTranslator tries to find SQLException with actual error code among causes (SPR-10260) * SQLErrorCodeSQLExceptionTranslator tries to find SQLException with actual error code among causes (SPR-10260)
* DefaultMessageListenerContainer invokes specified ExceptionListener for recovery exceptions as well (SPR-10230) * DefaultMessageListenerContainer invokes specified ExceptionListener for recovery exceptions as well (SPR-10230)
* DefaultMessageListenerContainer logs recovery failures at error level and exposes "isRecovering()" method (SPR-10230) * DefaultMessageListenerContainer logs recovery failures at error level and exposes "isRecovering()" method (SPR-10230)

View File

@ -13,18 +13,25 @@
<section xml:id="beans-factory-lifecycle"> <section xml:id="beans-factory-lifecycle">
<title>Lifecycle callbacks</title> <title>Lifecycle callbacks</title>
<!-- MLP Beverly to review: Old Text: The Spring Framework provides several callback interfaces to
change the behavior of your bean in the container; they include -->
<para>To interact with the container's management of the bean lifecycle, you <para>To interact with the container's management of the bean lifecycle, you
can implement the Spring <interfacename>InitializingBean</interfacename> can implement the Spring <interfacename>InitializingBean</interfacename>
and <interfacename>DisposableBean</interfacename> interfaces. The and <interfacename>DisposableBean</interfacename> interfaces. The
container calls <methodname>afterPropertiesSet()</methodname> for the container calls <methodname>afterPropertiesSet()</methodname> for the
former and <methodname>destroy()</methodname> for the latter to allow the former and <methodname>destroy()</methodname> for the latter to allow the
bean to perform certain actions upon initialization and destruction of bean to perform certain actions upon initialization and destruction of
your beans. You can also achieve the same integration with the container your beans.</para>
without coupling your classes to Spring interfaces through the use of
init-method and destroy method object definition metadata.</para> <tip>
<para>The JSR-250 <interfacename>@PostConstruct</interfacename> and
<interfacename>@PreDestroy</interfacename> annotations are generally
considered best practice for receiving lifecycle callbacks in a modern
Spring application. Using these annotations means that your beans are not
coupled to Spring specific interfaces. For details see
<xref linkend="beans-postconstruct-and-predestroy-annotations"/>.</para>
<para>If you don't want to use the JSR-250 annotations but you are still
looking to remove coupling consider the use of init-method and destroy-method
object definition metadata.</para>
</tip>
<para>Internally, the Spring Framework uses <para>Internally, the Spring Framework uses
<interfacename>BeanPostProcessor</interfacename> implementations to <interfacename>BeanPostProcessor</interfacename> implementations to
@ -57,7 +64,9 @@
<para>It is recommended that you do not use the <para>It is recommended that you do not use the
<interfacename>InitializingBean</interfacename> interface because it <interfacename>InitializingBean</interfacename> interface because it
unnecessarily couples the code to Spring. Alternatively, specify a POJO unnecessarily couples the code to Spring. Alternatively, use the
<link linkend="beans-postconstruct-and-predestroy-annotations">
<interfacename>@PostConstruct</interfacename> annotation</link> or specify a POJO
initialization method. In the case of XML-based configuration metadata, initialization method. In the case of XML-based configuration metadata,
you use the <literal>init-method</literal> attribute to specify the name you use the <literal>init-method</literal> attribute to specify the name
of the method that has a void no-argument signature. For example, the of the method that has a void no-argument signature. For example, the
@ -99,7 +108,9 @@
<para>It is recommended that you do not use the <para>It is recommended that you do not use the
<interfacename>DisposableBean</interfacename> callback interface because <interfacename>DisposableBean</interfacename> callback interface because
it unnecessarily couples the code to Spring. Alternatively, specify a it unnecessarily couples the code to Spring. Alternatively, use the
<link linkend="beans-postconstruct-and-predestroy-annotations">
<interfacename>@PreDestroy</interfacename> annotation</link> or specify a
generic method that is supported by bean definitions. With XML-based generic method that is supported by bean definitions. With XML-based
configuration metadata, you use the <literal>destroy-method</literal> configuration metadata, you use the <literal>destroy-method</literal>
attribute on the <literal>&lt;bean/&gt;</literal>. For example, the attribute on the <literal>&lt;bean/&gt;</literal>. For example, the