diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Autowire.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Autowire.java index bb1eba859d8..e26c4515c59 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Autowire.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Autowire.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,14 +34,6 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory; */ public enum Autowire { - /** - * Constant that indicates that autowiring information was not specified. - * In some cases it may be necessary to specify autowiring status, - * but merely confirm that this should be inherited from an enclosing - * container definition scope. - */ - INHERITED(-1), - /** * Constant that indicates no autowiring at all. */ diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index c3c7fd9ee89..2a641819468 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -54,7 +54,6 @@ import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -102,8 +101,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean protected final Log logger = LogFactory.getLog(AutowiredAnnotationBeanPostProcessor.class); @SuppressWarnings("unchecked") - private Class[] autowiredAnnotationTypes = - new Class[] {Autowired.class, Qualifier.class, Value.class}; + private Class[] autowiredAnnotationTypes = new Class[] {Autowired.class, Value.class}; private String requiredParameterName = "required"; @@ -317,29 +315,19 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean }); ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { public void doWith(Method method) { - if (!isFactoryMethod(method)) { - Annotation annotation = findAutowiredAnnotation(method); - if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { - if (Modifier.isStatic(method.getModifiers())) { - throw new IllegalStateException("Autowired annotation is not supported on static methods"); - } - if (method.getParameterTypes().length == 0) { - throw new IllegalStateException("Autowired annotation requires at least one argument: " + method); - } - boolean required = determineRequiredStatus(annotation); - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); - newMetadata.addInjectedMethod(new AutowiredMethodElement(method, required, pd)); + Annotation annotation = findAutowiredAnnotation(method); + if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { + if (Modifier.isStatic(method.getModifiers())) { + throw new IllegalStateException("Autowired annotation is not supported on static methods"); + } + if (method.getParameterTypes().length == 0) { + throw new IllegalStateException("Autowired annotation requires at least one argument: " + method); } + boolean required = determineRequiredStatus(annotation); + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); + newMetadata.addInjectedMethod(new AutowiredMethodElement(method, required, pd)); } } - - private boolean isFactoryMethod(Method method) { - if (AnnotationUtils.findAnnotation(method, FactoryMethod.class)!= null) { - return true; - } else { - return false; - } - } }); metadata = newMetadata; this.injectionMetadataCache.put(clazz, metadata); @@ -429,7 +417,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { Field field = (Field) this.member; try { - Object value = null; + Object value; if (this.cached) { if (this.cachedFieldValue instanceof DependencyDescriptor) { DependencyDescriptor descriptor = (DependencyDescriptor) this.cachedFieldValue; diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java index bf73389df0e..bda5d2b7fc5 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java @@ -17,6 +17,7 @@ package org.springframework.beans.factory.annotation; import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -25,9 +26,9 @@ import org.springframework.beans.SimpleTypeConverter; import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.DependencyDescriptor; -import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.AutowireCandidateResolver; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; @@ -134,8 +135,14 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan return true; } boolean match = checkQualifiers(bdHolder, descriptor.getAnnotations()); - if (match && descriptor.getMethodParameter() != null) { - match = checkQualifiers(bdHolder, descriptor.getMethodParameter().getAnnotations()); + if (match) { + MethodParameter methodParam = descriptor.getMethodParameter(); + if (methodParam != null) { + Method method = methodParam.getMethod(); + if (method == null || void.class.equals(method.getReturnType())) { + match = checkQualifiers(bdHolder, methodParam.getAnnotations()); + } + } } return match; } @@ -178,15 +185,21 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) { Class type = annotation.annotationType(); - AbstractBeanDefinition bd = (AbstractBeanDefinition) bdHolder.getBeanDefinition(); + RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition(); AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName()); if (qualifier == null) { qualifier = bd.getQualifier(ClassUtils.getShortName(type)); } - if (qualifier == null && bd.hasBeanClass()) { - // look for matching annotation on the target class - Class beanClass = bd.getBeanClass(); - Annotation targetAnnotation = beanClass.getAnnotation(type); + if (qualifier == null) { + Annotation targetAnnotation = null; + if (bd.getFactoryMethodForIntrospection() != null) { + targetAnnotation = bd.getFactoryMethodForIntrospection().getAnnotation(type); + } + if (targetAnnotation == null && bd.hasBeanClass()) { + // look for matching annotation on the target class + Class beanClass = bd.getBeanClass(); + targetAnnotation = beanClass.getAnnotation(type); + } if (targetAnnotation != null && targetAnnotation.equals(annotation)) { return true; } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/ScopedProxy.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/ScopedProxy.java deleted file mode 100644 index 1aeb505389a..00000000000 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/ScopedProxy.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2002-2008 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.beans.factory.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - - -/** - * Marker annotation identical in functionality with <aop:scoped-proxy/> tag. Provides a smart - * proxy backed by a scoped bean, which can be injected into object instances (usually singletons) - * allowing the same reference to be held while delegating method invocations to the backing, scoped - * beans. - * - * @author Costin Leau - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface ScopedProxy { - - /** - * Use CGLib-based class proxies (true) or JDK interface-based (false). - * - * Default is CGLib (true). - * @return - */ - boolean proxyTargetClass() default true; -} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java index 24fd58f233a..9dffd88b3dd 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,7 +98,10 @@ public interface AutowireCapableBeanFactory extends BeanFactory { * through introspection of the bean class. * @see #createBean * @see #autowire + * @deprecated as of Spring 3.0: If you are using mixed autowiring strategies, + * prefer annotation-based autowiring for clearer demarcation of autowiring needs. */ + @Deprecated int AUTOWIRE_AUTODETECT = 4; @@ -181,7 +184,6 @@ public interface AutowireCapableBeanFactory extends BeanFactory { * @see #AUTOWIRE_BY_NAME * @see #AUTOWIRE_BY_TYPE * @see #AUTOWIRE_CONSTRUCTOR - * @see #AUTOWIRE_AUTODETECT */ Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException; diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java index 7eab8d2fa06..db9bb30f114 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -143,6 +143,30 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { */ void setScope(String scope); + /** + * Return whether this bean should be lazily initialized, i.e. not + * eagerly instantiated on startup. Only applicable to a singleton bean. + */ + boolean isLazyInit(); + + /** + * Set whether this bean should be lazily initialized. + *

If false, the bean will get instantiated on startup by bean + * factories that perform eager initialization of singletons. + */ + void setLazyInit(boolean lazyInit); + + /** + * Return the bean names that this bean depends on. + */ + String[] getDependsOn(); + + /** + * Set the names of the beans that this bean depends on being initialized. + * The bean factory will guarantee that these beans get initialized first. + */ + void setDependsOn(String[] dependsOn); + /** * Return whether this bean is a candidate for getting autowired into some other bean. */ @@ -153,6 +177,20 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { */ void setAutowireCandidate(boolean autowireCandidate); + /** + * Return whether this bean is a primary autowire candidate. + * If this value is true for exactly one bean among multiple + * matching candidates, it will serve as a tie-breaker. + */ + boolean isPrimary(); + + /** + * Set whether this bean is a primary autowire candidate. + *

If this value is true for exactly one bean among multiple + * matching candidates, it will serve as a tie-breaker. + */ + void setPrimary(boolean primary); + /** * Return the constructor argument values for this bean. @@ -180,12 +218,6 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { */ boolean isAbstract(); - /** - * Return whether this bean should be lazily initialized, that is, not - * eagerly instantiated on startup. - */ - boolean isLazyInit(); - /** * Get the role hint for this BeanDefinition. The role hint * provides tools with an indication of the importance of a particular diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 8b53368f297..ad5e09344d6 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -524,7 +524,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac @Override protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class[] typesToMatch) { - Class beanClass = null; + Class beanClass; if (mbd.getFactoryMethodName() != null) { beanClass = getTypeForFactoryMethod(beanName, mbd, typesToMatch); } @@ -562,7 +562,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac * @see #createBean */ protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class[] typesToMatch) { - Class factoryClass = null; + Class factoryClass; boolean isStatic = true; String factoryBeanName = mbd.getFactoryBeanName(); @@ -1203,7 +1203,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } MutablePropertyValues mpvs = null; - List original = null; + List original; if (pvs instanceof MutablePropertyValues) { mpvs = (MutablePropertyValues) pvs; diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java index 93621785b4e..b0fdd5320ad 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java @@ -16,9 +16,9 @@ package org.springframework.beans.factory.support; +import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.util.Arrays; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -83,7 +83,10 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess * Constant that indicates determining an appropriate autowire strategy * through introspection of the bean class. * @see #setAutowireMode + * @deprecated as of Spring 3.0: If you are using mixed autowiring strategies, + * use annotation-based autowiring for clearer demarcation of autowiring needs. */ + @Deprecated public static final int AUTOWIRE_AUTODETECT = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT; @@ -134,11 +137,11 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess private boolean autowireCandidate = true; + private boolean primary = false; + private final Map qualifiers = new LinkedHashMap(); - private boolean primary = false; - private ConstructorArgumentValues constructorArgumentValues; private MutablePropertyValues propertyValues; @@ -317,8 +320,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess */ public void applyDefaults(BeanDefinitionDefaults defaults) { setLazyInit(defaults.isLazyInit()); - setDependencyCheck(defaults.getDependencyCheck()); setAutowireMode(defaults.getAutowireMode()); + setDependencyCheck(defaults.getDependencyCheck()); setInitMethodName(defaults.getInitMethodName()); setEnforceInitMethod(false); setDestroyMethodName(defaults.getDestroyMethodName()); @@ -336,7 +339,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess /** * Specify the class for this bean. */ - public void setBeanClass(Class beanClass) { + public void setBeanClass(Class beanClass) { this.beanClass = beanClass; } @@ -346,7 +349,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess * @throws IllegalStateException if the bean definition does not define a bean class, * or a specified bean class name has not been resolved into an actual Class */ - public Class getBeanClass() throws IllegalStateException { + public Class getBeanClass() throws IllegalStateException { Object beanClassObject = this.beanClass; if (beanClassObject == null) { throw new IllegalStateException("No bean class specified on bean definition"); @@ -556,7 +559,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess /** * Set the names of the beans that this bean depends on being initialized. - * The bean factory will guarantee that these beans get initialized before. + * The bean factory will guarantee that these beans get initialized first. *

Note that dependencies are normally expressed through bean properties or * constructor arguments. This property should just be necessary for other kinds * of dependencies like statics (*ugh*) or database preparation on startup. @@ -586,6 +589,24 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess return this.autowireCandidate; } + /** + * Set whether this bean is a primary autowire candidate. + * If this value is true for exactly one bean among multiple + * matching candidates, it will serve as a tie-breaker. + */ + public void setPrimary(boolean primary) { + this.primary = primary; + } + + /** + * Return whether this bean is a primary autowire candidate. + * If this value is true for exactly one bean among multiple + * matching candidates, it will serve as a tie-breaker. + */ + public boolean isPrimary() { + return this.primary; + } + /** * Register a qualifier to be used for autowire candidate resolution, * keyed by the qualifier's type name. @@ -626,24 +647,6 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess this.qualifiers.putAll(source.qualifiers); } - /** - * Set whether this bean is a primary autowire candidate. - * If this value is true for exactly one bean among multiple - * matching candidates, it will serve as a tie-breaker. - */ - public void setPrimary(boolean primary) { - this.primary = primary; - } - - /** - * Return whether this bean is a primary autowire candidate. - * If this value is true for exactly one bean among multiple - * matching candidates, it will serve as a tie-breaker. - */ - public boolean isPrimary() { - return this.primary; - } - /** * Specify constructor argument values for this bean. diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java index 879f52aa3ed..ac19224bafb 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java @@ -59,6 +59,28 @@ abstract class AutowireUtils { }); } + /** + * Sort the given factory methods, preferring public methods and "greedy" ones + * with a maximum of arguments. The result will contain public methods first, + * with decreasing number of arguments, then non-public methods, again with + * decreasing number of arguments. + * @param factoryMethods the factory method array to sort + */ + public static void sortFactoryMethods(Method[] factoryMethods) { + Arrays.sort(factoryMethods, new Comparator() { + public int compare(Method fm1, Method fm2) { + boolean p1 = Modifier.isPublic(fm1.getModifiers()); + boolean p2 = Modifier.isPublic(fm2.getModifiers()); + if (p1 != p2) { + return (p1 ? -1 : 1); + } + int c1pl = fm1.getParameterTypes().length; + int c2pl = fm2.getParameterTypes().length; + return (new Integer(c1pl)).compareTo(c2pl) * -1; + } + }); + } + /** * Determine whether the given bean property is excluded from dependency checks. *

This implementation excludes properties defined by CGLIB. diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 303ff0fcb05..36ca8e2dfce 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -20,6 +20,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -45,6 +46,7 @@ import org.springframework.core.MethodParameter; import org.springframework.util.MethodInvoker; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; +import org.springframework.util.ClassUtils; /** * Helper class for resolving constructors and factory methods. @@ -284,6 +286,10 @@ class ConstructorResolver { } else { // It's a static factory method on the bean class. + if (!mbd.hasBeanClass()) { + throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, + "bean definition declares neither a bean class nor a factory-bean reference"); + } factoryBean = null; factoryClass = mbd.getBeanClass(); isStatic = true; @@ -309,7 +315,18 @@ class ConstructorResolver { if (factoryMethodToUse == null) { // Need to determine the factory method... // Try all methods with this name to see if they match the given arguments. - Method[] candidates = ReflectionUtils.getAllDeclaredMethods(factoryClass); + factoryClass = ClassUtils.getUserClass(factoryClass); + Method[] rawCandidates = ReflectionUtils.getAllDeclaredMethods(factoryClass); + List candidateSet = new ArrayList(); + for (Method candidate : rawCandidates) { + if (Modifier.isStatic(candidate.getModifiers()) == isStatic && + candidate.getName().equals(mbd.getFactoryMethodName())) { + candidateSet.add(candidate); + } + } + Method[] candidates = candidateSet.toArray(new Method[candidateSet.size()]); + AutowireUtils.sortFactoryMethods(candidates); + boolean autowiring = (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); int minTypeDiffWeight = Integer.MAX_VALUE; ConstructorArgumentValues resolvedValues = null; @@ -332,10 +349,7 @@ class ConstructorResolver { Method candidate = candidates[i]; Class[] paramTypes = candidate.getParameterTypes(); - if (Modifier.isStatic(candidate.getModifiers()) == isStatic && - candidate.getName().equals(mbd.getFactoryMethodName()) && - paramTypes.length >= minNrOfArgs) { - + if (paramTypes.length >= minNrOfArgs) { ArgumentsHolder args; if (resolvedValues != null) { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 2155342a997..6dcd240e64f 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -17,7 +17,9 @@ package org.springframework.beans.factory.support; import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; @@ -47,7 +49,9 @@ import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -428,6 +432,32 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto */ protected boolean isAutowireCandidate(String beanName, RootBeanDefinition mbd, DependencyDescriptor descriptor) { resolveBeanClass(mbd, beanName); + // TODO: the following is duplicating the factory method resolution algorithm in + // ConstructorResolver quite a bit... + if (mbd.getFactoryMethodName() != null && mbd.factoryMethodForIntrospection == null) { + Class factoryClass; + if (mbd.getFactoryBeanName() != null) { + factoryClass = getType(mbd.getFactoryBeanName()); + } + else { + factoryClass = mbd.getBeanClass(); + } + factoryClass = ClassUtils.getUserClass(factoryClass); + Method[] candidates = ReflectionUtils.getAllDeclaredMethods(factoryClass); + Method uniqueCandidate = null; + for (Method candidate : candidates) { + if (candidate.getName().equals(mbd.getFactoryMethodName())) { + if (uniqueCandidate == null) { + uniqueCandidate = candidate; + } + else if (!Arrays.equals(uniqueCandidate.getParameterTypes(), candidate.getParameterTypes())) { + uniqueCandidate = null; + break; + } + } + } + mbd.factoryMethodForIntrospection = uniqueCandidate; + } return getAutowireCandidateResolver().isAutowireCandidate( new BeanDefinitionHolder(mbd, beanName, getAliases(beanName)), descriptor); } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index cde55ea74d4..a3a146ae1df 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -17,6 +17,7 @@ package org.springframework.beans.factory.support; import java.lang.reflect.Member; +import java.lang.reflect.Method; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -51,6 +52,8 @@ public class RootBeanDefinition extends AbstractBeanDefinition { private final Set externallyManagedDestroyMethods = Collections.synchronizedSet(new HashSet()); + volatile Method factoryMethodForIntrospection; + /** Package-visible field for caching the resolved constructor or factory method */ volatile Object resolvedConstructorOrFactoryMethod; @@ -218,6 +221,10 @@ public class RootBeanDefinition extends AbstractBeanDefinition { } } + public Method getFactoryMethodForIntrospection() { + return this.factoryMethodForIntrospection; + } + public void registerExternallyManagedConfigMember(Member configMember) { this.externallyManagedConfigMembers.add(configMember); diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AbstractConfigurationClassProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AbstractConfigurationClassProcessor.java deleted file mode 100644 index 35f800935ab..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AbstractConfigurationClassProcessor.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.parsing.FailFastProblemReporter; -import org.springframework.beans.factory.parsing.ProblemReporter; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; - - -/** - * Abstract superclass for processing {@link Configuration}-annotated classes and registering - * bean definitions based on {@link Bean}-annotated methods within those classes. - * - *

Provides template method {@link #processConfigBeanDefinitions()} that orchestrates calling each - * of several abstract methods to be overriden by concrete implementations that allow for - * customizing how {@link Configuration} classes are found ({@link #getConfigurationBeanDefinitions}), - * customizing the creation of a {@link ConfigurationClassParser} ({@link #createConfigurationParser}), - * and customizing {@link ConfigurationModel} validation logic ({@link #validateModel}). - * - *

This class was expressly designed with tooling in mind. Spring IDE will maintain it's - * own implementation of this class but still take advantage of the generic parsing algorithm - * defined here by {@link #processConfigBeanDefinitions()}. - * - * @author Chris Beams - * @since 3.0 - * @see Configuration - * @see ConfigurationClassPostProcessor - */ -public abstract class AbstractConfigurationClassProcessor { - - /** - * Used to register any problems detected with {@link Configuration} or {@link Bean} - * declarations. For instance, a Bean method marked as {@literal final} is illegal - * and would be reported as a problem. Defaults to {@link FailFastProblemReporter}, - * but is overridable with {@link #setProblemReporter} - */ - private ProblemReporter problemReporter = new FailFastProblemReporter(); - - /** - * Populate and return a registry containing all {@link Configuration} bean definitions - * to be processed. - * - * @param includeAbstractBeanDefs whether abstract Configuration bean definitions should - * be included in the resulting BeanDefinitionRegistry. Usually false, but called as true - * during the enhancement phase. - * @see #processConfigBeanDefinitions() - */ - protected abstract BeanDefinitionRegistry getConfigurationBeanDefinitions(boolean includeAbstractBeanDefs); - - /** - * Create and return a new {@link ConfigurationClassParser}, allowing for customization of - * type (ASM/JDT/Reflection) as well as providing specialized ClassLoader during - * construction. - * @see #processConfigBeanDefinitions() - */ - protected abstract ConfigurationClassParser createConfigurationParser(); - - /** - * Override the default {@link ProblemReporter}. - * @param problemReporter custom problem reporter - */ - protected final void setProblemReporter(ProblemReporter problemReporter) { - this.problemReporter = problemReporter; - } - - /** - * Get the currently registered {@link ProblemReporter}. - */ - protected final ProblemReporter getProblemReporter() { - return problemReporter; - } - - /** - * Build and validate a {@link ConfigurationModel} based on the registry of - * {@link Configuration} classes provided by {@link #getConfigurationBeanDefinitions}, - * then, based on the content of that model, create and register bean definitions - * against a new {@link BeanDefinitionRegistry}, then return the registry. - * - * @return registry containing one bean definition per {@link Bean} method declared - * within the Configuration classes - */ - protected final BeanDefinitionRegistry processConfigBeanDefinitions() { - BeanDefinitionRegistry configBeanDefs = getConfigurationBeanDefinitions(false); - - // return an empty registry immediately if no @Configuration classes were found - if(configBeanDefs.getBeanDefinitionCount() == 0) - return configBeanDefs; - - // populate a new ConfigurationModel by parsing each @Configuration classes - ConfigurationClassParser parser = createConfigurationParser(); - - for(String beanName : configBeanDefs.getBeanDefinitionNames()) { - BeanDefinition beanDef = configBeanDefs.getBeanDefinition(beanName); - String className = beanDef.getBeanClassName(); - - parser.parse(className, beanName); - } - - ConfigurationModel configModel = parser.getConfigurationModel(); - - configModel.validate(problemReporter); - - // read the model and create bean definitions based on its content - return new ConfigurationModelBeanDefinitionReader().loadBeanDefinitions(configModel); - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AddAnnotationAdapter.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AddAnnotationAdapter.java deleted file mode 100644 index 9d9549cc2d9..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AddAnnotationAdapter.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import net.sf.cglib.asm.Constants; - -import org.springframework.asm.AnnotationVisitor; -import org.springframework.asm.ClassAdapter; -import org.springframework.asm.ClassVisitor; -import org.springframework.asm.FieldVisitor; -import org.springframework.asm.MethodVisitor; - - -/** - * Transforms a class by adding bytecode for a class-level annotation. Checks to ensure that - * the desired annotation is not already present before adding. Used by - * {@link ConfigurationClassEnhancer} to dynamically add an {@link org.aspectj.lang.Aspect} - * annotation to an enhanced Configuration subclass. - * - *

This class was originally adapted from examples the ASM 3.0 documentation. - * - * @author Chris Beams - */ -class AddAnnotationAdapter extends ClassAdapter { - private String annotationDesc; - private boolean isAnnotationPresent; - - /** - * Creates a new AddAnnotationAdapter instance. - * - * @param cv the ClassVisitor delegate - * @param annotationDesc name of the annotation to be added (in type descriptor format) - */ - public AddAnnotationAdapter(ClassVisitor cv, String annotationDesc) { - super(cv); - this.annotationDesc = annotationDesc; - } - - /** - * Ensures that the version of the resulting class is Java 5 or better. - */ - @Override - public void visit(int version, int access, String name, String signature, - String superName, String[] interfaces) { - int v = (version & 0xFF) < Constants.V1_5 ? Constants.V1_5 : version; - cv.visit(v, access, name, signature, superName, interfaces); - } - - /** - * Checks to ensure that the desired annotation is not already present. - */ - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - if (visible && desc.equals(annotationDesc)) { - isAnnotationPresent = true; - } - return cv.visitAnnotation(desc, visible); - } - - @Override - public void visitInnerClass(String name, String outerName, String innerName, int access) { - addAnnotation(); - cv.visitInnerClass(name, outerName, innerName, access); - } - - @Override - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - addAnnotation(); - return cv.visitField(access, name, desc, signature, value); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - addAnnotation(); - return cv.visitMethod(access, name, desc, signature, exceptions); - } - - /** - * Kicks off the process of actually adding the desired annotation. - * - * @see #addAnnotation() - */ - @Override - public void visitEnd() { - addAnnotation(); - cv.visitEnd(); - } - - /** - * Actually adds the desired annotation. - */ - private void addAnnotation() { - if (!isAnnotationPresent) { - AnnotationVisitor av = cv.visitAnnotation(annotationDesc, true); - if (av != null) { - av.visitEnd(); - } - isAnnotationPresent = true; - } - } -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationAdapter.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationAdapter.java deleted file mode 100644 index 27522b580da..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationAdapter.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import org.springframework.asm.AnnotationVisitor; - - -/** - * An empty {@link AnnotationVisitor} that delegates to another AnnotationVisitor. This - * class can be used as a super class to quickly implement useful annotation adapter - * classes, just by overriding the necessary methods. Note that for some reason, ASM - * doesn't provide this class (it does provide MethodAdapter and ClassAdapter), thus - * we're following the general pattern and adding our own here. - * - * @author Chris Beams - */ -class AnnotationAdapter implements AnnotationVisitor { - - private AnnotationVisitor delegate; - - /** - * Creates a new AnnotationAdapter instance that will delegate all its calls to - * delegate. - * - * @param delegate In most cases, the delegate will simply be - * {@link AsmUtils#ASM_EMPTY_VISITOR} - */ - public AnnotationAdapter(AnnotationVisitor delegate) { - this.delegate = delegate; - } - - public void visit(String arg0, Object arg1) { - delegate.visit(arg0, arg1); - } - - public AnnotationVisitor visitAnnotation(String arg0, String arg1) { - return delegate.visitAnnotation(arg0, arg1); - } - - public AnnotationVisitor visitArray(String arg0) { - return delegate.visitArray(arg0); - } - - public void visitEnum(String arg0, String arg1, String arg2) { - delegate.visitEnum(arg0, arg1, arg2); - } - - public void visitEnd() { - delegate.visitEnd(); - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java index 73795dddb34..78ebc9c1373 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java @@ -46,16 +46,16 @@ import org.springframework.util.ClassUtils; public class AnnotationConfigUtils { /** - * The bean name of the internally managed Autowired annotation processor. + * The bean name of the internally managed Configuration annotation processor. */ - public static final String AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME = - "org.springframework.context.annotation.internalAutowiredAnnotationProcessor"; + public static final String CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME = + "org.springframework.context.annotation.internalConfigurationAnnotationProcessor"; /** - * The bean name of the internally managed Configuration annotation processor. + * The bean name of the internally managed Autowired annotation processor. */ - public static final String CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME = - "org.springframework.context.annotation.configurationAnnotationProcessor"; + public static final String AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME = + "org.springframework.context.annotation.internalAutowiredAnnotationProcessor"; /** * The bean name of the internally managed Required annotation processor. @@ -109,18 +109,18 @@ public class AnnotationConfigUtils { Set beanDefs = new LinkedHashSet(4); - if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { - RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); - def.setSource(source); - beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); - } - if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } + if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { + RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); + def.setSource(source); + beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); + } + if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class); def.setSource(source); diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java index 45104c5ed7f..25f589c4990 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,13 +33,13 @@ import org.springframework.util.Assert; * @author Mark Fisher * @author Juergen Hoeller * @since 2.5 - * @see Scope + * @see org.springframework.context.annotation.Scope */ public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver { private Class scopeAnnotationType = Scope.class; - private ScopedProxyMode scopedProxyMode; + private final ScopedProxyMode defaultProxyMode; /** @@ -48,16 +48,16 @@ public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver { * @see ScopedProxyMode#NO */ public AnnotationScopeMetadataResolver() { - this(ScopedProxyMode.NO); + this.defaultProxyMode = ScopedProxyMode.NO; } /** * Create a new instance of the AnnotationScopeMetadataResolver class. - * @param scopedProxyMode the desired scoped-proxy mode + * @param defaultProxyMode the desired scoped-proxy mode */ - public AnnotationScopeMetadataResolver(ScopedProxyMode scopedProxyMode) { - Assert.notNull(scopedProxyMode, "'scopedProxyMode' must not be null"); - this.scopedProxyMode = scopedProxyMode; + public AnnotationScopeMetadataResolver(ScopedProxyMode defaultProxyMode) { + Assert.notNull(defaultProxyMode, "'defaultProxyMode' must not be null"); + this.defaultProxyMode = defaultProxyMode; } @@ -78,11 +78,16 @@ public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver { AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; Map attributes = annDef.getMetadata().getAnnotationAttributes(this.scopeAnnotationType.getName()); + ScopedProxyMode annMode = null; if (attributes != null) { metadata.setScopeName((String) attributes.get("value")); + annMode = (ScopedProxyMode) attributes.get("proxyMode"); } - if (!metadata.getScopeName().equals(BeanDefinition.SCOPE_SINGLETON)) { - metadata.setScopedProxyMode(this.scopedProxyMode); + if (annMode != null && annMode != ScopedProxyMode.DEFAULT) { + metadata.setScopedProxyMode(annMode); + } + else if (!metadata.getScopeName().equals(BeanDefinition.SCOPE_SINGLETON)) { + metadata.setScopedProxyMode(this.defaultProxyMode); } } return metadata; diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AsmUtils.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AsmUtils.java deleted file mode 100644 index 4abe3c49836..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AsmUtils.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import static java.lang.String.*; -import static org.springframework.util.ClassUtils.*; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.lang.annotation.Annotation; -import java.lang.reflect.Proxy; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.asm.ClassReader; -import org.springframework.asm.commons.EmptyVisitor; -import org.springframework.util.ClassUtils; - - -/** - * Various utility methods commonly used when interacting with ASM, classloading - * and creating {@link MutableAnnotation} instances. - * - * @author Chris Beams - */ -class AsmUtils { - - public static final EmptyVisitor ASM_EMPTY_VISITOR = new EmptyVisitor(); - - private static final Log log = LogFactory.getLog(AsmUtils.class); - - /** - * Convert a type descriptor to a classname suitable for classloading with - * Class.forName(). - * - * @param typeDescriptor see ASM guide section 2.1.3 - */ - public static String convertAsmTypeDescriptorToClassName(String typeDescriptor) { - final String internalName; // See ASM guide section 2.1.2 - - if ("V".equals(typeDescriptor)) - return Void.class.getName(); - if ("I".equals(typeDescriptor)) - return Integer.class.getName(); - if ("Z".equals(typeDescriptor)) - return Boolean.class.getName(); - - // strip the leading array/object/primitive identifier - if (typeDescriptor.startsWith("[[")) - internalName = typeDescriptor.substring(3); - else if (typeDescriptor.startsWith("[")) - internalName = typeDescriptor.substring(2); - else - internalName = typeDescriptor.substring(1); - - // convert slashes to dots - String className = internalName.replace('/', '.'); - - // and strip trailing semicolon (if present) - if (className.endsWith(";")) - className = className.substring(0, internalName.length() - 1); - - return className; - } - - /** - * @param methodDescriptor see ASM guide section 2.1.4 - */ - public static String getReturnTypeFromAsmMethodDescriptor(String methodDescriptor) { - String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')') + 1); - return convertAsmTypeDescriptorToClassName(returnTypeDescriptor); - } - - /** - * Creates a new ASM {@link ClassReader} for pathToClass. Appends '.class' to - * pathToClass before attempting to load. - * - * @throws RuntimeException if pathToClass+.class cannot be found on the - * classpath - * @throws RuntimeException if an IOException occurs when creating the new ClassReader - */ - public static ClassReader newAsmClassReader(String pathToClass, ClassLoader classLoader) { - InputStream is = getClassAsStream(pathToClass, classLoader); - return newAsmClassReader(is); - } - - /** - * Convenience method that creates and returns a new ASM {@link ClassReader} for the - * given InputStream is, closing the InputStream after creating the - * ClassReader and rethrowing any IOException thrown during ClassReader instantiation as - * an unchecked exception. Logs and ignores any IOException thrown when closing the - * InputStream. - * - * @param is InputStream that will be provided to the new ClassReader instance. - */ - public static ClassReader newAsmClassReader(InputStream is) { - try { - return new ClassReader(is); - } catch (IOException ex) { - throw new RuntimeException("An unexpected exception occurred while creating ASM ClassReader: " + ex); - } finally { - try { - is.close(); - } catch (IOException ex) { - log.error("Ignoring exception thrown while closing InputStream", ex); - } - } - } - - /** - * Uses the default ClassLoader to load pathToClass. Appends '.class' to - * pathToClass before attempting to load. - * - * @param pathToClass resource path to class, not including .class suffix. e.g.: - * com/acme/MyClass - * - * @return inputStream for pathToClass - * - * @throws RuntimeException if pathToClass does not exist - */ - public static InputStream getClassAsStream(String pathToClass, ClassLoader classLoader) { - String classFileName = pathToClass + ClassUtils.CLASS_FILE_SUFFIX; - - InputStream is = classLoader.getResourceAsStream(classFileName); - - if (is == null) - throw new RuntimeException( - new FileNotFoundException("Class file [" + classFileName + "] not found")); - - return is; - } - - /** - * Loads the specified class using the default class loader, rethrowing any - * {@link ClassNotFoundException} as an unchecked exception. - * - * @param type of class to be returned - * @param fqClassName fully-qualified class name - * - * @return newly loaded class instance - * - * @throws IllegalArgumentException if configClassName cannot be loaded. - * - * @see #loadClass(String) - * @see #loadToolingSafeClass(String) - * @see ClassUtils#getDefaultClassLoader() - */ - @SuppressWarnings("unchecked") - public static Class loadRequiredClass(String fqClassName) { - try { - return (Class) getDefaultClassLoader().loadClass(fqClassName); - } catch (ClassNotFoundException ex) { - throw new IllegalArgumentException(format( - "Class [%s] could not be loaded, check your CLASSPATH.", fqClassName), ex); - } - } - - /** - * Loads the specified class using the default class loader, gracefully handling any - * {@link ClassNotFoundException} that may be thrown by issuing a WARN level logging - * statement and return null. This functionality is specifically implemented to - * accomodate tooling (Spring IDE) concerns, where user-defined types will not be - * available to the tooling. - *

- * Because {@link ClassNotFoundException} is compensated for by returning null, callers - * must take care to handle the null case appropriately. - *

- * In cases where the WARN logging statement is not desired, use the - * {@link #loadClass(String)} method, which returns null but issues no logging - * statements. - *

- * This method should only ever return null in the case of a user-defined type be - * processed at tooling time. Therefore, tooling may not be able to represent any custom - * annotation semantics, but JavaConfig itself will not have any problem loading and - * respecting them at actual runtime. - * - * @param type of class to be returned - * @param fqClassName fully-qualified class name - * - * @return newly loaded class, null if class could not be found. - * - * @see #loadClass(String) - * @see #loadRequiredClass(String) - * @see ClassUtils#getDefaultClassLoader() - */ - @SuppressWarnings("unchecked") - public static Class loadToolingSafeClass(String fqClassName, ClassLoader classLoader) { - try { - return (Class) classLoader.loadClass(fqClassName); - } catch (ClassNotFoundException ex) { - log.warn(format("Unable to load class [%s], likely due to tooling-specific restrictions." - + "Attempting to continue, but unexpected errors may occur", fqClassName), ex); - return null; - } - } - - /** - * Creates a {@link MutableAnnotation} for {@code annoType}. JDK dynamic proxies are - * used, and the returned proxy implements both {@link MutableAnnotation} and annotation - * type {@code A} - * - * @param annotation type that must be supplied and returned - * @param annoType type of annotation to create - */ - public static A createMutableAnnotation(Class annoType, ClassLoader classLoader) { - MutableAnnotationInvocationHandler handler = new MutableAnnotationInvocationHandler(annoType); - Class[] interfaces = new Class[] { annoType, MutableAnnotation.class }; - - @SuppressWarnings("unchecked") - A mutableAnno = (A) Proxy.newProxyInstance(classLoader, interfaces, handler); - return mutableAnno; - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java index a4e77a7d4bf..57d10141cd9 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Bean.java @@ -22,6 +22,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.beans.factory.annotation.Autowire; /** * Indicates that a method produces a bean to be managed by the Spring container. The @@ -60,7 +61,7 @@ import java.lang.annotation.Target; * @see Configuration * @see Lazy * @see Primary - * @see Scope + * @see org.springframework.context.annotation.Scope */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @@ -74,6 +75,11 @@ public @interface Bean { */ String[] name() default {}; + /** + * Are dependencies to be injected via autowiring? + */ + Autowire autowire() default Autowire.NO; + /** * The optional name of a method to call on the bean instance during initialization. * Not commonly used, given that the method may be called programmatically directly @@ -82,10 +88,9 @@ public @interface Bean { String initMethod() default ""; /** - * The optional name of a method to call on the bean instance during upon closing - * the application context, for example a {@literal close()} - * method on a {@literal DataSource}. The method must have no arguments, but may - * throw any exception. + * The optional name of a method to call on the bean instance during upon closing the + * application context, for example a {@literal close()} method on a {@literal DataSource}. + * The method must have no arguments, but may throw any exception. *

Note: Only invoked on beans whose lifecycle is under the full control of the * factory which is always the case for singletons, but not guaranteed * for any other scope. @@ -93,14 +98,4 @@ public @interface Bean { */ String destroyMethod() default ""; - /** - * Beans on which the current bean depends. Any beans specified are guaranteed to be - * created by the container before this bean. Used infrequently in cases where a bean - * does not explicitly depend on another through properties or constructor arguments, - * but rather depends on the side effects of another bean's initialization. - *

Note: This attribute will not be inherited by child bean definitions, - * hence it needs to be specified per concrete bean definition. - */ - String[] dependsOn() default {}; - } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanMethodInterceptor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanMethodInterceptor.java deleted file mode 100644 index 0e819df87ff..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanMethodInterceptor.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import static java.lang.String.*; - -import java.lang.reflect.Method; - -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.core.annotation.AnnotationUtils; - - -/** - * Intercepts the invocation of any {@link Bean}-annotated methods in order to ensure proper - * handling of bean semantics such as scoping and AOP proxying. - * - * @author Chris Beams - * @see Bean - * @see ConfigurationClassEnhancer - */ -class BeanMethodInterceptor implements MethodInterceptor { - - private static final Log log = LogFactory.getLog(BeanMethodInterceptor.class); - - private final DefaultListableBeanFactory beanFactory; - - public BeanMethodInterceptor(DefaultListableBeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - /** - * Enhances a {@link Bean @Bean} method to check the supplied BeanFactory for the - * existence of this bean object. - */ - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - - // by default the bean name is the name of the @Bean-annotated method - String beanName = method.getName(); - - // check to see if the user has explicitly set the bean name - Bean bean = method.getAnnotation(Bean.class); - if(bean != null && bean.name().length > 0) - beanName = bean.name()[0]; - - // determine whether this bean is a scoped-proxy - Scope scope = AnnotationUtils.findAnnotation(method, Scope.class); - boolean isScopedProxy = (scope != null && scope.proxyMode() != ScopedProxyMode.NO); - String scopedBeanName = ConfigurationModelBeanDefinitionReader.resolveHiddenScopedProxyBeanName(beanName); - if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName)) - beanName = scopedBeanName; - - // to handle the case of an inter-bean method reference, we must explicitly check the - // container for already cached instances - if (factoryContainsBean(beanName)) { - // we have an already existing cached instance of this bean -> retrieve it - Object cachedBean = beanFactory.getBean(beanName); - if (log.isInfoEnabled()) - log.info(format("Returning cached singleton object [%s] for @Bean method %s.%s", - cachedBean, method.getDeclaringClass().getSimpleName(), beanName)); - - return cachedBean; - } - - // actually create and return the bean - return proxy.invokeSuper(obj, args); - } - - /** - * Check the beanFactory to see whether the bean named beanName already - * exists. Accounts for the fact that the requested bean may be "in creation", i.e.: - * we're in the middle of servicing the initial request for this bean. From JavaConfig's - * perspective, this means that the bean does not actually yet exist, and that it is now - * our job to create it for the first time by executing the logic in the corresponding - * Bean method. - *

- * Said another way, this check repurposes - * {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} to determine whether - * the container is calling this method or the user is calling this method. - * - * @param beanName name of bean to check for - * - * @return true if beanName already exists in beanFactory - */ - private boolean factoryContainsBean(String beanName) { - return beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName); - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index 1e65f464a7a..77f63b2523a 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -197,54 +197,39 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo */ protected Set doScan(String... basePackages) { Set beanDefinitions = new LinkedHashSet(); - for (int i = 0; i < basePackages.length; i++) { - Set candidates = findCandidateComponents(basePackages[i]); + for (String basePackage : basePackages) { + Set candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } - ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); + if (candidate instanceof AnnotatedBeanDefinition) { + AnnotatedBeanDefinition abd = (AnnotatedBeanDefinition) candidate; + if (abd.getMetadata().hasAnnotation(Primary.class.getName())) { + abd.setPrimary(true); + } + if (abd.getMetadata().hasAnnotation(Lazy.class.getName())) { + Boolean value = (Boolean) abd.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value"); + abd.setLazyInit(value); + } + if (abd.getMetadata().hasAnnotation(DependsOn.class.getName())) { + String[] value = (String[]) abd.getMetadata().getAnnotationAttributes(DependsOn.class.getName()).get("value"); + abd.setDependsOn(value); + } + } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); + ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); definitionHolder = applyScope(definitionHolder, scopeMetadata); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } - - - postProcessComponentBeanDefinitions(beanDefinitions); return beanDefinitions; } - protected void postProcessComponentBeanDefinitions(Set beanDefinitions) { - //TODO refactor increment index count as part of naming strategy. - Set factoryBeanDefinitions = new LinkedHashSet(); - for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) { - Set candidates = findCandidateFactoryMethods(beanDefinitionHolder); - for (BeanDefinition candidate : candidates ) { - - - BeanDefinitionHolder definitionHolder; - if (candidate.getBeanClassName().equals("org.springframework.aop.scope.ScopedProxyFactoryBean")){ - String scopedFactoryBeanName = "scopedTarget." + candidate.getPropertyValues().getPropertyValue("targetBeanName").getValue(); - definitionHolder = new BeanDefinitionHolder(candidate, scopedFactoryBeanName); - } else { - String configurationComponentBeanName = beanDefinitionHolder.getBeanName(); - String factoryMethodName = candidate.getFactoryMethodName(); - String beanName = createFactoryBeanName(configurationComponentBeanName, factoryMethodName); - definitionHolder = new BeanDefinitionHolder(candidate, beanName); - } - - factoryBeanDefinitions.add(definitionHolder); - registerBeanDefinition(definitionHolder, this.registry); - } - } - beanDefinitions.addAll(factoryBeanDefinitions); - } - /** * Apply further settings to the given bean definition, * beyond the contents retrieved from scanning the component class. @@ -325,8 +310,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo String scope = scopeMetadata.getScopeName(); ScopedProxyMode scopedProxyMode = scopeMetadata.getScopedProxyMode(); definitionHolder.getBeanDefinition().setScope(scope); - if (BeanDefinition.SCOPE_SINGLETON.equals(scope) || BeanDefinition.SCOPE_PROTOTYPE.equals(scope) || - scopedProxyMode.equals(ScopedProxyMode.NO)) { + if (scopedProxyMode.equals(ScopedProxyMode.NO)) { return definitionHolder; } boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS); diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 93927269cd1..4578bab663d 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,33 +17,23 @@ package org.springframework.context.annotation; import java.io.IOException; - import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.aop.scope.ScopedProxyFactoryBean; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.AutowireCandidateQualifier; -import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; -import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -75,13 +65,8 @@ import org.springframework.util.SystemPropertyUtils; */ public class ClassPathScanningCandidateComponentProvider implements ResourceLoaderAware { - protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; + private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; - protected static final String QUALIFIER_CLASS_NAME = "org.springframework.beans.factory.annotation.Qualifier"; - - protected static final String SCOPE_CLASS_NAME = "org.springframework.context.annotation.Scope"; - - protected static final String SCOPEDPROXY_CLASS_NAME = "org.springframework.beans.factory.annotation.ScopedProxy"; protected final Log logger = LogFactory.getLog(getClass()); @@ -95,8 +80,6 @@ public class ClassPathScanningCandidateComponentProvider implements ResourceLoad private final List excludeFilters = new LinkedList(); - private int factoryBeanCount = 0; - /** * Create a ClassPathScanningCandidateComponentProvider. @@ -199,33 +182,38 @@ public class ClassPathScanningCandidateComponentProvider implements ResourceLoad Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); - for (int i = 0; i < resources.length; i++) { - Resource resource = resources[i]; + for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } if (resource.isReadable()) { - MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); - if (isCandidateComponent(metadataReader)) { - ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); - sbd.setResource(resource); - sbd.setSource(resource); - if (isCandidateComponent(sbd)) { - if (debugEnabled) { - logger.debug("Identified candidate component class: " + resource); + try { + MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); + if (isCandidateComponent(metadataReader)) { + ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); + sbd.setResource(resource); + sbd.setSource(resource); + if (isCandidateComponent(sbd)) { + if (debugEnabled) { + logger.debug("Identified candidate component class: " + resource); + } + candidates.add(sbd); + } + else { + if (debugEnabled) { + logger.debug("Ignored because not a concrete top-level class: " + resource); + } } - candidates.add(sbd); } else { - if (debugEnabled) { - logger.debug("Ignored because not a concrete top-level class: " + resource); + if (traceEnabled) { + logger.trace("Ignored because not matching any filter: " + resource); } } } - else { - if (traceEnabled) { - logger.trace("Ignored because not matching any filter: " + resource); - } + catch (Throwable ex) { + throw new BeanDefinitionStoreException( + "Failed to read candidate component class: " + resource, ex); } } else { @@ -240,116 +228,6 @@ public class ClassPathScanningCandidateComponentProvider implements ResourceLoad } return candidates; } - - public Set findCandidateFactoryMethods(final BeanDefinitionHolder beanDefinitionHolder) { - Set candidates = new LinkedHashSet(); - AbstractBeanDefinition containingBeanDef = (AbstractBeanDefinition)beanDefinitionHolder.getBeanDefinition(); - Resource resource = containingBeanDef.getResource(); - boolean debugEnabled = logger.isDebugEnabled(); - boolean traceEnabled = logger.isTraceEnabled(); - - try { - if (resource.isReadable()) { - MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); - Set factoryMethodMetadataSet = metadataReader.getAnnotationMetadata().getAnnotatedMethods("org.springframework.beans.factory.annotation.FactoryMethod"); - for (MethodMetadata methodMetadata : factoryMethodMetadataSet) { - if (isCandidateFactoryMethod(methodMetadata)) { - ScannedGenericBeanDefinition factoryBeanDef = new ScannedGenericBeanDefinition(metadataReader); - - if (!methodMetadata.isStatic()) { - factoryBeanDef.setFactoryBeanName(beanDefinitionHolder.getBeanName()); - } - factoryBeanDef.setFactoryMethodName(methodMetadata.getMethodName()); - - addQualifierToFactoryMethodBeanDefinition(methodMetadata, factoryBeanDef); - addScopeToFactoryMethodBeanDefinition(containingBeanDef, methodMetadata, factoryBeanDef); - - factoryBeanDef.setResource(containingBeanDef.getResource()); - factoryBeanDef.setSource(containingBeanDef.getSource()); - - if (debugEnabled) { - logger.debug("Identified candidate factory method in class: " + resource); - } - candidates.add(factoryBeanDef); - - RootBeanDefinition scopedFactoryBeanDef = null; - if (methodMetadata.hasAnnotation(SCOPEDPROXY_CLASS_NAME)) { - //TODO validate that @ScopedProxy isn't applied to singleton/prototype beans. - Map attributes = methodMetadata.getAnnotationAttributes(SCOPEDPROXY_CLASS_NAME); - scopedFactoryBeanDef = new RootBeanDefinition(ScopedProxyFactoryBean.class); - String t= scopedFactoryBeanDef.getBeanClassName(); - String targetBeanName = createFactoryBeanName(beanDefinitionHolder.getBeanName(), factoryBeanDef.getFactoryMethodName()); - scopedFactoryBeanDef.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName); - - //TODO handle cglib options - // scopedFactoryBeanDef.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE); - scopedFactoryBeanDef.setAutowireCandidate(false); - scopedFactoryBeanDef.setResource(containingBeanDef.getResource()); - scopedFactoryBeanDef.setSource(containingBeanDef.getSource()); - - candidates.add(scopedFactoryBeanDef); - - } - - - - } - else { - if (traceEnabled) { - logger.trace("Ignored because not matching any filter: " + resource); - } - } - } - - } - } catch (IOException ex) { - throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); - } - - return candidates; - } - - - private void addScopeToFactoryMethodBeanDefinition( - AbstractBeanDefinition containingBeanDefinition, - MethodMetadata factoryMethodMetadata, - ScannedGenericBeanDefinition factoryBeanDefinition) { - if (factoryMethodMetadata.hasAnnotation(SCOPE_CLASS_NAME)) { - Map attributes = factoryMethodMetadata.getAnnotationAttributes(SCOPE_CLASS_NAME); - factoryBeanDefinition.setScope(attributes.get("value").toString()); - } else { - factoryBeanDefinition.setScope(containingBeanDefinition.getScope()); - } - } - - - protected void addQualifierToFactoryMethodBeanDefinition(MethodMetadata methodMetadata, - ScannedGenericBeanDefinition beanDef) { - //Add qualifiers to bean definition - if (methodMetadata.hasAnnotation(QUALIFIER_CLASS_NAME)) - { - Map attributes = methodMetadata.getAnnotationAttributes(QUALIFIER_CLASS_NAME); - beanDef.addQualifier(new AutowireCandidateQualifier(Qualifier.class, attributes.get("value"))); - } - - if (methodMetadata.hasMetaAnnotation(QUALIFIER_CLASS_NAME)) - { - //Need the attribute that has a qualifier meta-annotation. - Set annotationTypes = methodMetadata.getAnnotationTypesWithMetaAnnotation(QUALIFIER_CLASS_NAME); - if (annotationTypes.size() == 1) - { - String annotationType = annotationTypes.iterator().next(); - Map attributes = methodMetadata.getAnnotationAttributes(annotationType); - beanDef.addQualifier(new AutowireCandidateQualifier(annotationType, attributes.get("value"))); - } - } - } - - protected boolean isCandidateFactoryMethod(MethodMetadata methodMetadata) { - - //TODO decide if we can support generic wildcard return types, parameter-less method and put in appropriate checks - return true; - } /** @@ -395,11 +273,4 @@ public class ClassPathScanningCandidateComponentProvider implements ResourceLoad return (beanDefinition.getMetadata().isConcrete() && beanDefinition.getMetadata().isIndependent()); } - - protected String createFactoryBeanName(String configurationComponentBeanName, String factoryMethodName) { - //TODO consider adding hex string and passing in definition object. - String beanName = configurationComponentBeanName + "$" + factoryMethodName; - return beanName; - } - } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java index 21301fdd6c4..b550048b6ec 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.XmlReaderContext; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AspectJTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; @@ -75,8 +76,8 @@ public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser { public BeanDefinition parse(Element element, ParserContext parserContext) { - String[] basePackages = - StringUtils.commaDelimitedListToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE)); + String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE), + ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); // Actually scan for bean definitions and register them. ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java index e4609c494cf..47de69733d9 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java @@ -57,7 +57,7 @@ import org.springframework.stereotype.Component; * @see Value * @see ConfigurationClassPostProcessor; */ -@Target( { ElementType.TYPE }) +@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java index f00851114a6..520cbacc063 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java @@ -16,44 +16,99 @@ package org.springframework.context.annotation; -import static java.lang.String.*; - import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.util.HashSet; import java.util.Set; +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; +import org.springframework.core.io.ClassPathResource; import org.springframework.util.Assert; - +import org.springframework.util.ClassUtils; /** * Represents a user-defined {@link Configuration @Configuration} class. * Includes a set of {@link Bean} methods, including all such methods defined in the - * ancestry of the class, in a 'flattened-out' manner. Note that each {@link BeanMethod} + * ancestry of the class, in a 'flattened-out' manner. Note that each {@link ConfigurationClassMethod} * representation contains source information about where it was originally detected * (for the purpose of tooling with Spring IDE). - * + * * @author Chris Beams - * @see ConfigurationModel - * @see BeanMethod + * @author Juergen Hoeller + * @since 3.0 + * @see ConfigurationClassMethod * @see ConfigurationClassParser */ -final class ConfigurationClass extends ModelClass { +final class ConfigurationClass implements BeanMetadataElement { + + private String name; + + private transient Object source; private String beanName; + private int modifiers; - private HashSet annotations = new HashSet(); - private HashSet methods = new HashSet(); + + private Set annotations = new HashSet(); + + private Set methods = new HashSet(); + private ConfigurationClass declaringClass; + + /** + * Returns the fully-qualified name of this class. + */ + public String getName() { + return name; + } + + /** + * Sets the fully-qualified name of this class. + */ + public void setName(String className) { + this.name = className; + } + + /** + * Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'. + */ + public String getSimpleName() { + return name == null ? null : ClassUtils.getShortName(name); + } + + /** + * Returns a resource path-formatted representation of the .java file that declares this + * class + */ + public Object getSource() { + return source; + } + + /** + * Set the source location for this class. Must be a resource-path formatted string. + * @param source resource path to the .java file that declares this class. + */ + public void setSource(Object source) { + this.source = source; + } + + public Location getLocation() { + if (getName() == null) { + throw new IllegalStateException("'name' property is null. Call setName() before calling getLocation()"); + } + return new Location(new ClassPathResource(ClassUtils.convertClassNameToResourcePath(getName())), getSource()); + } + public String getBeanName() { - return beanName == null ? getName() : beanName; + return beanName; } - public void setBeanName(String id) { - this.beanName = id; + public void setBeanName(String beanName) { + this.beanName = beanName; } public int getModifiers() { @@ -76,10 +131,11 @@ final class ConfigurationClass extends ModelClass { */ @SuppressWarnings("unchecked") public A getAnnotation(Class annoType) { - for (Annotation annotation : annotations) - if(annotation.annotationType().equals(annoType)) + for (Annotation annotation : annotations) { + if (annotation.annotationType().equals(annoType)) { return (A) annotation; - + } + } return null; } @@ -90,19 +146,18 @@ final class ConfigurationClass extends ModelClass { */ public A getRequiredAnnotation(Class annoType) { A anno = getAnnotation(annoType); - - if(anno == null) + if (anno == null) { throw new IllegalStateException( - format("required annotation %s is not present on %s", annoType.getSimpleName(), this)); - + String.format("Required annotation %s is not present on %s", annoType.getSimpleName(), this)); + } return anno; } - public Set getBeanMethods() { + public Set getBeanMethods() { return methods; } - public ConfigurationClass addBeanMethod(BeanMethod method) { + public ConfigurationClass addMethod(ConfigurationClassMethod method) { method.setDeclaringClass(this); methods.add(method); return this; @@ -117,93 +172,24 @@ final class ConfigurationClass extends ModelClass { } public void validate(ProblemReporter problemReporter) { - // configuration classes must be annotated with @Configuration - if (getAnnotation(Configuration.class) == null) - problemReporter.error(new NonAnnotatedConfigurationProblem()); - // a configuration class may not be final (CGLIB limitation) - if (Modifier.isFinal(modifiers)) - problemReporter.error(new FinalConfigurationProblem()); - - for (BeanMethod method : methods) - method.validate(problemReporter); - } - - @Override - public String toString() { - return format("%s; modifiers=%d; methods=%s", super.toString(), modifiers, methods); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result - + ((annotations == null) ? 0 : annotations.hashCode()); - result = prime * result - + ((beanName == null) ? 0 : beanName.hashCode()); - result = prime * result - + ((declaringClass == null) ? 0 : declaringClass.hashCode()); - result = prime * result + ((methods == null) ? 0 : methods.hashCode()); - result = prime * result + modifiers; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!super.equals(obj)) - return false; - if (getClass() != obj.getClass()) - return false; - ConfigurationClass other = (ConfigurationClass) obj; - if (annotations == null) { - if (other.annotations != null) - return false; - } else if (!annotations.equals(other.annotations)) - return false; - if (beanName == null) { - if (other.beanName != null) - return false; - } else if (!beanName.equals(other.beanName)) - return false; - if (declaringClass == null) { - if (other.declaringClass != null) - return false; - } else if (!declaringClass.equals(other.declaringClass)) - return false; - if (methods == null) { - if (other.methods != null) - return false; - } else if (!methods.equals(other.methods)) - return false; - if (modifiers != other.modifiers) - return false; - return true; - } - - - /** Configuration classes must be annotated with {@link Configuration @Configuration}. */ - class NonAnnotatedConfigurationProblem extends Problem { - - NonAnnotatedConfigurationProblem() { - super(format("%s was specified as a @Configuration class but was not actually annotated " + - "with @Configuration. Annotate the class or do not attempt to process it.", - getSimpleName()), - ConfigurationClass.this.getLocation()); + if (getAnnotation(Configuration.class) != null) { + if (Modifier.isFinal(modifiers)) { + problemReporter.error(new FinalConfigurationProblem()); + } + for (ConfigurationClassMethod method : methods) { + method.validate(problemReporter); + } } - } /** Configuration classes must be non-final to accommodate CGLIB subclassing. */ - class FinalConfigurationProblem extends Problem { + private class FinalConfigurationProblem extends Problem { - FinalConfigurationProblem() { - super(format("@Configuration class [%s] may not be final. Remove the final modifier to continue.", - getSimpleName()), - ConfigurationClass.this.getLocation()); + public FinalConfigurationProblem() { + super(String.format("@Configuration class [%s] may not be final. Remove the final modifier to continue.", + getSimpleName()), ConfigurationClass.this.getLocation()); } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotation.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotation.java similarity index 63% rename from org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotation.java rename to org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotation.java index 698f701d33e..1dc085e2e6f 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotation.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotation.java @@ -16,24 +16,26 @@ package org.springframework.context.annotation; +import java.lang.annotation.Annotation; /** * Interface used when dynamically creating mutable instances of annotations associated * with {@link Configuration} class processing. This functionality is necessary given * that parsing of Configuration classes is done with ASM. Annotation metadata (including - * attributes) is parsed from the classfiles, and instances of those annotations are + * attributes) is parsed from the class files, and instances of those annotations are * then created using this interface and its associated utilities. The annotation - * instances are attached to the {@link ConfigurationModel} objects at runtime, namely - * {@link BeanMethod}. This approach is better than the alternative of creating fine-grained - * model representations of all annotations and attributes. It is better to simply attach - * annotation instances and read them as needed. - * + * instances are attached to the configuration model objects at runtime, namely + * {@link ConfigurationClassMethod}. This approach is better than the alternative of + * creating fine-grained model representations of all annotations and attributes. + * It is better to simply attach annotation instances and read them as needed. + * * @author Chris Beams - * @see MutableAnnotationVisitor - * @see MutableAnnotationInvocationHandler - * @see AsmUtils#createMutableAnnotation + * @author Juergen Hoeller + * @since 3.0 + * @see ConfigurationClassAnnotationVisitor + * @see ConfigurationClassReaderUtils#createMutableAnnotation */ -interface MutableAnnotation { +interface ConfigurationClassAnnotation extends Annotation { void setAttributeValue(String attribName, Object attribValue); diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotationVisitor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotationVisitor.java new file mode 100644 index 00000000000..df3787ccc8f --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassAnnotationVisitor.java @@ -0,0 +1,163 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Array; +import java.util.List; +import java.util.ArrayList; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.Type; +import org.springframework.asm.commons.EmptyVisitor; + +/** + * ASM {@link AnnotationVisitor} that populates a given {@link ConfigurationClassAnnotation} instance + * with its attributes. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.0 + * @see ConfigurationClassAnnotation + * @see ConfigurationClassReaderUtils#createMutableAnnotation + */ +class ConfigurationClassAnnotationVisitor implements AnnotationVisitor { + + protected final ConfigurationClassAnnotation mutableAnno; + + private final ClassLoader classLoader; + + + /** + * Creates a new {@link ConfigurationClassAnnotationVisitor} instance that will populate the the + * attributes of the given mutableAnno. Accepts {@link Annotation} instead of + * {@link ConfigurationClassAnnotation} to avoid the need for callers to typecast. + * @param mutableAnno {@link ConfigurationClassAnnotation} instance to visit and populate + * @see ConfigurationClassReaderUtils#createMutableAnnotation + */ + public ConfigurationClassAnnotationVisitor(ConfigurationClassAnnotation mutableAnno, ClassLoader classLoader) { + this.mutableAnno = mutableAnno; + this.classLoader = classLoader; + } + + public void visit(String attribName, Object attribValue) { + Class attribReturnType = mutableAnno.getAttributeType(attribName); + + if (attribReturnType.equals(Class.class)) { + // the attribute type is Class -> load it and set it. + String fqClassName = ((Type) attribValue).getClassName(); + Class classVal = ConfigurationClassReaderUtils.loadToolingSafeClass(fqClassName, classLoader); + if (classVal == null) { + return; + } + mutableAnno.setAttributeValue(attribName, classVal); + return; + } + + // otherwise, assume the value can be set literally + mutableAnno.setAttributeValue(attribName, attribValue); + } + + @SuppressWarnings("unchecked") + public void visitEnum(String attribName, String enumTypeDescriptor, String strEnumValue) { + String enumClassName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(enumTypeDescriptor); + Class enumClass = ConfigurationClassReaderUtils.loadToolingSafeClass(enumClassName, classLoader); + if (enumClass == null) { + return; + } + Enum enumValue = Enum.valueOf(enumClass, strEnumValue); + mutableAnno.setAttributeValue(attribName, enumValue); + } + + public AnnotationVisitor visitAnnotation(String attribName, String attribAnnoTypeDesc) { + String annoTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(attribAnnoTypeDesc); + Class annoType = ConfigurationClassReaderUtils.loadToolingSafeClass(annoTypeName, classLoader); + if (annoType == null) { + return new EmptyVisitor(); + } + ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoType, classLoader); + try { + Field attribute = mutableAnno.getClass().getField(attribName); + attribute.set(mutableAnno, anno); + } + catch (Exception ex) { + throw new IllegalStateException("Could not reflectively set annotation field", ex); + } + return new ConfigurationClassAnnotationVisitor(anno, classLoader); + } + + public AnnotationVisitor visitArray(final String attribName) { + return new MutableAnnotationArrayVisitor(mutableAnno, attribName, classLoader); + } + + public void visitEnd() { + } + + + /** + * ASM {@link AnnotationVisitor} that visits any annotation array values while populating + * a new {@link ConfigurationClassAnnotation} instance. + */ + private static class MutableAnnotationArrayVisitor implements AnnotationVisitor { + + private final List values = new ArrayList(); + + private final ConfigurationClassAnnotation mutableAnno; + + private final String attribName; + + private final ClassLoader classLoader; + + + public MutableAnnotationArrayVisitor(ConfigurationClassAnnotation mutableAnno, String attribName, ClassLoader classLoader) { + this.mutableAnno = mutableAnno; + this.attribName = attribName; + this.classLoader = classLoader; + } + + + public void visit(String na, Object value) { + values.add(value); + } + + public void visitEnum(String s, String s1, String s2) { + } + + public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) { + String annoTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc); + Class annoType = ConfigurationClassReaderUtils.loadToolingSafeClass(annoTypeName, classLoader); + if (annoType == null) { + return new EmptyVisitor(); + } + ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoType, classLoader); + values.add(anno); + return new ConfigurationClassAnnotationVisitor(anno, classLoader); + } + + public AnnotationVisitor visitArray(String s) { + return new EmptyVisitor(); + } + + public void visitEnd() { + Class arrayType = mutableAnno.getAttributeType(attribName); + Object[] array = (Object[]) Array.newInstance(arrayType.getComponentType(), 0); + mutableAnno.setAttributeValue(attribName, values.toArray(array)); + } + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java new file mode 100644 index 00000000000..afebf5feaa2 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -0,0 +1,207 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.BeanDefinitionReader; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Reads a given fully-populated configuration model, registering bean definitions + * with the given {@link BeanDefinitionRegistry} based on its contents. + * + *

This class was modeled after the {@link BeanDefinitionReader} hierarchy, but does + * not implement/extend any of its artifacts as a configuration model is not a {@link Resource}. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.0 + */ +class ConfigurationClassBeanDefinitionReader { + + private static final Log logger = LogFactory.getLog(ConfigurationClassBeanDefinitionReader.class); + + private final BeanDefinitionRegistry registry; + + + public ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry) { + Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); + this.registry = registry; + } + + + /** + * Reads {@code configurationModel}, registering bean definitions with {@link #registry} + * based on its contents. + */ + public void loadBeanDefinitions(Set configurationModel) { + for (ConfigurationClass configClass : configurationModel) { + loadBeanDefinitionsForConfigurationClass(configClass); + } + } + + /** + * Reads a particular {@link ConfigurationClass}, registering bean definitions for the + * class itself, all its {@link Bean} methods + */ + private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { + doLoadBeanDefinitionForConfigurationClass(configClass); + for (ConfigurationClassMethod method : configClass.getBeanMethods()) { + loadBeanDefinitionsForModelMethod(method); + } + } + + /** + * Registers the {@link Configuration} class itself as a bean definition. + */ + private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) { + if (configClass.getBeanName() == null) { + GenericBeanDefinition configBeanDef = new GenericBeanDefinition(); + configBeanDef.setBeanClassName(configClass.getName()); + String configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(configBeanDef, registry); + configClass.setBeanName(configBeanName); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Registered bean definition for imported @Configuration class %s", configBeanName)); + } + } + } + + /** + * Reads a particular {@link ConfigurationClassMethod}, registering bean definitions with + * {@link #registry} based on its contents. + */ + private void loadBeanDefinitionsForModelMethod(ConfigurationClassMethod method) { + RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(); + ConfigurationClass configClass = method.getDeclaringClass(); + beanDef.setFactoryBeanName(configClass.getBeanName()); + beanDef.setFactoryMethodName(method.getName()); + beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); + + // consider name and any aliases + Bean bean = method.getRequiredAnnotation(Bean.class); + List names = new ArrayList(Arrays.asList(bean.name())); + String beanName = (names.size() > 0) ? names.remove(0) : method.getName(); + for (String alias : bean.name()) { + registry.registerAlias(beanName, alias); + } + + // has this already been overriden (i.e.: via XML)? + if (registry.containsBeanDefinition(beanName)) { + BeanDefinition existingBeanDef = registry.getBeanDefinition(beanName); + // is the existing bean definition one that was created by JavaConfig? + if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) { + // no -> then it's an external override, probably XML + // overriding is legal, return immediately + if (logger.isDebugEnabled()) { + logger.debug(String.format("Skipping loading bean definition for %s: a definition for bean " + + "'%s' already exists. This is likely due to an override in XML.", method, beanName)); + } + return; + } + } + + if (method.getAnnotation(Primary.class) != null) { + beanDef.setPrimary(true); + } + + // is this bean to be instantiated lazily? + Lazy lazy = method.getAnnotation(Lazy.class); + if (lazy != null) { + beanDef.setLazyInit(lazy.value()); + } + else { + Lazy defaultLazy = configClass.getAnnotation(Lazy.class); + if (defaultLazy != null) { + beanDef.setLazyInit(defaultLazy.value()); + } + } + + DependsOn dependsOn = method.getAnnotation(DependsOn.class); + if (dependsOn != null && dependsOn.value().length > 0) { + beanDef.setDependsOn(dependsOn.value()); + } + + Autowire autowire = bean.autowire(); + if (autowire.isAutowire()) { + beanDef.setAutowireMode(autowire.value()); + } + + String initMethodName = bean.initMethod(); + if (StringUtils.hasText(initMethodName)) { + beanDef.setInitMethodName(initMethodName); + } + + String destroyMethodName = bean.destroyMethod(); + if (StringUtils.hasText(destroyMethodName)) { + beanDef.setDestroyMethodName(destroyMethodName); + } + + // consider scoping + Scope scope = method.getAnnotation(Scope.class); + ScopedProxyMode proxyMode = ScopedProxyMode.NO; + if (scope != null) { + beanDef.setScope(scope.value()); + proxyMode = scope.proxyMode(); + if (proxyMode == ScopedProxyMode.DEFAULT) { + proxyMode = ScopedProxyMode.NO; + } + } + + // replace the original bean definition with the target one, if necessary + BeanDefinition beanDefToRegister = beanDef; + if (proxyMode != ScopedProxyMode.NO) { + BeanDefinitionHolder proxyDef = ScopedProxyUtils.createScopedProxy( + new BeanDefinitionHolder(beanDef, beanName), registry, proxyMode == ScopedProxyMode.TARGET_CLASS); + beanDefToRegister = proxyDef.getBeanDefinition(); + } + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getName(), beanName)); + } + + registry.registerBeanDefinition(beanName, beanDefToRegister); + } + + + /** + * {@link RootBeanDefinition} marker subclass used to signify that a bean definition created + * by JavaConfig as opposed to any other configuration source. Used in bean overriding cases + * where it's necessary to determine whether the bean definition was created externally + * (e.g. via XML). + */ + @SuppressWarnings("serial") + private class ConfigurationClassBeanDefinition extends RootBeanDefinition { + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index c3036d9bd03..0cbae633883 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -16,60 +16,59 @@ package org.springframework.context.annotation; -import static java.lang.String.*; -import static org.springframework.context.annotation.AsmUtils.*; - -import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.List; -import net.sf.cglib.core.DefaultGeneratorStrategy; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.CallbackFilter; import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.NoOp; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.asm.ClassAdapter; -import org.springframework.asm.ClassReader; -import org.springframework.asm.ClassWriter; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; + +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; - /** * Enhances {@link Configuration} classes by generating a CGLIB subclass capable of * interacting with the Spring container to respect bean semantics. - * - * @see #enhance(String) - * + * * @author Chris Beams + * @author Juergen Hoeller + * @since 3.0 + * @see #enhance * @see ConfigurationClassPostProcessor */ class ConfigurationClassEnhancer { - private static final Log log = LogFactory.getLog(ConfigurationClassEnhancer.class); + private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class); + + private final List callbackInstances = new ArrayList(); + + private final List> callbackTypes = new ArrayList>(); - private final ArrayList callbackInstances = new ArrayList(); - private final ArrayList> callbackTypes = new ArrayList>(); private final CallbackFilter callbackFilter; /** * Creates a new {@link ConfigurationClassEnhancer} instance. */ - public ConfigurationClassEnhancer(DefaultListableBeanFactory beanFactory) { - Assert.notNull(beanFactory, "beanFactory must be non-null"); + public ConfigurationClassEnhancer(ConfigurableBeanFactory beanFactory) { + Assert.notNull(beanFactory, "BeanFactory must not be null"); callbackInstances.add(new BeanMethodInterceptor(beanFactory)); callbackInstances.add(NoOp.INSTANCE); - for (Callback callback : callbackInstances) + for (Callback callback : callbackInstances) { callbackTypes.add(callback.getClass()); + } - // set up the callback filter to return the index of the BeanMethodInterceptor when + // Set up the callback filter to return the index of the BeanMethodInterceptor when // handling a @Bean-annotated method; otherwise, return index of the NoOp callback. callbackFilter = new CallbackFilter() { public int accept(Method candidateMethod) { @@ -82,24 +81,18 @@ class ConfigurationClassEnhancer { /** * Loads the specified class and generates a CGLIB subclass of it equipped with * container-aware callbacks capable of respecting scoping and other bean semantics. - * * @return fully-qualified name of the enhanced subclass */ - public String enhance(String configClassName) { - if (log.isInfoEnabled()) - log.info("Enhancing " + configClassName); - - Class superclass = loadRequiredClass(configClassName); - - Class subclass = createClass(newEnhancer(superclass), superclass); - - subclass = nestOneClassDeeperIfAspect(superclass, subclass); - - if (log.isInfoEnabled()) - log.info(format("Successfully enhanced %s; enhanced class name is: %s", - configClassName, subclass.getName())); - - return subclass.getName(); + public Class enhance(Class configClass) { + if (logger.isInfoEnabled()) { + logger.info("Enhancing " + configClass.getName()); + } + Class enhancedClass = createClass(newEnhancer(configClass)); + if (logger.isInfoEnabled()) { + logger.info(String.format("Successfully enhanced %s; enhanced class name is: %s", + configClass.getName(), enhancedClass.getName())); + } + return enhancedClass; } /** @@ -116,7 +109,7 @@ class ConfigurationClassEnhancer { enhancer.setSuperclass(superclass); enhancer.setUseFactory(false); enhancer.setCallbackFilter(callbackFilter); - enhancer.setCallbackTypes(callbackTypes.toArray(new Class[] {})); + enhancer.setCallbackTypes(callbackTypes.toArray(new Class[callbackTypes.size()])); return enhancer; } @@ -125,53 +118,86 @@ class ConfigurationClassEnhancer { * Uses enhancer to generate a subclass of superclass, ensuring that * {@link #callbackInstances} are registered for the new subclass. */ - private Class createClass(Enhancer enhancer, Class superclass) { + private Class createClass(Enhancer enhancer) { Class subclass = enhancer.createClass(); - - Enhancer.registerCallbacks(subclass, callbackInstances.toArray(new Callback[] {})); - + Enhancer.registerCallbacks(subclass, callbackInstances.toArray(new Callback[callbackInstances.size()])); return subclass; } + /** - * Works around a constraint imposed by the AspectJ 5 annotation-style programming - * model. See comments inline for detail. - * - * @return original subclass instance unless superclass is annnotated with @Aspect, in - * which case a subclass of the subclass is returned + * Intercepts the invocation of any {@link Bean}-annotated methods in order to ensure proper + * handling of bean semantics such as scoping and AOP proxying. + * @author Chris Beams + * @see Bean + * @see ConfigurationClassEnhancer */ - private Class nestOneClassDeeperIfAspect(Class superclass, Class origSubclass) { - boolean superclassIsAnAspect = false; - - // check for @Aspect by name rather than by class literal to avoid - // requiring AspectJ as a runtime dependency. - for (Annotation anno : superclass.getAnnotations()) - if (anno.annotationType().getName().equals("org.aspectj.lang.annotation.Aspect")) - superclassIsAnAspect = true; - - if (!superclassIsAnAspect) - return origSubclass; - - // the superclass is annotated with AspectJ's @Aspect. - // this means that we must create a subclass of the subclass - // in order to avoid some guard logic in Spring core that disallows - // extending a concrete aspect class. - Enhancer enhancer = newEnhancer(origSubclass); - enhancer.setStrategy(new DefaultGeneratorStrategy() { - @Override - protected byte[] transform(byte[] b) throws Exception { - ClassWriter writer = new ClassWriter(false); - ClassAdapter adapter = new AddAnnotationAdapter(writer, "Lorg/aspectj/lang/annotation/Aspect;"); - ClassReader reader = new ClassReader(b); - reader.accept(adapter, false); - return writer.toByteArray(); + private static class BeanMethodInterceptor implements MethodInterceptor { + + private static final Log logger = LogFactory.getLog(BeanMethodInterceptor.class); + + private final ConfigurableBeanFactory beanFactory; + + public BeanMethodInterceptor(ConfigurableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + + /** + * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the + * existence of this bean object. + */ + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + // by default the bean name is the name of the @Bean-annotated method + String beanName = method.getName(); + + // check to see if the user has explicitly set the bean name + Bean bean = method.getAnnotation(Bean.class); + if(bean != null && bean.name().length > 0) { + beanName = bean.name()[0]; } - }); - // create a subclass of the original subclass - Class newSubclass = createClass(enhancer, origSubclass); + // determine whether this bean is a scoped-proxy + Scope scope = AnnotationUtils.findAnnotation(method, Scope.class); + if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) { + String scopedBeanName = ScopedProxyUtils.getTargetBeanName(beanName); + if (beanFactory.isCurrentlyInCreation(scopedBeanName)) { + beanName = scopedBeanName; + } + } - return newSubclass; - } + // to handle the case of an inter-bean method reference, we must explicitly check the + // container for already cached instances + if (factoryContainsBean(beanName)) { + // we have an already existing cached instance of this bean -> retrieve it + Object cachedBean = beanFactory.getBean(beanName); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Returning cached object [%s] for @Bean method %s.%s", + cachedBean, method.getDeclaringClass().getSimpleName(), beanName)); + } + return cachedBean; + } + + // actually create and return the bean + return proxy.invokeSuper(obj, args); + } + + /** + * Check the beanFactory to see whether the bean named beanName already + * exists. Accounts for the fact that the requested bean may be "in creation", i.e.: + * we're in the middle of servicing the initial request for this bean. From JavaConfig's + * perspective, this means that the bean does not actually yet exist, and that it is now + * our job to create it for the first time by executing the logic in the corresponding + * Bean method. + *

Said another way, this check repurposes + * {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} to determine whether + * the container is calling this method or the user is calling this method. + * @param beanName name of bean to check for + * @return true if beanName already exists in the factory + */ + private boolean factoryContainsBean(String beanName) { + return beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName); + } + } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanMethod.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethod.java similarity index 51% rename from org.springframework.context/src/main/java/org/springframework/context/annotation/BeanMethod.java rename to org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethod.java index 121aaada018..a9e977b09bb 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanMethod.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethod.java @@ -16,9 +16,6 @@ package org.springframework.context.annotation; -import static java.lang.String.*; -import static org.springframework.context.annotation.StandardScopes.*; - import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -28,34 +25,42 @@ import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.util.Assert; - +import org.springframework.util.ClassUtils; +import org.springframework.core.io.ClassPathResource; /** * Represents a {@link Configuration} class method marked with the {@link Bean} annotation. * * @author Chris Beams + * @author Juergen Hoeller + * @since 3.0 * @see ConfigurationClass - * @see ConfigurationModel * @see ConfigurationClassParser - * @see ConfigurationModelBeanDefinitionReader + * @see ConfigurationClassBeanDefinitionReader */ -final class BeanMethod implements BeanMetadataElement { +final class ConfigurationClassMethod implements BeanMetadataElement { private final String name; + private final int modifiers; - private final ModelClass returnType; + + private final ReturnType returnType; + private final ArrayList annotations = new ArrayList(); private transient ConfigurationClass declaringClass; + private transient Object source; - public BeanMethod(String name, int modifiers, ModelClass returnType, Annotation... annotations) { + + public ConfigurationClassMethod(String name, int modifiers, ReturnType returnType, Annotation... annotations) { Assert.hasText(name); this.name = name; Assert.notNull(annotations); - for (Annotation annotation : annotations) + for (Annotation annotation : annotations) { this.annotations.add(annotation); + } Assert.isTrue(modifiers >= 0, "modifiers must be non-negative: " + modifiers); this.modifiers = modifiers; @@ -68,7 +73,7 @@ final class BeanMethod implements BeanMetadataElement { return name; } - public ModelClass getReturnType() { + public ReturnType getReturnType() { return returnType; } @@ -100,18 +105,17 @@ final class BeanMethod implements BeanMetadataElement { */ public T getRequiredAnnotation(Class annoType) { T anno = getAnnotation(annoType); - - if(anno == null) + if (anno == null) { throw new IllegalStateException( - format("required annotation %s is not present on %s", annoType.getSimpleName(), this)); - + String.format("required annotation %s is not present on %s", annoType.getSimpleName(), this)); + } return anno; } /** * Set up a bi-directional relationship between this method and its declaring class. * - * @see ConfigurationClass#addBeanMethod(BeanMethod) + * @see ConfigurationClass#addMethod(ConfigurationClassMethod) */ public void setDeclaringClass(ConfigurationClass declaringClass) { this.declaringClass = declaringClass; @@ -130,97 +134,118 @@ final class BeanMethod implements BeanMetadataElement { } public Location getLocation() { - if (declaringClass == null) + if (declaringClass == null) { throw new IllegalStateException( "declaringClass property is null. Call setDeclaringClass() before calling getLocation()"); + } return new Location(declaringClass.getLocation().getResource(), getSource()); } public void validate(ProblemReporter problemReporter) { - - if (Modifier.isPrivate(getModifiers())) + if (Modifier.isPrivate(getModifiers())) { problemReporter.error(new PrivateMethodError()); - - if (Modifier.isFinal(getModifiers())) + } + if (Modifier.isFinal(getModifiers())) { problemReporter.error(new FinalMethodError()); - - Scope scope = this.getAnnotation(Scope.class); - if(scope != null - && scope.proxyMode() != ScopedProxyMode.NO - && (scope.value().equals(SINGLETON) || scope.value().equals(PROTOTYPE))) - problemReporter.error(new InvalidScopedProxyDeclarationError(this)); + } } - @Override - public String toString() { - String returnTypeName = returnType == null ? "" : returnType.getSimpleName(); - return format("%s: name=%s; returnType=%s; modifiers=%d", - getClass().getSimpleName(), name, returnTypeName, modifiers); - } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((annotations == null) ? 0 : annotations.hashCode()); - result = prime * result + modifiers; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((returnType == null) ? 0 : returnType.hashCode()); - return result; - } + static class ReturnType implements BeanMetadataElement { - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - BeanMethod other = (BeanMethod) obj; - if (annotations == null) { - if (other.annotations != null) - return false; - } else if (!annotations.equals(other.annotations)) - return false; - if (modifiers != other.modifiers) - return false; - if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - if (returnType == null) { - if (other.returnType != null) - return false; - } else if (!returnType.equals(other.returnType)) - return false; - return true; - } + private String name; + + private boolean isInterface; + + private transient Object source; - /** {@link Bean} methods must be non-private in order to accommodate CGLIB. */ - class PrivateMethodError extends Problem { - PrivateMethodError() { - super(format("Method '%s' may not be private; increase the method's visibility to continue", getName()), - BeanMethod.this.getLocation()); + + public ReturnType(String name) { + this.name = name; } - } - /** {@link Bean} methods must be non-final in order to accommodate CGLIB. */ - class FinalMethodError extends Problem { - FinalMethodError() { - super(format("Method '%s' may not be final; remove the final modifier to continue", getName()), - BeanMethod.this.getLocation()); + /** + * Returns the fully-qualified name of this class. + */ + public String getName() { + return name; + } + + /** + * Sets the fully-qualified name of this class. + */ + public void setName(String className) { + this.name = className; + } + + /** + * Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'. + */ + public String getSimpleName() { + return name == null ? null : ClassUtils.getShortName(name); + } + + /** + * Returns whether the class represented by this ModelClass instance is an interface. + */ + public boolean isInterface() { + return isInterface; + } + + /** + * Signifies that this class is (true) or is not (false) an interface. + */ + public void setInterface(boolean isInterface) { + this.isInterface = isInterface; + } + + /** + * Returns a resource path-formatted representation of the .java file that declares this + * class + */ + public Object getSource() { + return source; + } + + /** + * Set the source location for this class. Must be a resource-path formatted string. + * + * @param source resource path to the .java file that declares this class. + */ + public void setSource(Object source) { + this.source = source; + } + + public Location getLocation() { + if (getName() == null) { + throw new IllegalStateException("'name' property is null. Call setName() before calling getLocation()"); + } + return new Location(new ClassPathResource(ClassUtils.convertClassNameToResourcePath(getName())), getSource()); } } - class InvalidScopedProxyDeclarationError extends Problem { - InvalidScopedProxyDeclarationError(BeanMethod method) { - super(format("Method %s contains an invalid annotation declaration: scoped proxies " - + "cannot be created for singleton/prototype beans", method.getName()), - BeanMethod.this.getLocation()); + + /** + * {@link Bean} methods must be non-private in order to accommodate CGLIB. + */ + private class PrivateMethodError extends Problem { + + public PrivateMethodError() { + super(String.format("Method '%s' must not be private; increase the method's visibility to continue", + getName()), ConfigurationClassMethod.this.getLocation()); } + } + + /** + * {@link Bean} methods must be non-final in order to accommodate CGLIB. + */ + private class FinalMethodError extends Problem { + + public FinalMethodError() { + super(String.format("Method '%s' must not be final; remove the final modifier to continue", + getName()), ConfigurationClassMethod.this.getLocation()); + } } -} \ No newline at end of file +} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethodVisitor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethodVisitor.java deleted file mode 100644 index 918c4b88505..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethodVisitor.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import static org.springframework.context.annotation.AsmUtils.*; -import static org.springframework.util.ClassUtils.*; - -import java.lang.annotation.Annotation; -import java.util.ArrayList; - -import org.springframework.asm.AnnotationVisitor; -import org.springframework.asm.ClassAdapter; -import org.springframework.asm.Label; -import org.springframework.asm.MethodAdapter; -import org.springframework.asm.MethodVisitor; -import org.springframework.asm.Opcodes; - - -/** - * ASM {@link MethodVisitor} that visits a single method declared in a given - * {@link Configuration} class. Determines whether the method is a {@link Bean} - * method and if so, adds it to the {@link ConfigurationClass}. - * - * @author Chris Beams - */ -class ConfigurationClassMethodVisitor extends MethodAdapter { - - private final ConfigurationClass configClass; - private final String methodName; - private final int modifiers; - private final ModelClass returnType; - private final ArrayList annotations = new ArrayList(); - private final ClassLoader classLoader; - - private int lineNumber; - - /** - * Creates a new {@link ConfigurationClassMethodVisitor} instance. - * - * @param configClass model object to which this method will be added - * @param methodName name of the method declared in the {@link Configuration} class - * @param methodDescriptor ASM representation of the method signature - * @param modifiers modifiers for this method - */ - public ConfigurationClassMethodVisitor(ConfigurationClass configClass, String methodName, - String methodDescriptor, int modifiers, ClassLoader classLoader) { - super(ASM_EMPTY_VISITOR); - - this.configClass = configClass; - this.methodName = methodName; - this.classLoader = classLoader; - this.modifiers = modifiers; - this.returnType = initReturnTypeFromMethodDescriptor(methodDescriptor); - } - - /** - * Visits a single annotation on this method. Will be called once for each annotation - * present (regardless of its RetentionPolicy). - */ - @Override - public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) { - String annoClassName = convertAsmTypeDescriptorToClassName(annoTypeDesc); - - Class annoClass = loadToolingSafeClass(annoClassName, classLoader); - - if (annoClass == null) - return super.visitAnnotation(annoTypeDesc, visible); - - Annotation annotation = createMutableAnnotation(annoClass, classLoader); - - annotations.add(annotation); - - return new MutableAnnotationVisitor(annotation, classLoader); - } - - /** - * Provides the line number of this method within its declaring class. In reality, this - * number is always inaccurate - lineNo represents the line number of the - * first instruction in this method. Method declaration line numbers are not in any way - * tracked in the bytecode. Any tooling or output that reads this value will have to - * compensate and estimate where the actual method declaration is. - */ - @Override - public void visitLineNumber(int lineNo, Label start) { - this.lineNumber = lineNo; - } - - /** - * Parses through all {@link #annotations} on this method in order to determine whether - * it is a {@link Bean} method and if so adds it to the enclosing {@link #configClass}. - */ - @Override - public void visitEnd() { - for (Annotation anno : annotations) { - if (Bean.class.equals(anno.annotationType())) { - // this method is annotated with @Bean -> add it to the ConfigurationClass model - Annotation[] annoArray = annotations.toArray(new Annotation[] {}); - BeanMethod method = new BeanMethod(methodName, modifiers, returnType, annoArray); - method.setSource(lineNumber); - configClass.addBeanMethod(method); - break; - } - } - } - - /** - * Determines return type from ASM methodDescriptor and determines whether - * that type is an interface. - */ - private ModelClass initReturnTypeFromMethodDescriptor(String methodDescriptor) { - final ModelClass returnType = new ModelClass(getReturnTypeFromAsmMethodDescriptor(methodDescriptor)); - - // detect whether the return type is an interface - newAsmClassReader(convertClassNameToResourcePath(returnType.getName()), classLoader).accept( - new ClassAdapter(ASM_EMPTY_VISITOR) { - @Override - public void visit(int arg0, int arg1, String arg2, String arg3, String arg4, String[] arg5) { - returnType.setInterface((arg1 & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE); - } - }, false); - - return returnType; - } -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 5fa1c6b3c33..475fb88af52 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -16,75 +16,79 @@ package org.springframework.context.annotation; -import static org.springframework.context.annotation.AsmUtils.*; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.Stack; import org.springframework.asm.ClassReader; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.util.ClassUtils; - /** - * Parses a {@link Configuration} class definition, populating a {@link ConfigurationModel}. + * Parses a {@link Configuration} class definition, populating a configuration model. * This ASM-based implementation avoids reflection and eager classloading in order to * interoperate effectively with tooling (Spring IDE) and OSGi environments. - *

- * This class helps separate the concern of parsing the structure of a Configuration class + * + *

This class helps separate the concern of parsing the structure of a Configuration class * from the concern of registering {@link BeanDefinition} objects based on the content of * that model. - * + * * @author Chris Beams - * @see ConfigurationModel - * @see ConfigurationModelBeanDefinitionReader + * @author Juergen Hoeller + * @since 3.0 + * @see ConfigurationClassBeanDefinitionReader */ class ConfigurationClassParser { - /** - * Model to be populated during calls to {@link #parse(Object, String)} - */ - private final ConfigurationModel model; + private final Set model; + private final ProblemReporter problemReporter; + private final ClassLoader classLoader; + /** - * Creates a new {@link ConfigurationClassParser} instance that will be used to populate a - * {@link ConfigurationModel}. - * + * Create a new {@link ConfigurationClassParser} instance that will be used to populate a + * configuration model. * @param model model to be populated by each successive call to {@link #parse} - * @see #getConfigurationModel() */ public ConfigurationClassParser(ProblemReporter problemReporter, ClassLoader classLoader) { - this.model = new ConfigurationModel(); + this.model = new LinkedHashSet(); this.problemReporter = problemReporter; this.classLoader = classLoader; } + /** - * Parse the {@link Configuration @Configuration} class encapsulated by - * configurationSource. - * - * @param configurationSource reader for Configuration class being parsed - * @param configurationId may be null, but if populated represents the bean id (assumes - * that this configuration class was configured via XML) + * Parse the specified {@link Configuration @Configuration} class. + * @param className the name of the class to parse + * @param beanName may be null, but if populated represents the bean id + * (assumes that this configuration class was configured via XML) */ - public void parse(String className, String configurationId) { - + public void parse(String className, String beanName) { String resourcePath = ClassUtils.convertClassNameToResourcePath(className); - - ClassReader configClassReader = newAsmClassReader(getClassAsStream(resourcePath, classLoader)); - + ClassReader configClassReader = ConfigurationClassReaderUtils.newAsmClassReader(ConfigurationClassReaderUtils.getClassAsStream(resourcePath, classLoader)); ConfigurationClass configClass = new ConfigurationClass(); - configClass.setBeanName(configurationId); - + configClass.setBeanName(beanName); configClassReader.accept(new ConfigurationClassVisitor(configClass, model, problemReporter, classLoader), false); model.add(configClass); } /** - * Returns the current {@link ConfigurationModel}, to be called after {@link #parse}. + * Recurse through the model validating each {@link ConfigurationClass}. + * @param problemReporter {@link ProblemReporter} against which any validation errors + * will be registered + * @see ConfigurationClass#validate */ - public ConfigurationModel getConfigurationModel() { - return model; + public void validate() { + for (ConfigurationClass configClass : this.model) { + configClass.validate(this.problemReporter); + } + } + + public Set getModel() { + return this.model; } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index b54b5626922..0b0d15b4327 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -16,28 +16,31 @@ package org.springframework.context.annotation; -import static java.lang.String.*; - import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.parsing.FailFastProblemReporter; +import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.Conventions; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.type.AnnotationMetadata; -import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - +import org.springframework.stereotype.Component; +import org.springframework.util.ClassUtils; /** * {@link BeanFactoryPostProcessor} used for bootstrapping processing of @@ -53,129 +56,134 @@ import org.springframework.util.StringUtils; * executes. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.0 */ -public class ConfigurationClassPostProcessor extends AbstractConfigurationClassProcessor - implements Ordered, BeanFactoryPostProcessor { +public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor, BeanClassLoaderAware { + + public static final String CONFIGURATION_CLASS_ATTRIBUTE = + Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass"); + + /** Whether the CGLIB2 library is present on the classpath */ + private static final boolean cglibAvailable = ClassUtils.isPresent( + "net.sf.cglib.proxy.Enhancer", ConfigurationClassPostProcessor.class.getClassLoader()); + private static final Log logger = LogFactory.getLog(ConfigurationClassPostProcessor.class); /** - * A well-known class in the CGLIB API used when testing to see if CGLIB - * is present on the classpath. Package-private visibility allows for - * manipulation by tests. - * @see #assertCglibIsPresent(BeanDefinitionRegistry) + * Used to register any problems detected with {@link Configuration} or {@link Bean} + * declarations. For instance, a Bean method marked as {@literal final} is illegal + * and would be reported as a problem. Defaults to {@link FailFastProblemReporter}, + * but is overridable with {@link #setProblemReporter} */ - static String CGLIB_TEST_CLASS = "net.sf.cglib.proxy.Callback"; + private ProblemReporter problemReporter = new FailFastProblemReporter(); - /** - * Holder for the calling BeanFactory - * @see #postProcessBeanFactory(ConfigurableListableBeanFactory) - */ - private DefaultListableBeanFactory beanFactory; + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); /** - * @return {@link Ordered#HIGHEST_PRECEDENCE}. + * Override the default {@link ProblemReporter}. + * @param problemReporter custom problem reporter */ - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE; + public void setProblemReporter(ProblemReporter problemReporter) { + this.problemReporter = problemReporter; } - /** - * Finds {@link Configuration} bean definitions within clBeanFactory - * and processes them in order to register bean definitions for each Bean method - * found within; also prepares the the Configuration classes for servicing - * bean requests at runtime by replacing them with CGLIB-enhanced subclasses. - */ - public void postProcessBeanFactory(ConfigurableListableBeanFactory clBeanFactory) throws BeansException { - Assert.isInstanceOf(DefaultListableBeanFactory.class, clBeanFactory); - beanFactory = (DefaultListableBeanFactory) clBeanFactory; - - BeanDefinitionRegistry factoryBeanDefs = processConfigBeanDefinitions(); - - for(String beanName : factoryBeanDefs.getBeanDefinitionNames()) - beanFactory.registerBeanDefinition(beanName, factoryBeanDefs.getBeanDefinition(beanName)); + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } - enhanceConfigurationClasses(); + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; } + /** - * @return a ConfigurationParser that uses the enclosing BeanFactory's - * ClassLoader to load all Configuration class artifacts. + * Prepare the Configuration classes for servicing bean requests at runtime + * by replacing them with CGLIB-enhanced subclasses. */ - @Override - protected ConfigurationClassParser createConfigurationParser() { - return new ConfigurationClassParser(this.getProblemReporter(), beanFactory.getBeanClassLoader()); + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + if (!(beanFactory instanceof BeanDefinitionRegistry)) { + throw new IllegalStateException( + "ConfigurationClassPostProcessor expects a BeanFactory that implements BeanDefinitionRegistry"); + } + processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); + enhanceConfigurationClasses(beanFactory); } + /** - * @return map of all non-abstract {@link BeanDefinition}s in the - * enclosing {@link #beanFactory} + * Build and validate a configuration model based on the registry of + * {@link Configuration} classes. */ - @Override - protected BeanDefinitionRegistry getConfigurationBeanDefinitions(boolean includeAbstractBeanDefs) { - - BeanDefinitionRegistry configBeanDefs = new DefaultListableBeanFactory(); - - for (String beanName : beanFactory.getBeanDefinitionNames()) { - BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); + protected final void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { + Set configBeanDefs = new LinkedHashSet(); + for (String beanName : registry.getBeanDefinitionNames()) { + BeanDefinition beanDef = registry.getBeanDefinition(beanName); + if (checkConfigurationClassBeanDefinition(beanDef)) { + configBeanDefs.add(new BeanDefinitionHolder(beanDef, beanName)); + } + } - if (beanDef.isAbstract() && !includeAbstractBeanDefs) - continue; + // Return immediately if no @Configuration classes were found + if (configBeanDefs.isEmpty()) { + return; + } - if (isConfigurationClassBeanDefinition(beanDef, beanFactory.getBeanClassLoader())) - configBeanDefs.registerBeanDefinition(beanName, beanDef); + // Populate a new configuration model by parsing each @Configuration classes + ConfigurationClassParser parser = new ConfigurationClassParser(this.problemReporter, this.beanClassLoader); + for (BeanDefinitionHolder holder : configBeanDefs) { + parser.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); } + parser.validate(); - return configBeanDefs; + // Read the model and create bean definitions based on its content + new ConfigurationClassBeanDefinitionReader(registry).loadBeanDefinitions(parser.getModel()); } /** - * Post-processes a BeanFactory in search of Configuration class BeanDefinitions; any - * candidates are then enhanced by a {@link ConfigurationClassEnhancer}. Candidate status is - * determined by BeanDefinition attribute metadata. - * + * Post-processes a BeanFactory in search of Configuration class BeanDefinitions; + * any candidates are then enhanced by a {@link ConfigurationClassEnhancer}. + * Candidate status is determined by BeanDefinition attribute metadata. * @see ConfigurationClassEnhancer - * @see BeanFactoryPostProcessor */ - private void enhanceConfigurationClasses() { - - BeanDefinitionRegistry configBeanDefs = getConfigurationBeanDefinitions(true); - - if (configBeanDefs.getBeanDefinitionCount() == 0) + private void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { + Set configBeanDefs = new LinkedHashSet(); + for (String beanName : beanFactory.getBeanDefinitionNames()) { + BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); + if ("full".equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE))) { + configBeanDefs.add(new BeanDefinitionHolder(beanDef, beanName)); + } + } + if (configBeanDefs.isEmpty()) { // nothing to enhance -> return immediately return; - - assertCglibIsPresent(configBeanDefs); - - ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer(beanFactory); - - for (String beanName : configBeanDefs.getBeanDefinitionNames()) { - BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); - String configClassName = beanDef.getBeanClassName(); - String enhancedClassName = enhancer.enhance(configClassName); - - if (logger.isDebugEnabled()) - logger.debug(format("Replacing bean definition '%s' existing class name '%s' " - + "with enhanced class name '%s'", beanName, configClassName, enhancedClassName)); - - beanDef.setBeanClassName(enhancedClassName); } - } - - /** - * Tests for the presence of CGLIB on the classpath by trying to - * classload {@link #CGLIB_TEST_CLASS}. - * @throws IllegalStateException if CGLIB is not present. - */ - private void assertCglibIsPresent(BeanDefinitionRegistry configBeanDefs) { - try { - Class.forName(CGLIB_TEST_CLASS); - } catch (ClassNotFoundException e) { + if (!cglibAvailable) { + Set beanNames = new LinkedHashSet(); + for (BeanDefinitionHolder holder : configBeanDefs) { + beanNames.add(holder.getBeanName()); + } throw new IllegalStateException("CGLIB is required to process @Configuration classes. " + - "Either add CGLIB to the classpath or remove the following @Configuration bean definitions: [" + - StringUtils.arrayToCommaDelimitedString(configBeanDefs.getBeanDefinitionNames()) + "]"); + "Either add CGLIB to the classpath or remove the following @Configuration bean definitions: " + + beanNames); + } + ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer(beanFactory); + for (BeanDefinitionHolder holder : configBeanDefs) { + AbstractBeanDefinition beanDef = (AbstractBeanDefinition) holder.getBeanDefinition(); + try { + Class configClass = beanDef.resolveBeanClass(this.beanClassLoader); + Class enhancedClass = enhancer.enhance(configClass); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Replacing bean definition '%s' existing class name '%s' " + + "with enhanced class name '%s'", holder.getBeanName(), configClass.getName(), enhancedClass.getName())); + } + beanDef.setBeanClass(enhancedClass); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex); + } } } @@ -183,29 +191,41 @@ public class ConfigurationClassPostProcessor extends AbstractConfigurationClassP * @return whether the BeanDefinition's beanClass (or its ancestry) is * {@link Configuration}-annotated, false if no beanClass is specified. */ - private static boolean isConfigurationClassBeanDefinition(BeanDefinition beanDef, ClassLoader classLoader) { - + private boolean checkConfigurationClassBeanDefinition(BeanDefinition beanDef) { // accommodating SPR-5655 - Assert.isInstanceOf(AbstractBeanDefinition.class, beanDef); - if(((AbstractBeanDefinition) beanDef).hasBeanClass()) - return AnnotationUtils.findAnnotation( - ((AbstractBeanDefinition)beanDef).getBeanClass(), Configuration.class) != null; - + if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { + if (AnnotationUtils.findAnnotation( + ((AbstractBeanDefinition) beanDef).getBeanClass(), Configuration.class) != null) { + beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full"); + return true; + } + else if (AnnotationUtils.findAnnotation( + ((AbstractBeanDefinition) beanDef).getBeanClass(), Component.class) != null) { + beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "lite"); + return true; + } + else { + return false; + } + } + SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(this.beanClassLoader); String className = beanDef.getBeanClassName(); - while (className != null && !(className.equals(Object.class.getName()))) { try { - MetadataReader metadataReader = - new SimpleMetadataReaderFactory(classLoader).getMetadataReader(className); - AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); - ClassMetadata classMetadata = metadataReader.getClassMetadata(); - - if (annotationMetadata.hasAnnotation(Configuration.class.getName())) + MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className); + AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); + if (metadata.hasAnnotation(Configuration.class.getName())) { + beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full"); return true; - - className = classMetadata.getSuperClassName(); - } catch (IOException ex) { - throw new RuntimeException(ex); + } + if (metadata.hasAnnotation(Component.class.getName())) { + beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "lite"); + return true; + } + className = metadata.getSuperClassName(); + } + catch (IOException ex) { + throw new BeanDefinitionStoreException("Failed to load class file [" + className + "]", ex); } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassReaderUtils.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassReaderUtils.java new file mode 100644 index 00000000000..08f97f1da29 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassReaderUtils.java @@ -0,0 +1,376 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import java.io.IOException; +import java.io.InputStream; +import static java.lang.String.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.asm.ClassReader; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import static org.springframework.core.annotation.AnnotationUtils.*; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Various utility methods commonly used when interacting with ASM, classloading + * and creating {@link ConfigurationClassAnnotation} instances. + * + * @author Chris Beams + * @since 3.0 + */ +class ConfigurationClassReaderUtils { + + private static final Log logger = LogFactory.getLog(ConfigurationClassReaderUtils.class); + + /** + * Convert a type descriptor to a classname suitable for classloading with + * Class.forName(). + * + * @param typeDescriptor see ASM guide section 2.1.3 + */ + public static String convertAsmTypeDescriptorToClassName(String typeDescriptor) { + final String internalName; // See ASM guide section 2.1.2 + + if ("V".equals(typeDescriptor)) + return Void.class.getName(); + if ("I".equals(typeDescriptor)) + return Integer.class.getName(); + if ("Z".equals(typeDescriptor)) + return Boolean.class.getName(); + + // strip the leading array/object/primitive identifier + if (typeDescriptor.startsWith("[[")) + internalName = typeDescriptor.substring(3); + else if (typeDescriptor.startsWith("[")) + internalName = typeDescriptor.substring(2); + else + internalName = typeDescriptor.substring(1); + + // convert slashes to dots + String className = internalName.replace('/', '.'); + + // and strip trailing semicolon (if present) + if (className.endsWith(";")) + className = className.substring(0, internalName.length() - 1); + + return className; + } + + /** + * @param methodDescriptor see ASM guide section 2.1.4 + */ + public static String getReturnTypeFromAsmMethodDescriptor(String methodDescriptor) { + String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')') + 1); + return convertAsmTypeDescriptorToClassName(returnTypeDescriptor); + } + + /** + * Create a new ASM {@link ClassReader} for pathToClass. Appends '.class' to + * pathToClass before attempting to load. + */ + public static ClassReader newAsmClassReader(String pathToClass, ClassLoader classLoader) { + InputStream is = getClassAsStream(pathToClass, classLoader); + return newAsmClassReader(is); + } + + /** + * Convenience method that creates and returns a new ASM {@link ClassReader} for the + * given InputStream is, closing the InputStream after creating the + * ClassReader and rethrowing any IOException thrown during ClassReader instantiation as + * an unchecked exception. Logs and ignores any IOException thrown when closing the + * InputStream. + * + * @param is InputStream that will be provided to the new ClassReader instance. + */ + public static ClassReader newAsmClassReader(InputStream is) { + try { + return new ClassReader(is); + } + catch (IOException ex) { + throw new BeanDefinitionStoreException("An unexpected exception occurred while creating ASM ClassReader: " + ex); + } + finally { + try { + is.close(); + } + catch (IOException ex) { + // ignore + } + } + } + + /** + * Uses the default ClassLoader to load pathToClass. Appends '.class' to + * pathToClass before attempting to load. + * @param pathToClass resource path to class, not including .class suffix. e.g.: com/acme/MyClass + * @return inputStream for pathToClass + * @throws RuntimeException if pathToClass does not exist + */ + public static InputStream getClassAsStream(String pathToClass, ClassLoader classLoader) { + String classFileName = pathToClass + ClassUtils.CLASS_FILE_SUFFIX; + InputStream is = classLoader.getResourceAsStream(classFileName); + if (is == null) { + throw new IllegalStateException("Class file [" + classFileName + "] not found"); + } + return is; + } + + /** + * Loads the specified class using the default class loader, gracefully handling any + * {@link ClassNotFoundException} that may be thrown by issuing a WARN level logging + * statement and return null. This functionality is specifically implemented to + * accomodate tooling (Spring IDE) concerns, where user-defined types will not be + * available to the tooling. + *

+ * Because {@link ClassNotFoundException} is compensated for by returning null, callers + * must take care to handle the null case appropriately. + *

+ * In cases where the WARN logging statement is not desired, use the + * {@link #loadClass(String)} method, which returns null but issues no logging + * statements. + *

+ * This method should only ever return null in the case of a user-defined type be + * processed at tooling time. Therefore, tooling may not be able to represent any custom + * annotation semantics, but JavaConfig itself will not have any problem loading and + * respecting them at actual runtime. + * + * @param type of class to be returned + * @param fqClassName fully-qualified class name + * + * @return newly loaded class, null if class could not be found. + * + * @see #loadClass(String) + * @see #loadRequiredClass(String) + * @see ClassUtils#getDefaultClassLoader() + */ + @SuppressWarnings("unchecked") + public static Class loadToolingSafeClass(String fqClassName, ClassLoader classLoader) { + try { + return (Class) classLoader.loadClass(fqClassName); + } + catch (ClassNotFoundException ex) { + logger.warn(String.format("Unable to load class [%s], likely due to tooling-specific restrictions." + + "Attempting to continue, but unexpected errors may occur", fqClassName), ex); + return null; + } + } + + /** + * Creates a {@link ConfigurationClassAnnotation} for {@code annoType}. JDK dynamic proxies are used, + * and the returned proxy implements both {@link ConfigurationClassAnnotation} and the annotation type. + * @param annoType annotation type that must be supplied and returned + * @param annoType type of annotation to create + */ + @SuppressWarnings("unchecked") + public static ConfigurationClassAnnotation createMutableAnnotation(Class annoType, ClassLoader classLoader) { + MutableAnnotationInvocationHandler handler = new MutableAnnotationInvocationHandler(annoType); + Class[] interfaces = new Class[] {annoType, ConfigurationClassAnnotation.class}; + return (ConfigurationClassAnnotation) Proxy.newProxyInstance(classLoader, interfaces, handler); + } + + + /** + * Handles calls to {@link ConfigurationClassAnnotation} attribute methods at runtime. Essentially + * emulates what JDK annotation dynamic proxies do. + */ + private static final class MutableAnnotationInvocationHandler implements InvocationHandler { + + private final Class annoType; + private final Map attributes = new HashMap(); + private final Map> attributeTypes = new HashMap>(); + + public MutableAnnotationInvocationHandler(Class annoType) { + // pre-populate the attributes hash will all the names + // and default values of the attributes defined in 'annoType' + Method[] attribs = annoType.getDeclaredMethods(); + for (Method attrib : attribs) { + this.attributes.put(attrib.getName(), getDefaultValue(annoType, attrib.getName())); + this.attributeTypes.put(attrib.getName(), getAttributeType(annoType, attrib.getName())); + } + + this.annoType = annoType; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Assert.isInstanceOf(Annotation.class, proxy); + + String methodName = method.getName(); + + // first -> check to see if this method is an attribute on our annotation + if (attributes.containsKey(methodName)) + return attributes.get(methodName); + + + // second -> is it a method from java.lang.annotation.Annotation? + if (methodName.equals("annotationType")) + return annoType; + + + // third -> is it a method from java.lang.Object? + if (methodName.equals("toString")) + return format("@%s(%s)", annoType.getName(), getAttribs()); + + if (methodName.equals("equals")) + return isEqualTo(proxy, args[0]); + + if (methodName.equals("hashCode")) + return calculateHashCode(proxy); + + + // finally -> is it a method specified by MutableAnno? + if (methodName.equals("setAttributeValue")) { + attributes.put((String) args[0], args[1]); + return null; // setAttributeValue has a 'void' return type + } + + if (methodName.equals("getAttributeType")) + return attributeTypes.get(args[0]); + + throw new UnsupportedOperationException("this proxy does not support method: " + methodName); + } + + /** + * Conforms to the hashCode() specification for Annotation. + * + * @see Annotation#hashCode() + */ + private Object calculateHashCode(Object proxy) { + int sum = 0; + + for (String attribName : attributes.keySet()) { + Object attribValue = attributes.get(attribName); + + final int attribNameHashCode = attribName.hashCode(); + final int attribValueHashCode; + + if (attribValue == null) + // memberValue may be null when a mutable annotation is being added to a + // collection + // and before it has actually been visited (and populated) by + // MutableAnnotationVisitor + attribValueHashCode = 0; + else if (attribValue.getClass().isArray()) + attribValueHashCode = Arrays.hashCode((Object[]) attribValue); + else + attribValueHashCode = attribValue.hashCode(); + + sum += (127 * attribNameHashCode) ^ attribValueHashCode; + } + + return sum; + } + + /** + * Compares proxy object and other object by comparing the return + * values of the methods specified by their common {@link Annotation} ancestry. + *

+ * other must be the same type as or a subtype of proxy. Will + * return false otherwise. + *

+ * Eagerly returns true if {@code proxy} == other + *

+ *

+ * Conforms strictly to the equals() specification for Annotation + *

+ * + * @see Annotation#equals(Object) + */ + private Object isEqualTo(Object proxy, Object other) { + if (proxy == other) + return true; + + if (other == null) + return false; + + if (!annoType.isAssignableFrom(other.getClass())) + return false; + + for (String attribName : attributes.keySet()) { + Object thisVal; + Object thatVal; + + try { + thisVal = attributes.get(attribName); + thatVal = other.getClass().getDeclaredMethod(attribName).invoke(other); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + + if ((thisVal == null) && (thatVal != null)) + return false; + + if ((thatVal == null) && (thisVal != null)) + return false; + + if (thatVal.getClass().isArray()) { + if (!Arrays.equals((Object[]) thatVal, (Object[]) thisVal)) { + return false; + } + } else if (thisVal instanceof Double) { + if (!Double.valueOf((Double) thisVal).equals(Double.valueOf((Double) thatVal))) + return false; + } else if (thisVal instanceof Float) { + if (!Float.valueOf((Float) thisVal).equals(Float.valueOf((Float) thatVal))) + return false; + } else if (!thisVal.equals(thatVal)) { + return false; + } + } + + return true; + } + + private String getAttribs() { + ArrayList attribs = new ArrayList(); + + for (String attribName : attributes.keySet()) + attribs.add(format("%s=%s", attribName, attributes.get(attribName))); + + return StringUtils.collectionToDelimitedString(attribs, ", "); + } + + /** + * Retrieve the type of the given annotation attribute. + */ + private static Class getAttributeType(Class annotationType, String attributeName) { + Method method = null; + + try { + method = annotationType.getDeclaredMethod(attributeName); + } catch (Exception ex) { + ReflectionUtils.handleReflectionException(ex); + } + + return method.getReturnType(); + } + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassVisitor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassVisitor.java index 47bde05e471..89743101274 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassVisitor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassVisitor.java @@ -16,75 +16,83 @@ package org.springframework.context.annotation; -import static java.lang.String.*; -import static org.springframework.context.annotation.AsmUtils.*; -import static org.springframework.util.ClassUtils.*; - import java.lang.annotation.Annotation; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Set; import java.util.Stack; import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.Attribute; import org.springframework.asm.ClassAdapter; import org.springframework.asm.ClassReader; import org.springframework.asm.ClassVisitor; +import org.springframework.asm.FieldVisitor; +import org.springframework.asm.Label; +import org.springframework.asm.MethodAdapter; import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; +import org.springframework.asm.Type; +import org.springframework.asm.commons.EmptyVisitor; import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.core.io.ClassPathResource; - +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * ASM {@link ClassVisitor} that visits a {@link Configuration} class, populating a * {@link ConfigurationClass} instance with information gleaned along the way. - * + * * @author Chris Beams + * @author Juergen Hoeller + * @since 3.0 * @see ConfigurationClassParser * @see ConfigurationClass */ -class ConfigurationClassVisitor extends ClassAdapter { +class ConfigurationClassVisitor implements ClassVisitor { - private static final String OBJECT_DESC = convertClassNameToResourcePath(Object.class.getName()); + private static final String OBJECT_DESC = ClassUtils.convertClassNameToResourcePath(Object.class.getName()); private final ConfigurationClass configClass; - private final ConfigurationModel model; + + private final Set model; + private final ProblemReporter problemReporter; + private final ClassLoader classLoader; - private final HashMap innerClasses = new HashMap(); + private final Stack importStack; - private boolean processInnerClasses = true; - public ConfigurationClassVisitor(ConfigurationClass configClass, ConfigurationModel model, - ProblemReporter problemReporter, ClassLoader classLoader) { - super(ASM_EMPTY_VISITOR); + public ConfigurationClassVisitor(ConfigurationClass configClass, Set model, + ProblemReporter problemReporter, ClassLoader classLoader) { + this.configClass = configClass; this.model = model; this.problemReporter = problemReporter; this.classLoader = classLoader; + this.importStack = new ImportStack(); } - public void setProcessInnerClasses(boolean processInnerClasses) { - this.processInnerClasses = processInnerClasses; + private ConfigurationClassVisitor(ConfigurationClass configClass, Set model, + ProblemReporter problemReporter, ClassLoader classLoader, Stack importStack) { + this.configClass = configClass; + this.model = model; + this.problemReporter = problemReporter; + this.classLoader = classLoader; + this.importStack = importStack; } - @Override - public void visitSource(String sourceFile, String debug) { - String resourcePath = - convertClassNameToResourcePath(configClass.getName()) - .substring(0, configClass.getName().lastIndexOf('.') + 1).concat(sourceFile); - - configClass.setSource(resourcePath); - } - @Override - public void visit(int classVersion, int modifiers, String classTypeDesc, String arg3, - String superTypeDesc, String[] arg5) { + public void visit(int classVersion, int modifiers, String classTypeDesc, String arg3, String superTypeDesc, String[] arg5) { visitSuperType(superTypeDesc); - configClass.setName(convertResourcePathToClassName(classTypeDesc)); + configClass.setName(ClassUtils.convertResourcePathToClassName(classTypeDesc)); // ASM always adds ACC_SUPER to the opcodes/modifiers for class definitions. // Unknown as to why (JavaDoc is silent on the matter), but it should be @@ -94,128 +102,327 @@ class ConfigurationClassVisitor extends ClassAdapter { private void visitSuperType(String superTypeDesc) { // traverse up the type hierarchy unless the next ancestor is java.lang.Object - if (OBJECT_DESC.equals(superTypeDesc)) + if (OBJECT_DESC.equals(superTypeDesc)) { return; + } + ConfigurationClassVisitor visitor = new ConfigurationClassVisitor(configClass, model, problemReporter, classLoader, importStack); + ClassReader reader = ConfigurationClassReaderUtils.newAsmClassReader(superTypeDesc, classLoader); + reader.accept(visitor, false); + } + + public void visitSource(String sourceFile, String debug) { + String resourcePath = + ClassUtils.convertClassNameToResourcePath(configClass.getName()) + .substring(0, configClass.getName().lastIndexOf('.') + 1).concat(sourceFile); - ConfigurationClassVisitor visitor = - new ConfigurationClassVisitor(configClass, model, problemReporter, classLoader); + configClass.setSource(resourcePath); + } - ClassReader reader = newAsmClassReader(superTypeDesc, classLoader); - reader.accept(visitor, false); + public void visitOuterClass(String s, String s1, String s2) { } /** * Visits a class level annotation on a {@link Configuration @Configuration} class. - * *

Upon encountering such an annotation, updates the {@link #configClass} model * object appropriately, and then returns an {@link AnnotationVisitor} implementation * that can populate the annotation appropriately with its attribute data as parsed * by ASM. - * - * @see MutableAnnotation + * @see ConfigurationClassAnnotation * @see Configuration * @see Lazy * @see Import */ - @Override public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) { - String annoTypeName = convertAsmTypeDescriptorToClassName(annoTypeDesc); - Class annoClass = loadToolingSafeClass(annoTypeName, classLoader); + String annoTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc); + Class annoClass = ConfigurationClassReaderUtils.loadToolingSafeClass(annoTypeName, classLoader); - if (annoClass == null) + if (annoClass == null) { // annotation was unable to be loaded -> probably Spring IDE unable to load a user-defined annotation - return super.visitAnnotation(annoTypeDesc, visible); + return new EmptyVisitor(); + } if (Import.class.equals(annoClass)) { - ImportStack importStack = ImportStackHolder.getImportStack(); - if (!importStack.contains(configClass)) { importStack.push(configClass); return new ImportAnnotationVisitor(model, problemReporter, classLoader); } - problemReporter.error(new CircularImportProblem(configClass, importStack)); } - Annotation mutableAnnotation = createMutableAnnotation(annoClass, classLoader); + ConfigurationClassAnnotation mutableAnnotation = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); configClass.addAnnotation(mutableAnnotation); - return new MutableAnnotationVisitor(mutableAnnotation, classLoader); + return new ConfigurationClassAnnotationVisitor(mutableAnnotation, classLoader); + } + + public void visitAttribute(Attribute attribute) { + } + + public void visitInnerClass(String s, String s1, String s2, int i) { + } + + public FieldVisitor visitField(int i, String s, String s1, String s2, Object o) { + return new EmptyVisitor(); } /** * Delegates all {@link Configuration @Configuration} class method parsing to * {@link ConfigurationClassMethodVisitor}. */ - @Override - public MethodVisitor visitMethod(int modifiers, String methodName, String methodDescriptor, - String arg3, String[] arg4) { - + public MethodVisitor visitMethod(int modifiers, String methodName, String methodDescriptor, String arg3, String[] arg4) { return new ConfigurationClassMethodVisitor(configClass, methodName, methodDescriptor, modifiers, classLoader); } + public void visitEnd() { + } + + /** - * Implementation deals with inner classes here even though it would have been more - * intuitive to deal with outer classes. Due to limitations in ASM (resulting from - * limitations in the VM spec) we cannot directly look for outer classes in all cases, - * so instead build up a model of {@link #innerClasses} and process declaring class - * logic in a kind of inverted manner. + * ASM {@link MethodVisitor} that visits a single method declared in a given + * {@link Configuration} class. Determines whether the method is a {@link Bean} + * method and if so, adds it to the {@link ConfigurationClass}. */ - @Override - public void visitInnerClass(String name, String outerName, String innerName, int access) { - if (processInnerClasses == false) - return; + private static class ConfigurationClassMethodVisitor extends MethodAdapter { + + private final ConfigurationClass configClass; + private final String methodName; + private final int modifiers; + private final ConfigurationClassMethod.ReturnType returnType; + private final List annotations = new ArrayList(); + private final ClassLoader classLoader; + + private int lineNumber; + + /** + * Create a new {@link ConfigurationClassMethodVisitor} instance. + * @param configClass model object to which this method will be added + * @param methodName name of the method declared in the {@link Configuration} class + * @param methodDescriptor ASM representation of the method signature + * @param modifiers modifiers for this method + */ + public ConfigurationClassMethodVisitor(ConfigurationClass configClass, String methodName, + String methodDescriptor, int modifiers, ClassLoader classLoader) { + super(new EmptyVisitor()); + this.configClass = configClass; + this.methodName = methodName; + this.classLoader = classLoader; + this.modifiers = modifiers; + this.returnType = initReturnTypeFromMethodDescriptor(methodDescriptor); + } + + /** + * Visits a single annotation on this method. Will be called once for each annotation + * present (regardless of its RetentionPolicy). + */ + @Override + public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) { + String annoClassName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc); + Class annoClass = ConfigurationClassReaderUtils.loadToolingSafeClass(annoClassName, classLoader); + if (annoClass == null) { + return super.visitAnnotation(annoTypeDesc, visible); + } + ConfigurationClassAnnotation annotation = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); + annotations.add(annotation); + return new ConfigurationClassAnnotationVisitor(annotation, classLoader); + } - String innerClassName = convertResourcePathToClassName(name); - String configClassName = configClass.getName(); + /** + * Provides the line number of this method within its declaring class. In reality, this + * number is always inaccurate - lineNo represents the line number of the + * first instruction in this method. Method declaration line numbers are not in any way + * tracked in the bytecode. Any tooling or output that reads this value will have to + * compensate and estimate where the actual method declaration is. + */ + @Override + public void visitLineNumber(int lineNo, Label start) { + this.lineNumber = lineNo; + } - // if the innerClassName is equal to configClassName, we just - // ran into the outermost inner class look up the outer class - // associated with this - if (innerClassName.equals(configClassName)) { - if (innerClasses.containsKey(outerName)) { - configClass.setDeclaringClass(innerClasses.get(outerName)); + /** + * Parses through all {@link #annotations} on this method in order to determine whether + * it is a {@link Bean} method and if so adds it to the enclosing {@link #configClass}. + */ + @Override + public void visitEnd() { + for (Annotation anno : annotations) { + if (Bean.class.equals(anno.annotationType())) { + // this method is annotated with @Bean -> add it to the ConfigurationClass model + Annotation[] annoArray = annotations.toArray(new Annotation[annotations.size()]); + ConfigurationClassMethod method = new ConfigurationClassMethod(methodName, modifiers, returnType, annoArray); + method.setSource(lineNumber); + configClass.addMethod(method); + break; + } } - return; } - ConfigurationClass innerConfigClass = new ConfigurationClass(); + /** + * Determines return type from ASM methodDescriptor and determines whether + * that type is an interface. + */ + private ConfigurationClassMethod.ReturnType initReturnTypeFromMethodDescriptor(String methodDescriptor) { + final ConfigurationClassMethod.ReturnType returnType = new ConfigurationClassMethod.ReturnType(ConfigurationClassReaderUtils.getReturnTypeFromAsmMethodDescriptor(methodDescriptor)); + // detect whether the return type is an interface + ConfigurationClassReaderUtils.newAsmClassReader(ClassUtils.convertClassNameToResourcePath(returnType.getName()), classLoader).accept( + new ClassAdapter(new EmptyVisitor()) { + @Override + public void visit(int arg0, int arg1, String arg2, String arg3, String arg4, String[] arg5) { + returnType.setInterface((arg1 & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE); + } + }, false); + return returnType; + } + } + + + /** + * ASM {@link AnnotationVisitor} implementation that reads an {@link Import} annotation + * for all its specified classes and then one by one processes each class with a new + * {@link ConfigurationClassVisitor}. + */ + private class ImportAnnotationVisitor implements AnnotationVisitor{ + + private final Set model; + + private final ProblemReporter problemReporter; + + private final ClassLoader classLoader; + + private final List classesToImport = new ArrayList(); + + + public ImportAnnotationVisitor( + Set model, ProblemReporter problemReporter, ClassLoader classLoader) { + + this.model = model; + this.problemReporter = problemReporter; + this.classLoader = classLoader; + } + + public void visit(String s, Object o) { + } + + public void visitEnum(String s, String s1, String s2) { + } + + public AnnotationVisitor visitAnnotation(String s, String s1) { + return null; + } + + public AnnotationVisitor visitArray(String attribName) { + Assert.isTrue("value".equals(attribName)); + return new AnnotationVisitor() { + public void visit(String na, Object type) { + Assert.isInstanceOf(Type.class, type); + classesToImport.add(((Type) type).getClassName()); + } + public void visitEnum(String s, String s1, String s2) { + } + public AnnotationVisitor visitAnnotation(String s, String s1) { + return new EmptyVisitor(); + } + public AnnotationVisitor visitArray(String s) { + return new EmptyVisitor(); + } + public void visitEnd() { + } + }; + } + + public void visitEnd() { + for (String classToImport : classesToImport) { + processClassToImport(classToImport); + } + importStack.pop(); + } + + private void processClassToImport(String classToImport) { + ConfigurationClass configClass = new ConfigurationClass(); + ClassReader reader = ConfigurationClassReaderUtils.newAsmClassReader(ClassUtils.convertClassNameToResourcePath(classToImport), classLoader); + reader.accept(new ConfigurationClassVisitor(configClass, model, problemReporter, classLoader, importStack), false); + if (configClass.getAnnotation(Configuration.class) == null) { + problemReporter.error(new NonAnnotatedConfigurationProblem(configClass.getName(), configClass.getLocation())); + } + else { + model.add(configClass); + } + } + } - ConfigurationClassVisitor ccVisitor = - new ConfigurationClassVisitor(innerConfigClass, new ConfigurationModel(), problemReporter, classLoader); - ccVisitor.setProcessInnerClasses(false); - ClassReader reader = newAsmClassReader(name, classLoader); - reader.accept(ccVisitor, false); + @SuppressWarnings("serial") + private static class ImportStack extends Stack { - if (innerClasses.containsKey(outerName)) - innerConfigClass.setDeclaringClass(innerClasses.get(outerName)); + /** + * Simplified contains() implementation that tests to see if any {@link ConfigurationClass} + * exists within this stack that has the same name as elem. Elem must be of + * type ConfigurationClass. + */ + @Override + public boolean contains(Object elem) { + if (!(elem instanceof ConfigurationClass)) { + return false; + } + ConfigurationClass configClass = (ConfigurationClass) elem; + Comparator comparator = new Comparator() { + public int compare(ConfigurationClass first, ConfigurationClass second) { + return first.getName().equals(second.getName()) ? 0 : 1; + } + }; + return (Collections.binarySearch(this, configClass, comparator) != -1); + } + + /** + * Given a stack containing (in order) + *

    + *
  1. com.acme.Foo
  2. + *
  3. com.acme.Bar
  4. + *
  5. com.acme.Baz
  6. + *
+ * Returns "Foo->Bar->Baz". In the case of an empty stack, returns empty string. + */ + @Override + public synchronized String toString() { + StringBuilder builder = new StringBuilder(); + Iterator iterator = iterator(); + while (iterator.hasNext()) { + builder.append(iterator.next().getSimpleName()); + if (iterator.hasNext()) { + builder.append("->"); + } + } + return builder.toString(); + } + } + + + /** Configuration classes must be annotated with {@link Configuration @Configuration}. */ + private static class NonAnnotatedConfigurationProblem extends Problem { + + public NonAnnotatedConfigurationProblem(String className, Location location) { + super(String.format("%s was imported as a @Configuration class but was not actually annotated " + + "with @Configuration. Annotate the class or do not attempt to process it.", className), location); + } - // is the inner class a @Configuration class? If so, add it to the list - if (innerConfigClass.getAnnotation(Configuration.class) != null) - innerClasses.put(name, innerConfigClass); } /** * {@link Problem} registered upon detection of a circular {@link Import}. - * * @see Import - * @see ImportStack - * @see ImportStackHolder */ - class CircularImportProblem extends Problem { + private static class CircularImportProblem extends Problem { - CircularImportProblem(ConfigurationClass attemptedImport, Stack importStack) { - super(format("A circular @Import has been detected: " + + public CircularImportProblem(ConfigurationClass attemptedImport, Stack importStack) { + super(String.format("A circular @Import has been detected: " + "Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " + "already present in the current import stack [%s]", importStack.peek().getSimpleName(), attemptedImport.getSimpleName(), attemptedImport.getSimpleName(), importStack), - new Location(new ClassPathResource(convertClassNameToResourcePath(importStack.peek().getName())), - importStack.peek().getSource()) + new Location(new ClassPathResource( + ClassUtils.convertClassNameToResourcePath(importStack.peek().getName())), + importStack.peek().getSource()) ); } - } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationModel.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationModel.java deleted file mode 100644 index a08008a0ff9..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationModel.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import static java.lang.String.*; - -import java.util.LinkedHashSet; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.parsing.ProblemReporter; - - -/** - * Represents the set of all user-defined {@link Configuration} classes. Once this model - * is populated using a {@link ConfigurationClassParser}, it can be rendered out to a set of - * {@link BeanDefinition} objects. This model provides an important layer of indirection - * between the complexity of parsing a set of classes and the complexity of representing - * the contents of those classes as BeanDefinitions. - * - * @author Chris Beams - * @see ConfigurationClass - * @see ConfigurationClassParser - * @see ConfigurationModelBeanDefinitionReader - */ -@SuppressWarnings("serial") -final class ConfigurationModel extends LinkedHashSet { - - /** - * Recurses through the model validating each {@link ConfigurationClass}. - * - * @param problemReporter {@link ProblemReporter} against which any validation errors - * will be registered - * @see ConfigurationClass#validate - */ - public void validate(ProblemReporter problemReporter) { - for (ConfigurationClass configClass : this) - configClass.validate(problemReporter); - } - - @Override - public String toString() { - return format("%s containing @Configuration classes: %s", getClass().getSimpleName(), super.toString()); - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationModelBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationModelBeanDefinitionReader.java deleted file mode 100644 index 9c1dba09a30..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationModelBeanDefinitionReader.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import static java.lang.String.*; -import static org.springframework.util.StringUtils.*; - -import java.util.ArrayList; -import java.util.Arrays; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.aop.framework.autoproxy.AutoProxyUtils; -import org.springframework.aop.scope.ScopedProxyFactoryBean; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionReader; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry; -import org.springframework.core.io.Resource; -import org.springframework.util.Assert; - - -/** - * Reads a given fully-populated {@link ConfigurationModel}, registering bean definitions - * with the given {@link BeanDefinitionRegistry} based on its contents. - *

- * This class was modeled after the {@link BeanDefinitionReader} hierarchy, but does not - * implement/extend any of its artifacts as {@link ConfigurationModel} is not a - * {@link Resource}. - * - * @author Chris Beams - * @see ConfigurationModel - * @see AbstractConfigurationClassProcessor#processConfigBeanDefinitions() - */ -class ConfigurationModelBeanDefinitionReader { - - private static final Log log = LogFactory.getLog(ConfigurationModelBeanDefinitionReader.class); - - private BeanDefinitionRegistry registry; - - - /** - * Reads {@code configurationModel}, registering bean definitions with {@link #registry} - * based on its contents. - * - * @return new {@link BeanDefinitionRegistry} containing {@link BeanDefinition}s read - * from the model. - */ - public BeanDefinitionRegistry loadBeanDefinitions(ConfigurationModel configurationModel) { - registry = new SimpleBeanDefinitionRegistry(); - - for (ConfigurationClass configClass : configurationModel) - loadBeanDefinitionsForConfigurationClass(configClass); - - return registry; - } - - /** - * Reads a particular {@link ConfigurationClass}, registering bean definitions for the - * class itself, all its {@link Bean} methods - */ - private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { - doLoadBeanDefinitionForConfigurationClass(configClass); - - for (BeanMethod method : configClass.getBeanMethods()) - loadBeanDefinitionsForModelMethod(method); - } - - /** - * Registers the {@link Configuration} class itself as a bean definition. - * @param beanDefs - */ - private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) { - - GenericBeanDefinition configBeanDef = new GenericBeanDefinition(); - configBeanDef.setBeanClassName(configClass.getName()); - - String configBeanName = configClass.getBeanName(); - - // consider the case where it's already been defined (probably in XML) - // and potentially has PropertyValues and ConstructorArgs) - if (registry.containsBeanDefinition(configBeanName)) { - if (log.isInfoEnabled()) - log.info(format("Copying property and constructor arg values from existing bean definition for " - + "@Configuration class %s to new bean definition", configBeanName)); - AbstractBeanDefinition existing = (AbstractBeanDefinition) registry.getBeanDefinition(configBeanName); - configBeanDef.setPropertyValues(existing.getPropertyValues()); - configBeanDef.setConstructorArgumentValues(existing.getConstructorArgumentValues()); - configBeanDef.setResource(existing.getResource()); - } - - if (log.isInfoEnabled()) - log.info(format("Registering bean definition for @Configuration class %s", configBeanName)); - - registry.registerBeanDefinition(configBeanName, configBeanDef); - } - - /** - * Reads a particular {@link BeanMethod}, registering bean definitions with - * {@link #registry} based on its contents. - */ - private void loadBeanDefinitionsForModelMethod(BeanMethod method) { - RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(); - - ConfigurationClass configClass = method.getDeclaringClass(); - - beanDef.setFactoryBeanName(configClass.getBeanName()); - beanDef.setFactoryMethodName(method.getName()); - - Bean bean = method.getRequiredAnnotation(Bean.class); - - // consider scoping - Scope scope = method.getAnnotation(Scope.class); - if(scope != null) - beanDef.setScope(scope.value()); - - // consider name and any aliases - ArrayList names = new ArrayList(Arrays.asList(bean.name())); - String beanName = (names.size() > 0) ? names.remove(0) : method.getName(); - for (String alias : bean.name()) - registry.registerAlias(beanName, alias); - - // has this already been overriden (i.e.: via XML)? - if (containsBeanDefinitionIncludingAncestry(beanName, registry)) { - BeanDefinition existingBeanDef = getBeanDefinitionIncludingAncestry(beanName, registry); - - // is the existing bean definition one that was created by JavaConfig? - if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) { - // no -> then it's an external override, probably XML - - // overriding is legal, return immediately - log.info(format("Skipping loading bean definition for %s: a definition for bean " - + "'%s' already exists. This is likely due to an override in XML.", method, beanName)); - return; - } - } - - if (method.getAnnotation(Primary.class) != null) - beanDef.setPrimary(true); - - // is this bean to be instantiated lazily? - Lazy defaultLazy = configClass.getAnnotation(Lazy.class); - if (defaultLazy != null) - beanDef.setLazyInit(defaultLazy.value()); - Lazy lazy = method.getAnnotation(Lazy.class); - if (lazy != null) - beanDef.setLazyInit(lazy.value()); - - // does this bean have a custom init-method specified? - String initMethodName = bean.initMethod(); - if (hasText(initMethodName)) - beanDef.setInitMethodName(initMethodName); - - // does this bean have a custom destroy-method specified? - String destroyMethodName = bean.destroyMethod(); - if (hasText(destroyMethodName)) - beanDef.setDestroyMethodName(destroyMethodName); - - // is this method annotated with @Scope(scopedProxy=...)? - if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) { - RootBeanDefinition targetDef = beanDef; - - // Create a scoped proxy definition for the original bean name, - // "hiding" the target bean in an internal target definition. - String targetBeanName = resolveHiddenScopedProxyBeanName(beanName); - RootBeanDefinition scopedProxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); - scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName); - - if (scope.proxyMode() == ScopedProxyMode.TARGET_CLASS) - targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); - // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we - // don't need to set it explicitly here. - else - scopedProxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE); - - // The target bean should be ignored in favor of the scoped proxy. - targetDef.setAutowireCandidate(false); - - // Register the target bean as separate bean in the factory - registry.registerBeanDefinition(targetBeanName, targetDef); - - // replace the original bean definition with the target one - beanDef = scopedProxyDefinition; - } - - if (bean.dependsOn().length > 0) - beanDef.setDependsOn(bean.dependsOn()); - - log.info(format("Registering bean definition for @Bean method %s.%s()", - configClass.getName(), beanName)); - - registry.registerBeanDefinition(beanName, beanDef); - - } - - private boolean containsBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) { - try { - getBeanDefinitionIncludingAncestry(beanName, registry); - return true; - } catch (NoSuchBeanDefinitionException ex) { - return false; - } - } - - private BeanDefinition getBeanDefinitionIncludingAncestry(String beanName, BeanDefinitionRegistry registry) { - if(!(registry instanceof ConfigurableListableBeanFactory)) - return registry.getBeanDefinition(beanName); - - ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) registry; - - do { - if (clbf.containsBeanDefinition(beanName)) - return registry.getBeanDefinition(beanName); - - BeanFactory parent = clbf.getParentBeanFactory(); - if (parent == null) { - clbf = null; - } else if (parent instanceof ConfigurableListableBeanFactory) { - clbf = (ConfigurableListableBeanFactory) parent; - // TODO: re-enable - // } else if (parent instanceof AbstractApplicationContext) { - // clbf = ((AbstractApplicationContext) parent).getBeanFactory(); - } else { - throw new IllegalStateException("unknown parent type: " + parent.getClass().getName()); - } - } while (clbf != null); - - throw new NoSuchBeanDefinitionException( - format("No bean definition matching name '%s' " + - "could be found in %s or its ancestry", beanName, registry)); - } - - /** - * Return the hidden name based on a scoped proxy bean name. - * - * @param originalBeanName the scope proxy bean name as declared in the - * Configuration-annotated class - * - * @return the internally-used hidden bean name - */ - public static String resolveHiddenScopedProxyBeanName(String originalBeanName) { - Assert.hasText(originalBeanName); - return TARGET_NAME_PREFIX.concat(originalBeanName); - } - - /** Prefix used when registering the target object for a scoped proxy. */ - private static final String TARGET_NAME_PREFIX = "scopedTarget."; -} - - -/** - * {@link RootBeanDefinition} marker subclass used to signify that a bean definition created - * by JavaConfig as opposed to any other configuration source. Used in bean overriding cases - * where it's necessary to determine whether the bean definition was created externally - * (e.g. via XML). - */ -@SuppressWarnings("serial") -class ConfigurationClassBeanDefinition extends RootBeanDefinition { -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/DependsOn.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/DependsOn.java new file mode 100644 index 00000000000..7380032a4ab --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/DependsOn.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Inherited; +import java.lang.annotation.Documented; + +/** + * Beans on which the current bean depends. Any beans specified are guaranteed to be + * created by the container before this bean. Used infrequently in cases where a bean + * does not explicitly depend on another through properties or constructor arguments, + * but rather depends on the side effects of another bean's initialization. + *

Note: This attribute will not be inherited by child bean definitions, + * hence it needs to be specified per concrete bean definition. + * + *

May be used on any class directly or indirectly annotated with + * {@link org.springframework.stereotype.Component} or on methods annotated + * with {@link Bean}. + * + *

Using {@link DependsOn} at the class level has no effect unless component-scanning + * is being used. If a {@link DependsOn}-annotated class is declared via XML, + * {@link DependsOn} annotation metadata is ignored, and + * {@literal } is respected instead. + * + * @author Juergen Hoeller + * @since 3.0 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface DependsOn { + + String[] value() default {}; + +} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Import.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Import.java index 20ac1bf19c4..51b6f01fc86 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Import.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Import.java @@ -23,18 +23,18 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - /** * Annotation that allows one {@link Configuration} class to import another Configuration, * and thereby all its {@link Bean} definitions. * *

Provides functionality equivalent to the {@literal } element in Spring XML. + * Only supported for actual {@link Configuration} classes. * * @author Chris Beams * @since 3.0 * @see Configuration */ -@Target({ ElementType.TYPE }) +@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportAnnotationVisitor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportAnnotationVisitor.java deleted file mode 100644 index b2d06a69bad..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportAnnotationVisitor.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import static java.lang.String.*; -import static org.springframework.context.annotation.AsmUtils.*; -import static org.springframework.util.ClassUtils.*; - -import java.util.ArrayList; - -import org.springframework.asm.AnnotationVisitor; -import org.springframework.asm.ClassReader; -import org.springframework.asm.Type; -import org.springframework.beans.factory.parsing.ProblemReporter; -import org.springframework.util.Assert; - - -/** - * ASM {@link AnnotationVisitor} implementation that reads an {@link Import} annotation - * for all its specified classes and then one by one processes each class with a new - * {@link ConfigurationClassVisitor}. - * - * @author Chris Beams - * @see Import - * @see ImportStack - * @see ImportStackHolder - * @see ConfigurationClassVisitor - */ -class ImportAnnotationVisitor extends AnnotationAdapter { - - private final ArrayList classesToImport = new ArrayList(); - private final ConfigurationModel model; - private final ProblemReporter problemReporter; - private final ClassLoader classLoader; - - public ImportAnnotationVisitor(ConfigurationModel model, ProblemReporter problemReporter, ClassLoader classLoader) { - super(ASM_EMPTY_VISITOR); - this.model = model; - this.problemReporter = problemReporter; - this.classLoader = classLoader; - } - - @Override - public AnnotationVisitor visitArray(String attribName) { - Assert.isTrue("value".equals(attribName), - format("expected 'value' attribute, got unknown '%s' attribute", attribName)); - - return new AnnotationAdapter(ASM_EMPTY_VISITOR) { - @Override - public void visit(String na, Object type) { - Assert.isInstanceOf(Type.class, type); - classesToImport.add(((Type) type).getClassName()); - } - }; - } - - @Override - public void visitEnd() { - for (String classToImport : classesToImport) - processClassToImport(classToImport); - - ImportStackHolder.getImportStack().pop(); - } - - private void processClassToImport(String classToImport) { - ConfigurationClass configClass = new ConfigurationClass(); - - ClassReader reader = newAsmClassReader(convertClassNameToResourcePath(classToImport), classLoader); - - reader.accept(new ConfigurationClassVisitor(configClass, model, problemReporter, classLoader), false); - - model.add(configClass); - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportStack.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportStack.java deleted file mode 100644 index 82b5230ae49..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportStack.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.Stack; - -import org.springframework.util.Assert; - - -/** - * {@link Stack} used for detecting circular use of the {@link Import} annotation. - * - * @author Chris Beams - * @see Import - * @see ImportStackHolder - * @see CircularImportException - */ -@SuppressWarnings("serial") -class ImportStack extends Stack { - - /** - * Simplified contains() implementation that tests to see if any {@link ConfigurationClass} - * exists within this stack that has the same name as elem. Elem must be of - * type ConfigurationClass. - */ - @Override - public boolean contains(Object elem) { - Assert.isInstanceOf(ConfigurationClass.class, elem); - - ConfigurationClass configClass = (ConfigurationClass) elem; - - Comparator comparator = new Comparator() { - public int compare(ConfigurationClass first, ConfigurationClass second) { - return first.getName().equals(second.getName()) ? 0 : 1; - } - }; - - int index = Collections.binarySearch(this, configClass, comparator); - - return index >= 0 ? true : false; - } - - /** - * Given a stack containing (in order) - *

    - *
  1. com.acme.Foo
  2. - *
  3. com.acme.Bar
  4. - *
  5. com.acme.Baz
  6. - *
- * Returns "Foo->Bar->Baz". In the case of an empty stack, returns empty string. - */ - @Override - public synchronized String toString() { - StringBuilder builder = new StringBuilder(); - - Iterator iterator = this.iterator(); - while (iterator.hasNext()) { - builder.append(iterator.next().getSimpleName()); - if (iterator.hasNext()) - builder.append("->"); - } - - return builder.toString(); - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportStackHolder.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportStackHolder.java deleted file mode 100644 index 33def118492..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportStackHolder.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - - - -/** - * Holder class to expose a thread-bound {@link ImportStack}, used while detecting circular - * declarations of the {@link Import} annotation. - * - * @author Chris Beams - * @see Import - * @see ImportStack - * @see CircularImportException - */ -class ImportStackHolder { - - private static ThreadLocal stackHolder = new ThreadLocal() { - @Override - protected ImportStack initialValue() { - return new ImportStack(); - } - }; - - public static ImportStack getImportStack() { - return stackHolder.get(); - } - - @Override - public String toString() { - return "Holder for circular @Import detection stack"; - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Lazy.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Lazy.java index c42b4530402..fa17b493978 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Lazy.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Lazy.java @@ -21,7 +21,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - +import java.lang.annotation.Inherited; /** * Indicates whether a bean is to be lazily initialized. @@ -51,8 +51,9 @@ import java.lang.annotation.Target; * @see Configuration * @see org.springframework.stereotype.Component */ -@Target({ ElementType.TYPE, ElementType.METHOD }) +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) +@Inherited @Documented public @interface Lazy { diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ModelClass.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ModelClass.java deleted file mode 100644 index 36b48427c63..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ModelClass.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import static org.springframework.util.ClassUtils.*; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.factory.parsing.Location; -import org.springframework.core.io.ClassPathResource; -import org.springframework.util.ClassUtils; - - -/** - * Represents a class, free from java reflection, - * populated by {@link ConfigurationClassParser}. - * - * @author Chris Beams - * @see ConfigurationModel - * @see ConfigurationClass - * @see ConfigurationClassParser - */ -class ModelClass implements BeanMetadataElement { - - private String name; - private boolean isInterface; - private transient Object source; - - /** - * Creates a new and empty ModelClass instance. - */ - public ModelClass() { - } - - /** - * Creates a new ModelClass instance - * - * @param name fully-qualified name of the class being represented - */ - public ModelClass(String name) { - this(name, false); - } - - /** - * Creates a new ModelClass instance - * - * @param name fully-qualified name of the class being represented - * @param isInterface whether the represented type is an interface - */ - public ModelClass(String name, boolean isInterface) { - this.name = name; - this.isInterface = isInterface; - } - - /** - * Returns the fully-qualified name of this class. - */ - public String getName() { - return name; - } - - /** - * Sets the fully-qualified name of this class. - */ - public void setName(String className) { - this.name = className; - } - - /** - * Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'. - */ - public String getSimpleName() { - return name == null ? null : ClassUtils.getShortName(name); - } - - /** - * Returns whether the class represented by this ModelClass instance is an interface. - */ - public boolean isInterface() { - return isInterface; - } - - /** - * Signifies that this class is (true) or is not (false) an interface. - */ - public void setInterface(boolean isInterface) { - this.isInterface = isInterface; - } - - /** - * Returns a resource path-formatted representation of the .java file that declares this - * class - */ - public Object getSource() { - return source; - } - - /** - * Set the source location for this class. Must be a resource-path formatted string. - * - * @param source resource path to the .java file that declares this class. - */ - public void setSource(Object source) { - this.source = source; - } - - public Location getLocation() { - if(getName() == null) - throw new IllegalStateException("'name' property is null. Call setName() before calling getLocation()"); - return new Location(new ClassPathResource(convertClassNameToResourcePath(getName())), getSource()); - } - - /** - * Given a ModelClass instance representing a class com.acme.Foo, this method will - * return - * - *
-	 * ModelClass: name=Foo
-	 * 
- */ - @Override - public String toString() { - return String.format("%s: name=%s", getClass().getSimpleName(), getSimpleName()); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = (prime * result) + (isInterface ? 1231 : 1237); - result = (prime * result) + ((name == null) ? 0 : name.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - - if (obj == null) - return false; - - if (getClass() != obj.getClass()) - return false; - - ModelClass other = (ModelClass) obj; - if (isInterface != other.isInterface) - return false; - - if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - - return true; - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotationArrayVisitor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotationArrayVisitor.java deleted file mode 100644 index 610988fba0d..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotationArrayVisitor.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import static org.springframework.context.annotation.AsmUtils.*; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.util.ArrayList; - -import org.springframework.asm.AnnotationVisitor; - - -/** - * ASM {@link AnnotationVisitor} that visits any annotation array values while populating - * a new {@link MutableAnnotation} instance. - * - * @author Chris Beams - * @see MutableAnnotation - * @see AsmUtils#createMutableAnnotation - */ -class MutableAnnotationArrayVisitor extends AnnotationAdapter { - - private final ArrayList values = new ArrayList(); - private final MutableAnnotation mutableAnno; - private final String attribName; - - private final ClassLoader classLoader; - - public MutableAnnotationArrayVisitor(MutableAnnotation mutableAnno, String attribName, ClassLoader classLoader) { - super(AsmUtils.ASM_EMPTY_VISITOR); - - this.mutableAnno = mutableAnno; - this.attribName = attribName; - this.classLoader = classLoader; - } - - @Override - public void visit(String na, Object value) { - values.add(value); - } - - @Override - public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) { - String annoTypeName = convertAsmTypeDescriptorToClassName(annoTypeDesc); - Class annoType = loadToolingSafeClass(annoTypeName, classLoader); - - if (annoType == null) - return super.visitAnnotation(na, annoTypeDesc); - - Annotation anno = createMutableAnnotation(annoType, classLoader); - values.add(anno); - return new MutableAnnotationVisitor(anno, classLoader); - } - - @Override - public void visitEnd() { - Class arrayType = mutableAnno.getAttributeType(attribName); - Object[] array = (Object[]) Array.newInstance(arrayType.getComponentType(), 0); - mutableAnno.setAttributeValue(attribName, values.toArray(array)); - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotationInvocationHandler.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotationInvocationHandler.java deleted file mode 100644 index da869b24f40..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotationInvocationHandler.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import static java.lang.String.*; -import static org.springframework.core.annotation.AnnotationUtils.*; - -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; - -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; - - -/** - * Handles calls to {@link MutableAnnotation} attribute methods at runtime. Essentially - * emulates what JDK annotation dynamic proxies do. - * - * @author Chris Beams - * @see MutableAnnotation - * @see AsmUtils#createMutableAnnotation - */ -final class MutableAnnotationInvocationHandler implements InvocationHandler { - - private final Class annoType; - private final HashMap attributes = new HashMap(); - private final HashMap> attributeTypes = new HashMap>(); - - public MutableAnnotationInvocationHandler(Class annoType) { - // pre-populate the attributes hash will all the names - // and default values of the attributes defined in 'annoType' - Method[] attribs = annoType.getDeclaredMethods(); - for (Method attrib : attribs) { - this.attributes.put(attrib.getName(), getDefaultValue(annoType, attrib.getName())); - this.attributeTypes.put(attrib.getName(), getAttributeType(annoType, attrib.getName())); - } - - this.annoType = annoType; - } - - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Assert.isInstanceOf(Annotation.class, proxy); - - String methodName = method.getName(); - - // first -> check to see if this method is an attribute on our annotation - if (attributes.containsKey(methodName)) - return attributes.get(methodName); - - - // second -> is it a method from java.lang.annotation.Annotation? - if (methodName.equals("annotationType")) - return annoType; - - - // third -> is it a method from java.lang.Object? - if (methodName.equals("toString")) - return format("@%s(%s)", annoType.getName(), getAttribs()); - - if (methodName.equals("equals")) - return isEqualTo(proxy, args[0]); - - if (methodName.equals("hashCode")) - return calculateHashCode(proxy); - - - // finally -> is it a method specified by MutableAnno? - if (methodName.equals("setAttributeValue")) { - attributes.put((String) args[0], args[1]); - return null; // setAttributeValue has a 'void' return type - } - - if (methodName.equals("getAttributeType")) - return attributeTypes.get(args[0]); - - throw new UnsupportedOperationException("this proxy does not support method: " + methodName); - } - - /** - * Conforms to the hashCode() specification for Annotation. - * - * @see Annotation#hashCode() - */ - private Object calculateHashCode(Object proxy) { - int sum = 0; - - for (String attribName : attributes.keySet()) { - Object attribValue = attributes.get(attribName); - - final int attribNameHashCode = attribName.hashCode(); - final int attribValueHashCode; - - if (attribValue == null) - // memberValue may be null when a mutable annotation is being added to a - // collection - // and before it has actually been visited (and populated) by - // MutableAnnotationVisitor - attribValueHashCode = 0; - else if (attribValue.getClass().isArray()) - attribValueHashCode = Arrays.hashCode((Object[]) attribValue); - else - attribValueHashCode = attribValue.hashCode(); - - sum += (127 * attribNameHashCode) ^ attribValueHashCode; - } - - return sum; - } - - /** - * Compares proxy object and other object by comparing the return - * values of the methods specified by their common {@link Annotation} ancestry. - *

- * other must be the same type as or a subtype of proxy. Will - * return false otherwise. - *

- * Eagerly returns true if {@code proxy} == other - *

- *

- * Conforms strictly to the equals() specification for Annotation - *

- * - * @see Annotation#equals(Object) - */ - private Object isEqualTo(Object proxy, Object other) { - if (proxy == other) - return true; - - if (other == null) - return false; - - if (!annoType.isAssignableFrom(other.getClass())) - return false; - - for (String attribName : attributes.keySet()) { - Object thisVal; - Object thatVal; - - try { - thisVal = attributes.get(attribName); - thatVal = other.getClass().getDeclaredMethod(attribName).invoke(other); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - - if ((thisVal == null) && (thatVal != null)) - return false; - - if ((thatVal == null) && (thisVal != null)) - return false; - - if (thatVal.getClass().isArray()) { - if (!Arrays.equals((Object[]) thatVal, (Object[]) thisVal)) { - return false; - } - } else if (thisVal instanceof Double) { - if (!Double.valueOf((Double) thisVal).equals(Double.valueOf((Double) thatVal))) - return false; - } else if (thisVal instanceof Float) { - if (!Float.valueOf((Float) thisVal).equals(Float.valueOf((Float) thatVal))) - return false; - } else if (!thisVal.equals(thatVal)) { - return false; - } - } - - return true; - } - - private String getAttribs() { - ArrayList attribs = new ArrayList(); - - for (String attribName : attributes.keySet()) - attribs.add(format("%s=%s", attribName, attributes.get(attribName))); - - return StringUtils.collectionToDelimitedString(attribs, ", "); - } - - /** - * Retrieve the type of the given annotation attribute. - */ - private static Class getAttributeType(Class annotationType, String attributeName) { - Method method = null; - - try { - method = annotationType.getDeclaredMethod(attributeName); - } catch (Exception ex) { - ReflectionUtils.handleReflectionException(ex); - } - - return method.getReturnType(); - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotationVisitor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotationVisitor.java deleted file mode 100644 index 6f2160af58e..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/MutableAnnotationVisitor.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation; - -import static org.springframework.context.annotation.AsmUtils.*; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; - -import org.springframework.asm.AnnotationVisitor; -import org.springframework.asm.Type; -import org.springframework.util.Assert; - - -/** - * ASM {@link AnnotationVisitor} that populates a given {@link MutableAnnotation} instance - * with its attributes. - * - * @author Chris Beams - * @see MutableAnnotation - * @see MutableAnnotationInvocationHandler - * @see AsmUtils#createMutableAnnotation - */ -class MutableAnnotationVisitor implements AnnotationVisitor { - - protected final MutableAnnotation mutableAnno; - - private final ClassLoader classLoader; - - /** - * Creates a new {@link MutableAnnotationVisitor} instance that will populate the the - * attributes of the given mutableAnno. Accepts {@link Annotation} instead of - * {@link MutableAnnotation} to avoid the need for callers to typecast. - * - * @param mutableAnno {@link MutableAnnotation} instance to visit and populate - * - * @throws IllegalArgumentException if mutableAnno is not of type - * {@link MutableAnnotation} - * - * @see AsmUtils#createMutableAnnotation - */ - public MutableAnnotationVisitor(Annotation mutableAnno, ClassLoader classLoader) { - Assert.isInstanceOf(MutableAnnotation.class, mutableAnno, "annotation must be mutable"); - this.mutableAnno = (MutableAnnotation) mutableAnno; - this.classLoader = classLoader; - } - - public AnnotationVisitor visitArray(final String attribName) { - return new MutableAnnotationArrayVisitor(mutableAnno, attribName, classLoader); - } - - public void visit(String attribName, Object attribValue) { - Class attribReturnType = mutableAnno.getAttributeType(attribName); - - if (attribReturnType.equals(Class.class)) { - // the attribute type is Class -> load it and set it. - String fqClassName = ((Type) attribValue).getClassName(); - - Class classVal = loadToolingSafeClass(fqClassName, classLoader); - - if (classVal == null) - return; - - mutableAnno.setAttributeValue(attribName, classVal); - return; - } - - // otherwise, assume the value can be set literally - mutableAnno.setAttributeValue(attribName, attribValue); - } - - @SuppressWarnings("unchecked") - public void visitEnum(String attribName, String enumTypeDescriptor, String strEnumValue) { - String enumClassName = convertAsmTypeDescriptorToClassName(enumTypeDescriptor); - - Class enumClass = loadToolingSafeClass(enumClassName, classLoader); - - if (enumClass == null) - return; - - Enum enumValue = Enum.valueOf(enumClass, strEnumValue); - mutableAnno.setAttributeValue(attribName, enumValue); - } - - public AnnotationVisitor visitAnnotation(String attribName, String attribAnnoTypeDesc) { - String annoTypeName = convertAsmTypeDescriptorToClassName(attribAnnoTypeDesc); - Class annoType = loadToolingSafeClass(annoTypeName, classLoader); - - if (annoType == null) - return ASM_EMPTY_VISITOR.visitAnnotation(attribName, attribAnnoTypeDesc); - - Annotation anno = createMutableAnnotation(annoType, classLoader); - - try { - Field attribute = mutableAnno.getClass().getField(attribName); - attribute.set(mutableAnno, anno); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - - return new MutableAnnotationVisitor(anno, classLoader); - } - - public void visitEnd() { - } - -} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Primary.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Primary.java index 2a29b92109a..56307e81f32 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Primary.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Primary.java @@ -18,11 +18,11 @@ package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - /** * Indicates that a bean should be given preference when multiple candidates * are qualified to autowire a single-valued dependency. If exactly one 'primary' @@ -43,8 +43,9 @@ import java.lang.annotation.Target; * @see Bean * @see org.springframework.stereotype.Component */ -@Target({ ElementType.TYPE, ElementType.METHOD }) +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) +@Inherited @Documented public @interface Primary { diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java index 558aa461497..9bc14970ce8 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java @@ -59,13 +59,11 @@ public @interface Scope { /** * Specifies whether a component should be configured as a scoped proxy * and if so, whether the proxy should be interface-based or subclass-based. - * *

Defaults to {@link ScopedProxyMode#NO}, indicating no scoped proxy * should be created. - * *

Analogous to {@literal } support in Spring XML. Valid * only in conjunction with a non-singleton, non-prototype {@link #value()}. */ - ScopedProxyMode proxyMode() default ScopedProxyMode.NO; + ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT; } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopeMetadata.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopeMetadata.java index fac37245247..07b41d1000d 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopeMetadata.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopeMetadata.java @@ -17,6 +17,7 @@ package org.springframework.context.annotation; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.util.Assert; /** * Describes scope characteristics for a Spring-managed bean including the scope @@ -26,6 +27,7 @@ import org.springframework.beans.factory.config.BeanDefinition; * scoped-proxies. * * @author Mark Fisher + * @author Juergen Hoeller * @since 2.5 * @see ScopeMetadataResolver * @see ScopedProxyMode @@ -41,6 +43,7 @@ public class ScopeMetadata { * Set the name of the scope. */ public void setScopeName(String scopeName) { + Assert.notNull(scopeName, "'scopeName' must not be null"); this.scopeName = scopeName; } @@ -55,6 +58,7 @@ public class ScopeMetadata { * Set the proxy-mode to be applied to the scoped instance. */ public void setScopedProxyMode(ScopedProxyMode scopedProxyMode) { + Assert.notNull(scopedProxyMode, "'scopedProxyMode' must not be null"); this.scopedProxyMode = scopedProxyMode; } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopeMetadataResolver.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopeMetadataResolver.java index d25609e6581..e9cbcb25f7b 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopeMetadataResolver.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopeMetadataResolver.java @@ -23,7 +23,7 @@ import org.springframework.beans.factory.config.BeanDefinition; * * @author Mark Fisher * @since 2.5 - * @see Scope + * @see org.springframework.context.annotation.Scope */ public interface ScopeMetadataResolver { diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopedProxyMode.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopedProxyMode.java index cc873d75705..178058270d1 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopedProxyMode.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ScopedProxyMode.java @@ -29,6 +29,12 @@ package org.springframework.context.annotation; */ public enum ScopedProxyMode { + /** + * Default typically equals {@link #NO}, unless a different default + * has been configured at the component-scan instruction level. + */ + DEFAULT, + /** * Do not create a scoped proxy. *

This proxy-mode is not typically useful when used with a diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/StandardScopes.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/StandardScopes.java index 9aedff08252..732eb8eb5b8 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/StandardScopes.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/StandardScopes.java @@ -16,17 +16,16 @@ package org.springframework.context.annotation; - /** * Enumerates the names of the scopes supported out of the box in Spring. - * + * *

Not modeled as an actual java enum because annotations that accept a scope attribute * must allow for user-defined scope names. Given that java enums are not extensible, these * must remain simple string constants. - * + * * @author Chris Beams * @since 3.0 - * @see Scope + * @see org.springframework.context.annotation.Scope */ public class StandardScopes { diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 42d875aa6d8..212f7ec31fa 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -37,6 +37,7 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.support.ResourceEditorRegistrar; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -487,9 +488,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader */ protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { // Invoke factory processors registered with the context instance. - for (BeanFactoryPostProcessor factoryProcessor : getBeanFactoryPostProcessors()) { - factoryProcessor.postProcessBeanFactory(beanFactory); - } + invokeBeanFactoryPostProcessors(getBeanFactoryPostProcessors(), beanFactory); // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let the bean factory post-processors apply to them! @@ -515,7 +514,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered. OrderComparator.sort(priorityOrderedPostProcessors); - invokeBeanFactoryPostProcessors(beanFactory, priorityOrderedPostProcessors); + invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory); // Next, invoke the BeanFactoryPostProcessors that implement Ordered. List orderedPostProcessors = new ArrayList(); @@ -523,21 +522,21 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader orderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class)); } OrderComparator.sort(orderedPostProcessors); - invokeBeanFactoryPostProcessors(beanFactory, orderedPostProcessors); + invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory); // Finally, invoke all other BeanFactoryPostProcessors. List nonOrderedPostProcessors = new ArrayList(); for (String postProcessorName : nonOrderedPostProcessorNames) { nonOrderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class)); } - invokeBeanFactoryPostProcessors(beanFactory, nonOrderedPostProcessors); + invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory); } /** * Invoke the given BeanFactoryPostProcessor beans. */ private void invokeBeanFactoryPostProcessors( - ConfigurableListableBeanFactory beanFactory, List postProcessors) { + List postProcessors, ConfigurableListableBeanFactory beanFactory) { for (BeanFactoryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanFactory(beanFactory); diff --git a/org.springframework.context/src/test/java/example/scannable/AutowiredQualifierFooService.java b/org.springframework.context/src/test/java/example/scannable/AutowiredQualifierFooService.java index 1fcc0e6d429..2bdea2ecce8 100644 --- a/org.springframework.context/src/test/java/example/scannable/AutowiredQualifierFooService.java +++ b/org.springframework.context/src/test/java/example/scannable/AutowiredQualifierFooService.java @@ -21,12 +21,14 @@ import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.AsyncResult; /** * @author Mark Fisher * @author Juergen Hoeller */ +@Lazy public class AutowiredQualifierFooService implements FooService { @Autowired diff --git a/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java b/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java index 03884b65c63..d31815f1eb2 100644 --- a/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java +++ b/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java @@ -27,6 +27,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.DependsOn; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternResolver; @@ -38,7 +40,7 @@ import org.springframework.util.Assert; * @author Mark Fisher * @author Juergen Hoeller */ -@Service +@Service @Lazy @DependsOn("myNamedComponent") public class FooServiceImpl implements FooService { @Autowired private FooDao fooDao; diff --git a/org.springframework.context/src/test/java/example/scannable/NamedComponent.java b/org.springframework.context/src/test/java/example/scannable/NamedComponent.java index 748eef7be8a..1dead3490dd 100644 --- a/org.springframework.context/src/test/java/example/scannable/NamedComponent.java +++ b/org.springframework.context/src/test/java/example/scannable/NamedComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,12 @@ package example.scannable; import org.springframework.stereotype.Component; +import org.springframework.context.annotation.Lazy; /** * @author Mark Fisher */ -@Component("myNamedComponent") +@Component("myNamedComponent") @Lazy public class NamedComponent { } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java index 8bfe58541c6..1a02ed5297f 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/AbstractCircularImportDetectionTests.java @@ -60,7 +60,8 @@ public abstract class AbstractCircularImportDetectionTests { boolean threw = false; try { newParser().parse(loadAsConfigurationSource(X.class), null); - } catch (BeanDefinitionParsingException ex) { + } + catch (BeanDefinitionParsingException ex) { assertTrue("Wrong message. Got: " + ex.getMessage(), ex.getMessage().contains( "Illegal attempt by @Configuration class 'AbstractCircularImportDetectionTests.Z2' " + @@ -71,6 +72,7 @@ public abstract class AbstractCircularImportDetectionTests { assertTrue(threw); } + @Configuration @Import(B.class) static class A { @Bean @@ -79,6 +81,7 @@ public abstract class AbstractCircularImportDetectionTests { } } + @Configuration @Import(A.class) static class B { @Bean @@ -87,6 +90,7 @@ public abstract class AbstractCircularImportDetectionTests { } } + @Configuration @Import( { Y.class, Z.class }) class X { @Bean @@ -95,6 +99,7 @@ public abstract class AbstractCircularImportDetectionTests { } } + @Configuration class Y { @Bean TestBean y() { @@ -102,6 +107,7 @@ public abstract class AbstractCircularImportDetectionTests { } } + @Configuration @Import( { Z1.class, Z2.class }) class Z { @Bean @@ -110,6 +116,7 @@ public abstract class AbstractCircularImportDetectionTests { } } + @Configuration class Z1 { @Bean TestBean z1() { @@ -117,6 +124,7 @@ public abstract class AbstractCircularImportDetectionTests { } } + @Configuration @Import(Z.class) class Z2 { @Bean @@ -124,4 +132,5 @@ public abstract class AbstractCircularImportDetectionTests { return new TestBean(); } } + } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/AnnotationScopeMetadataResolverTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/AnnotationScopeMetadataResolverTests.java index d9612acd4ac..4cfe5ab2c47 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/AnnotationScopeMetadataResolverTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/AnnotationScopeMetadataResolverTests.java @@ -1,18 +1,33 @@ +/* + * Copyright 2002-2008 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 static org.junit.Assert.*; - import org.junit.Before; import org.junit.Test; + import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; /** - * Unit tests for the {@link AnnotationScopeMetadataResolver} class. - * * @author Rick Evans * @author Chris Beams + * @author Juergen Hoeller */ public final class AnnotationScopeMetadataResolverTests { @@ -34,7 +49,6 @@ public final class AnnotationScopeMetadataResolverTests { assertEquals(ScopedProxyMode.NO, scopeMetadata.getScopedProxyMode()); } - @Test public void testThatResolveScopeMetadataDoesApplyScopedProxyModeToAPrototype() { this.scopeMetadataResolver = new AnnotationScopeMetadataResolver(ScopedProxyMode.INTERFACES); @@ -45,6 +59,16 @@ public final class AnnotationScopeMetadataResolverTests { assertEquals(ScopedProxyMode.INTERFACES, scopeMetadata.getScopedProxyMode()); } + @Test + public void testThatResolveScopeMetadataDoesReadScopedProxyModeFromTheAnnotation() { + this.scopeMetadataResolver = new AnnotationScopeMetadataResolver(); + AnnotatedBeanDefinition bd = new AnnotatedGenericBeanDefinition(AnnotatedWithScopedProxy.class); + ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(bd); + assertNotNull("resolveScopeMetadata(..) must *never* return null.", scopeMetadata); + assertEquals("request", scopeMetadata.getScopeName()); + assertEquals(ScopedProxyMode.TARGET_CLASS, scopeMetadata.getScopedProxyMode()); + } + @Test(expected=IllegalArgumentException.class) public void testCtorWithNullScopedProxyMode() { new AnnotationScopeMetadataResolver(null); @@ -65,4 +89,9 @@ public final class AnnotationScopeMetadataResolverTests { private static final class AnnotatedWithPrototypeScope { } + + @Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS) + private static final class AnnotatedWithScopedProxy { + } + } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanMethodTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanMethodTests.java index ff8a761bef5..9bb3092fe67 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanMethodTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanMethodTests.java @@ -13,42 +13,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.context.annotation; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.springframework.context.annotation.AsmUtils.*; -import static org.springframework.context.annotation.ScopedProxyMode.*; -import static org.springframework.context.annotation.StandardScopes.*; +package org.springframework.context.annotation; import java.lang.reflect.Modifier; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; import org.junit.Test; + import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.parsing.FailFastProblemReporter; import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.ProblemReporter; +import static org.springframework.context.annotation.ConfigurationClassReaderUtils.*; +import static org.springframework.context.annotation.ScopedProxyMode.*; +import static org.springframework.context.annotation.StandardScopes.*; import org.springframework.core.io.ClassPathResource; import org.springframework.util.ClassUtils; - /** - * Unit tests for {@link BeanMethod}. - * * @author Chris Beams */ public class BeanMethodTests { private ProblemReporter problemReporter = new FailFastProblemReporter(); private String beanName = "foo"; - private Bean beanAnno = createMutableAnnotation(Bean.class, ClassUtils.getDefaultClassLoader()); - private ModelClass returnType = new ModelClass("FooType"); + private Bean beanAnno = (Bean) createMutableAnnotation(Bean.class, ClassUtils.getDefaultClassLoader()); + private ConfigurationClassMethod.ReturnType returnType = new ConfigurationClassMethod.ReturnType("FooType"); private ConfigurationClass declaringClass = new ConfigurationClass(); { declaringClass.setName("test.Config"); } @Test public void testWellFormedMethod() { - BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno); + ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno); assertThat(beanMethod.getName(), sameInstance(beanName)); assertThat(beanMethod.getModifiers(), equalTo(0)); @@ -84,7 +82,7 @@ public class BeanMethodTests { @Test public void finalMethodsAreIllegal() { - BeanMethod beanMethod = new BeanMethod(beanName, Modifier.FINAL, returnType, beanAnno); + ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, Modifier.FINAL, returnType, beanAnno); beanMethod.setDeclaringClass(declaringClass); try { beanMethod.validate(problemReporter); @@ -96,7 +94,7 @@ public class BeanMethodTests { @Test public void privateMethodsAreIllegal() { - BeanMethod beanMethod = new BeanMethod(beanName, Modifier.PRIVATE, returnType, beanAnno); + ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, Modifier.PRIVATE, returnType, beanAnno); beanMethod.setDeclaringClass(declaringClass); try { beanMethod.validate(problemReporter); @@ -106,57 +104,18 @@ public class BeanMethodTests { } } - @Test - public void singletonInterfaceScopedProxiesAreIllegal() { - Scope scope = SingletonInterfaceProxy.class.getAnnotation(Scope.class); - BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno, scope); - beanMethod.setDeclaringClass(declaringClass); - try { - beanMethod.validate(problemReporter); - fail("should have failed due to singleton with scoped proxy"); - } catch (Exception ex) { - assertTrue(ex.getMessage().contains("cannot be created for singleton/prototype beans")); - } - } - - @Test - public void singletonTargetClassScopedProxiesAreIllegal() { - Scope scope = SingletonTargetClassProxy.class.getAnnotation(Scope.class); - BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno, scope); - beanMethod.setDeclaringClass(declaringClass); - try { - beanMethod.validate(problemReporter); - fail("should have failed due to singleton with scoped proxy"); - } catch (Exception ex) { - assertTrue(ex.getMessage().contains("cannot be created for singleton/prototype beans")); - } - } - @Test public void singletonsSansProxyAreLegal() { Scope scope = SingletonNoProxy.class.getAnnotation(Scope.class); - BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno, scope); + ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno, scope); beanMethod.setDeclaringClass(declaringClass); beanMethod.validate(problemReporter); // should validate without problems - it's legal } - @Test - public void prototypeInterfaceScopedProxiesAreIllegal() { - Scope scope = PrototypeInterfaceProxy.class.getAnnotation(Scope.class); - BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno, scope); - beanMethod.setDeclaringClass(declaringClass); - try { - beanMethod.validate(problemReporter); - fail("should have failed due to prototype with scoped proxy"); - } catch (Exception ex) { - assertTrue(ex.getMessage().contains("cannot be created for singleton/prototype beans")); - } - } - @Test public void sessionInterfaceScopedProxiesAreLegal() { Scope scope = SessionInterfaceProxy.class.getAnnotation(Scope.class); - BeanMethod beanMethod = new BeanMethod(beanName, 0, returnType, beanAnno, scope); + ConfigurationClassMethod beanMethod = new ConfigurationClassMethod(beanName, 0, returnType, beanAnno, scope); beanMethod.setDeclaringClass(declaringClass); beanMethod.validate(problemReporter); // should validate without problems - it's legal } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java index 03a09777bba..c2f159251f3 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java @@ -16,10 +16,15 @@ package org.springframework.context.annotation; -import static org.junit.Assert.*; - +import example.scannable.CustomComponent; +import example.scannable.FooService; +import example.scannable.FooServiceImpl; +import example.scannable.NamedStubDao; +import example.scannable.StubFooDao; import org.aspectj.lang.annotation.Aspect; +import static org.junit.Assert.*; import org.junit.Test; + import org.springframework.beans.TestBean; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -34,12 +39,6 @@ import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.stereotype.Component; -import example.scannable.CustomComponent; -import example.scannable.FooService; -import example.scannable.FooServiceImpl; -import example.scannable.NamedStubDao; -import example.scannable.StubFooDao; - /** * @author Mark Fisher * @author Juergen Hoeller @@ -49,7 +48,7 @@ public class ClassPathBeanDefinitionScannerTests { private static final String BASE_PACKAGE = "example.scannable"; - + @Test public void testSimpleScanWithDefaultFiltersAndPostProcessors() { GenericApplicationContext context = new GenericApplicationContext(); @@ -62,9 +61,34 @@ public class ClassPathBeanDefinitionScannerTests { assertTrue(context.containsBean("myNamedComponent")); assertTrue(context.containsBean("myNamedDao")); assertTrue(context.containsBean("thoreau")); + assertTrue(context.containsBean(AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); + context.refresh(); + FooServiceImpl service = context.getBean("fooServiceImpl", FooServiceImpl.class); + assertTrue(context.getDefaultListableBeanFactory().containsSingleton("myNamedComponent")); + assertEquals("bar", service.foo(1)); + } + + @Test + public void testSimpleScanWithDefaultFiltersAndPrimaryLazyBean() { + GenericApplicationContext context = new GenericApplicationContext(); + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); + scanner.scan(BASE_PACKAGE); + scanner.scan("org.springframework.context.annotation5"); + assertTrue(context.containsBean("serviceInvocationCounter")); + assertTrue(context.containsBean("fooServiceImpl")); + assertTrue(context.containsBean("stubFooDao")); + assertTrue(context.containsBean("myNamedComponent")); + assertTrue(context.containsBean("myNamedDao")); + assertTrue(context.containsBean("otherFooDao")); + context.refresh(); + assertFalse(context.getBeanFactory().containsSingleton("otherFooDao")); + assertFalse(context.getBeanFactory().containsSingleton("fooServiceImpl")); + FooServiceImpl service = context.getBean("fooServiceImpl", FooServiceImpl.class); + assertTrue(context.getBeanFactory().containsSingleton("otherFooDao")); + assertEquals("other", service.foo(1)); } @Test @@ -354,7 +378,7 @@ public class ClassPathBeanDefinitionScannerTests { assertEquals(10, beanCount); context.refresh(); - FooServiceImpl fooService = (FooServiceImpl) context.getBean("fooService"); + FooServiceImpl fooService = context.getBean("fooService", FooServiceImpl.class); StaticListableBeanFactory myBf = (StaticListableBeanFactory) context.getBean("myBf"); MessageSource ms = (MessageSource) context.getBean("messageSource"); assertTrue(fooService.isInitCalled()); @@ -415,6 +439,7 @@ public class ClassPathBeanDefinitionScannerTests { scanner.scan(BASE_PACKAGE); try { context.refresh(); + context.getBean("fooService"); fail("BeanCreationException expected; fooDao should not have been an autowire-candidate"); } catch (BeanCreationException expected) { diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathFactoryBeanDefinitionScannerTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathFactoryBeanDefinitionScannerTests.java index 10840926c1e..023ef3c258e 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathFactoryBeanDefinitionScannerTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathFactoryBeanDefinitionScannerTests.java @@ -16,91 +16,75 @@ package org.springframework.context.annotation; -import java.util.Set; - import junit.framework.TestCase; import org.springframework.aop.scope.ScopedObject; import org.springframework.aop.support.AopUtils; import org.springframework.beans.TestBean; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.SimpleMapScope; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation4.FactoryMethodComponent; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.util.ClassUtils; - +/** + * @author Mark Pollack + * @author Juergen Hoeller + */ public class ClassPathFactoryBeanDefinitionScannerTests extends TestCase { private static final String BASE_PACKAGE = FactoryMethodComponent.class.getPackage().getName(); - private static final int NUM_DEFAULT_BEAN_DEFS = 5; - - private static final int NUM_FACTORY_METHODS = 5; // @ScopedProxy creates another - - private static final int NUM_COMPONENT_DEFS = 1; - - public void testSingletonScopedFactoryMethod() - { + public void testSingletonScopedFactoryMethod() { GenericApplicationContext context = new GenericApplicationContext(); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); - - SimpleMapScope scope = new SimpleMapScope(); - context.getBeanFactory().registerScope("request", scope); - - int beanCount = scanner.scan(BASE_PACKAGE); - - assertEquals(NUM_FACTORY_METHODS + NUM_COMPONENT_DEFS + NUM_DEFAULT_BEAN_DEFS, beanCount); - assertTrue(context.containsBean("factoryMethodComponent")); - assertTrue(context.containsBean("factoryMethodComponent$staticInstance")); - assertTrue(context.containsBean("factoryMethodComponent$getPublicInstance")); - - + context.getBeanFactory().registerScope("request", new SimpleMapScope()); - TestBean staticTestBean = (TestBean)context.getBean("factoryMethodComponent$staticInstance");//1 - assertEquals("staticInstance", staticTestBean.getName()); - TestBean staticTestBean2 = (TestBean)context.getBean("factoryMethodComponent$staticInstance");//1 - assertSame(staticTestBean, staticTestBean2); - - TestBean tb = (TestBean)context.getBean("factoryMethodComponent$getPublicInstance"); //2 + scanner.scan(BASE_PACKAGE); + context.registerBeanDefinition("clientBean", new RootBeanDefinition(QualifiedClientBean.class)); + context.refresh(); + + FactoryMethodComponent fmc = context.getBean("factoryMethodComponent", FactoryMethodComponent.class); + assertFalse(fmc.getClass().getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)); + + TestBean tb = (TestBean)context.getBean("publicInstance"); //2 assertEquals("publicInstance", tb.getName()); - TestBean tb2 = (TestBean)context.getBean("factoryMethodComponent$getPublicInstance"); //2 + TestBean tb2 = (TestBean)context.getBean("publicInstance"); //2 assertEquals("publicInstance", tb2.getName()); assertSame(tb2, tb); - //Were qualifiers applied to bean definition - ConfigurableListableBeanFactory cbf = (ConfigurableListableBeanFactory)context.getAutowireCapableBeanFactory(); - AbstractBeanDefinition abd = (AbstractBeanDefinition)cbf.getBeanDefinition("factoryMethodComponent$getPublicInstance"); //2 - Set qualifierSet = abd.getQualifiers(); - assertEquals(1, qualifierSet.size()); - - - tb = (TestBean)context.getBean("factoryMethodComponent$getProtectedInstance"); //3 + tb = (TestBean)context.getBean("protectedInstance"); //3 assertEquals("protectedInstance", tb.getName()); - tb2 = (TestBean)context.getBean("factoryMethodComponent$getProtectedInstance"); //3 + assertSame(tb, context.getBean("protectedInstance")); + assertEquals("0", tb.getCountry()); + tb2 = (TestBean)context.getBean("protectedInstance"); //3 assertEquals("protectedInstance", tb2.getName()); assertSame(tb2, tb); - - tb = (TestBean)context.getBean("factoryMethodComponent$getPrivateInstance"); //4 + + tb = (TestBean)context.getBean("privateInstance"); //4 assertEquals("privateInstance", tb.getName()); - assertEquals(0, tb.getAge()); - tb2 = (TestBean)context.getBean("factoryMethodComponent$getPrivateInstance"); //4 - assertEquals(1, tb2.getAge()); + assertEquals(1, tb.getAge()); + tb2 = (TestBean)context.getBean("privateInstance"); //4 + assertEquals(2, tb2.getAge()); assertNotSame(tb2, tb); - Object bean = context.getBean("scopedTarget.factoryMethodComponent$requestScopedInstance"); //5 - assertNotNull(bean); - assertTrue(bean instanceof ScopedObject); - - //Scope assertions + Object bean = context.getBean("requestScopedInstance"); //5 assertTrue(AopUtils.isCglibProxy(bean)); - - - - - + assertTrue(bean instanceof ScopedObject); + + QualifiedClientBean clientBean = context.getBean("clientBean", QualifiedClientBean.class); + assertSame(clientBean.testBean, context.getBean("publicInstance")); } + + + public static class QualifiedClientBean { + + @Autowired @Qualifier("public") + public TestBean testBean; + } + } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index 3363a6bba7c..a62e8b113fa 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -13,104 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.context.annotation; -import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; -import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; - -import java.lang.reflect.Field; -import java.util.Vector; - -import org.junit.Ignore; import org.junit.Test; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ConfigurationClassPostProcessor; -import org.springframework.util.ClassUtils; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; /** - * Unit tests for {@link ConfigurationClassPostProcessor}. - * * @author Chris Beams */ public class ConfigurationClassPostProcessorTests { - private static final String ORIG_CGLIB_TEST_CLASS = ConfigurationClassPostProcessor.CGLIB_TEST_CLASS; - private static final String BOGUS_CGLIB_TEST_CLASS = "a.bogus.class"; - - /** - * CGLIB is an optional dependency for Spring. If users attempt - * to use {@link Configuration} classes, they'll need it on the classpath; - * if Configuration classes are present in the bean factory and CGLIB - * is not present, an instructive exception should be thrown. - */ - @Test - public void testFailFastIfCglibNotPresent() { - @Configuration class Config { } - - DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); - factory.registerBeanDefinition("config", rootBeanDefinition(Config.class).getBeanDefinition()); - ConfigurationClassPostProcessor cpp = new ConfigurationClassPostProcessor(); - - // temporarily set the cglib test class to something bogus - ConfigurationClassPostProcessor.CGLIB_TEST_CLASS = BOGUS_CGLIB_TEST_CLASS; - - try { - cpp.postProcessBeanFactory(factory); - } catch (RuntimeException ex) { - assertTrue(ex.getMessage().contains("CGLIB is required to process @Configuration classes")); - } finally { - ConfigurationClassPostProcessor.CGLIB_TEST_CLASS = ORIG_CGLIB_TEST_CLASS; - } - } - - /** - * In order to keep Spring's footprint as small as possible, CGLIB must - * not be required on the classpath unless the user is taking advantage - * of {@link Configuration} classes. - * - * This test will fail if any CGLIB classes are classloaded before the call - * to {@link ConfigurationClassPostProcessor#enhanceConfigurationClasses} - */ - @Ignore @Test // because classloader hacking below causes extremely hard to - // debug downstream side effects. Re-enable at will to verify - // CGLIB is not prematurely classloaded, but this technique is - // not stable enough to leave enabled. - public void testCglibClassesAreLoadedJustInTimeForEnhancement() throws Exception { - ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); - Field classesField = ClassLoader.class.getDeclaredField("classes"); - classesField.setAccessible(true); - - // first, remove any CGLIB classes that may have been loaded by other tests - @SuppressWarnings("unchecked") - Vector> classes = (Vector>) classesField.get(classLoader); - - Vector> cglibClassesAlreadyLoaded = new Vector>(); - for(Class loadedClass : classes) - if(loadedClass.getName().startsWith("net.sf.cglib")) - cglibClassesAlreadyLoaded.add(loadedClass); - - for(Class cglibClass : cglibClassesAlreadyLoaded) - classes.remove(cglibClass); - - // now, execute a scenario where everything except enhancement occurs - // -- no CGLIB classes should get loaded! - testFailFastIfCglibNotPresent(); - - // test to ensure that indeed no CGLIB classes have been loaded - for(Class loadedClass : classes) - if(loadedClass.getName().startsWith("net.sf.cglib")) - fail("CGLIB class should not have been eagerly loaded: " + loadedClass.getName()); - } - /** * Enhanced {@link Configuration} classes are only necessary for respecting * certain bean semantics, like singleton-scoping, scoped proxies, etc. - * - * Technically, {@link ConfigurationClassPostProcessor} could fail to enhance the + *

Technically, {@link ConfigurationClassPostProcessor} could fail to enhance the * registered Configuration classes and many use cases would still work. * Certain cases, however, like inter-bean singleton references would not. * We test for such a case below, and in doing so prove that enhancement is @@ -118,17 +38,35 @@ public class ConfigurationClassPostProcessorTests { */ @Test public void testEnhancementIsPresentBecauseSingletonSemanticsAreRespected() { - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerBeanDefinition("config", - rootBeanDefinition(SingletonBeanConfig.class).getBeanDefinition()); - new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class)); + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(beanFactory); Foo foo = (Foo) beanFactory.getBean("foo"); Bar bar = (Bar) beanFactory.getBean("bar"); - assertThat(foo, sameInstance(bar.foo)); + assertSame(foo, bar.foo); } + /** + * Tests the fix for SPR-5655, a special workaround that prefers reflection + * over ASM if a bean class is already loaded. + */ + @Test + public void testAlreadyLoadedConfigurationClasses() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerBeanDefinition("unloadedConfig", + new RootBeanDefinition(UnloadedConfig.class.getName(), null, null)); + beanFactory.registerBeanDefinition("loadedConfig", new RootBeanDefinition(LoadedConfig.class)); + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(beanFactory); + beanFactory.getBean("foo"); + beanFactory.getBean("bar"); + } + + @Configuration static class SingletonBeanConfig { + public @Bean Foo foo() { return new Foo(); } @@ -138,27 +76,16 @@ public class ConfigurationClassPostProcessorTests { } } - static class Foo { } + + static class Foo { + } + + static class Bar { final Foo foo; public Bar(Foo foo) { this.foo = foo; } } - /** - * Tests the fix for SPR-5655, a special workaround that prefers reflection - * over ASM if a bean class is already loaded. - */ - @Test - public void testAlreadyLoadedConfigurationClasses() { - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerBeanDefinition("unloadedConfig", - rootBeanDefinition(UnloadedConfig.class.getName()).getBeanDefinition()); - beanFactory.registerBeanDefinition("loadedConfig", - rootBeanDefinition(LoadedConfig.class).getBeanDefinition()); - new ConfigurationClassPostProcessor() .postProcessBeanFactory(beanFactory); - beanFactory.getBean("foo"); - beanFactory.getBean("bar"); - } @Configuration static class UnloadedConfig { @@ -167,10 +94,12 @@ public class ConfigurationClassPostProcessorTests { } } + @Configuration static class LoadedConfig { public @Bean Bar bar() { return new Bar(new Foo()); } } + } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationModelTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationModelTests.java deleted file mode 100644 index 8b2867b48ae..00000000000 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationModelTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.context.annotation; - -import static java.lang.String.*; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; - -import org.junit.Test; -import org.springframework.context.annotation.ConfigurationClass; -import org.springframework.context.annotation.ConfigurationModel; - - -/** - * Unit tests for {@link ConfigurationModel}. - * - * @author Chris Beams - */ -public class ConfigurationModelTests { - - @Test - public void testToString() { - ConfigurationModel model = new ConfigurationModel(); - assertThat(model.toString(), equalTo( - "ConfigurationModel containing @Configuration classes: []")); - - ConfigurationClass config1 = new ConfigurationClass(); - config1.setName("test.Config1"); - model.add(config1); - - assertThat(model.toString(), equalTo(format( - "ConfigurationModel containing @Configuration classes: [%s]", config1))); - } - -} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/InvalidConfigurationClassDefinitionTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/InvalidConfigurationClassDefinitionTests.java index 4a42783bc79..8e3e909b273 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/InvalidConfigurationClassDefinitionTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/InvalidConfigurationClassDefinitionTests.java @@ -45,9 +45,11 @@ public class InvalidConfigurationClassDefinitionTests { beanFactory.registerBeanDefinition("config", configBeanDef); try { - new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(beanFactory); fail("expected exception"); - } catch (BeanDefinitionParsingException ex) { + } + catch (BeanDefinitionParsingException ex) { assertTrue(ex.getMessage(), ex.getMessage().contains("Remove the final modifier")); } } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/AbstractBeanDefinitionConfigurationClassTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/AbstractBeanDefinitionConfigurationClassTests.java deleted file mode 100644 index 675d0b3de2c..00000000000 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/AbstractBeanDefinitionConfigurationClassTests.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.annotation.configuration; - -import static org.junit.Assert.*; -import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; - -import org.junit.Test; -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ConfigurationClassPostProcessor; - - -/** - * Covers the somewhat unlilely case of a {@link Configuration} class being declared - * as an abstract {@link BeanDefinition}. - * - * @author Chris Beams - * @see BeanDefinition#isAbstract() - */ -public class AbstractBeanDefinitionConfigurationClassTests { - - @SuppressWarnings("unused") - @Test - public void abstractConfigurationClassBeanDefinitionsAreIgnored() { - @Configuration class Abstract { @Bean Object foo1() { return null; } } - @Configuration class Concrete { @Bean Object foo2() { return null; } } - - DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); - factory.registerBeanDefinition("abstract", - rootBeanDefinition(Abstract.class).setAbstract(true).getBeanDefinition()); - factory.registerBeanDefinition("concrete", - rootBeanDefinition(Concrete.class).setAbstract(false).getBeanDefinition()); - new ConfigurationClassPostProcessor().postProcessBeanFactory(factory); - - assertTrue("abstract configuration should be CGLIB-enhanced", - AopUtils.isCglibProxyClassName(factory.getBeanDefinition("abstract").getBeanClassName())); - assertTrue("concrete configuration should be CGLIB-enhanced", - AopUtils.isCglibProxyClassName(factory.getBeanDefinition("concrete").getBeanClassName())); - - assertFalse("abstract configuration's @Bean method should not be registered", - factory.containsBeanDefinition("foo1")); - assertTrue("concrete configuration's @Bean method should be registered", - factory.containsBeanDefinition("foo2")); - } -} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/BeanAnnotationAttributePropagationTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/BeanAnnotationAttributePropagationTests.java index 9f772c91eb5..1eddf67a4f8 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/BeanAnnotationAttributePropagationTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/BeanAnnotationAttributePropagationTests.java @@ -28,7 +28,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Primary; - +import org.springframework.context.annotation.DependsOn; /** * Unit tests proving that the various attributes available via the {@link Bean} @@ -67,7 +67,7 @@ public class BeanAnnotationAttributePropagationTests { @Test public void dependsOnMetadataIsPropagated() { @Configuration class Config { - @Bean(dependsOn={"bar", "baz"}) Object foo() { return null; } + @Bean() @DependsOn({"bar", "baz"}) Object foo() { return null; } } assertArrayEquals("dependsOn metadata was not propagated", @@ -149,8 +149,8 @@ public class BeanAnnotationAttributePropagationTests { private AbstractBeanDefinition beanDef(Class configClass) { DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); factory.registerBeanDefinition("config", new RootBeanDefinition(configClass)); - new ConfigurationClassPostProcessor().postProcessBeanFactory(factory); - + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(factory); return (AbstractBeanDefinition) factory.getBeanDefinition("foo"); } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java index 171366e1fae..cd8f40f00ab 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java @@ -16,27 +16,24 @@ package org.springframework.context.annotation.configuration; -import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; -import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; - import org.junit.Test; +import test.beans.ITestBean; +import test.beans.TestBean; + import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.StandardScopes; -import test.beans.ITestBean; -import test.beans.TestBean; - - /** * Miscellaneous system tests covering {@link Bean} naming, aliases, scoping and error * handling within {@link Configuration} class definitions. @@ -51,27 +48,20 @@ public class ConfigurationClassProcessingTests { * post-processes the factory using JavaConfig's {@link ConfigurationClassPostProcessor}. * When complete, the factory is ready to service requests for any {@link Bean} methods * declared by configClasses. - * - * @param configClasses the {@link Configuration} classes under test. may be an empty - * list. - * - * @return fully initialized and post-processed {@link BeanFactory} */ - private static BeanFactory initBeanFactory(Class... configClasses) { + private BeanFactory initBeanFactory(Class... configClasses) { DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); - for (Class configClass : configClasses) { String configBeanName = configClass.getName(); - factory.registerBeanDefinition(configBeanName, rootBeanDefinition(configClass).getBeanDefinition()); + factory.registerBeanDefinition(configBeanName, new RootBeanDefinition(configClass)); } - - new ConfigurationClassPostProcessor().postProcessBeanFactory(factory); - + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(factory); factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor()); - return factory; } + @Test public void customBeanNameIsRespected() { BeanFactory factory = initBeanFactory(ConfigWithBeanWithCustomName.class); @@ -132,20 +122,10 @@ public class ConfigurationClassProcessingTests { @Test public void simplestPossibleConfiguration() { BeanFactory factory = initBeanFactory(SimplestPossibleConfig.class); - String stringBean = factory.getBean("stringBean", String.class); - - assertThat(stringBean, equalTo("foo")); + assertEquals(stringBean, "foo"); } - @Configuration - static class SimplestPossibleConfig { - public @Bean String stringBean() { - return "foo"; - } - } - - @Test public void configurationWithPrototypeScopedBeans() { BeanFactory factory = initBeanFactory(ConfigWithPrototypeBean.class); @@ -154,12 +134,22 @@ public class ConfigurationClassProcessingTests { ITestBean bar = factory.getBean("bar", ITestBean.class); ITestBean baz = factory.getBean("baz", ITestBean.class); - assertThat(foo.getSpouse(), sameInstance(bar)); - assertThat(bar.getSpouse(), not(sameInstance(baz))); + assertSame(foo.getSpouse(), bar); + assertNotSame(bar.getSpouse(), baz); + } + + + @Configuration + static class SimplestPossibleConfig { + public @Bean String stringBean() { + return "foo"; + } } + @Configuration static class ConfigWithPrototypeBean { + public @Bean TestBean foo() { TestBean foo = new TestBean("foo"); foo.setSpouse(bar()); diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java index 78fe0f3ee92..01e4bf859ad 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportTests.java @@ -41,9 +41,11 @@ public class ImportTests { private DefaultListableBeanFactory processConfigurationClasses(Class... classes) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - for (Class clazz : classes) + for (Class clazz : classes) { beanFactory.registerBeanDefinition(clazz.getSimpleName(), new RootBeanDefinition(clazz)); - new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + } + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(beanFactory); return beanFactory; } @@ -137,7 +139,8 @@ public class ImportTests { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerBeanDefinition("config", new RootBeanDefinition( WithMultipleArgumentsThatWillCauseDuplication.class)); - new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(beanFactory); assertThat(beanFactory.getBeanDefinitionCount(), equalTo(4)); assertThat(beanFactory.getBean("foo", ITestBean.class).getName(), equalTo("foo2")); } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/PolymorphicConfigurationTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/PolymorphicConfigurationTests.java index e6a7f996e82..2fefd1bb8eb 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/PolymorphicConfigurationTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/PolymorphicConfigurationTests.java @@ -40,18 +40,23 @@ public class PolymorphicConfigurationTests { public void subclassNeedNotDeclareConfigurationAnnotation() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerBeanDefinition("config", new RootBeanDefinition(Config.class)); - new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); - + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(beanFactory); beanFactory.getBean("testBean", TestBean.class); } + @Configuration static class SuperConfig { + @Bean public TestBean testBean() { return new TestBean(); } } - static class Config extends SuperConfig { } + + static class Config extends SuperConfig { + } + } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java index 28c5cc0b4ad..0fb107db0b5 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ScopingTests.java @@ -13,21 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.context.annotation.configuration; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; +package org.springframework.context.annotation.configuration; import java.util.HashMap; import java.util.Map; +import static org.hamcrest.CoreMatchers.*; import org.junit.After; +import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; +import test.beans.ITestBean; +import test.beans.TestBean; + import org.springframework.aop.scope.ScopedObject; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; +import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -37,17 +40,12 @@ import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.context.annotation.StandardScopes; import org.springframework.context.support.GenericApplicationContext; -import test.beans.ITestBean; -import test.beans.TestBean; - - - /** * Tests that scopes are properly supported by using a custom Scope implementations * and scoped proxy {@link Bean} declarations. * - * @author Costin Leau - * @author Chris Beams + * @author Costin Leau + * @author Chris Beams */ public class ScopingTests { @@ -82,19 +80,16 @@ public class ScopingTests { return ctx; } - @Test public void testScopeOnClasses() throws Exception { genericTestScope("scopedClass"); } - @Test public void testScopeOnInterfaces() throws Exception { genericTestScope("scopedInterface"); } - @Test public void testSameScopeOnDifferentBeans() throws Exception { Object beanAInScope = ctx.getBean("scopedClass"); @@ -112,22 +107,8 @@ public class ScopingTests { assertNotSame(newBeanBInScope, beanBInScope); } - - @Test - public void testScopedProxyOnSingletonBeanMethod() throws Exception { - // should throw - scoped proxies should not be applied on singleton/prototype beans - try { - createContext(null, InvalidProxyOnPredefinedScopesConfiguration.class); - fail("exception expected"); - } catch (BeanDefinitionParsingException ex) { - assertTrue(ex.getMessage().contains("scoped proxies cannot be created for singleton/prototype beans")); - } - } - - @Test public void testRawScopes() throws Exception { - String beanName = "scopedProxyInterface"; // get hidden bean @@ -368,71 +349,56 @@ public class ScopingTests { } } -} - - -/** - * Simple scope implementation which creates object based on a flag. - * - * @author Costin Leau - * @author Chris Beams - */ -class CustomScope implements org.springframework.beans.factory.config.Scope { - public boolean createNewScope = true; - - private Map beans = new HashMap(); - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.config.Scope#get(java.lang.String, - * org.springframework.beans.factory.ObjectFactory) + /** + * Simple scope implementation which creates object based on a flag. + * @author Costin Leau + * @author Chris Beams */ - public Object get(String name, ObjectFactory objectFactory) { - if (createNewScope) { - beans.clear(); - // reset the flag back - createNewScope = false; + static class CustomScope implements org.springframework.beans.factory.config.Scope { + + public boolean createNewScope = true; + + private Map beans = new HashMap(); + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.Scope#get(java.lang.String, + * org.springframework.beans.factory.ObjectFactory) + */ + public Object get(String name, ObjectFactory objectFactory) { + if (createNewScope) { + beans.clear(); + // reset the flag back + createNewScope = false; + } + + Object bean = beans.get(name); + // if a new object is requested or none exists under the current + // name, create one + if (bean == null) { + beans.put(name, objectFactory.getObject()); + } + + return beans.get(name); } - Object bean = beans.get(name); - // if a new object is requested or none exists under the current - // name, create one - if (bean == null) { - beans.put(name, objectFactory.getObject()); + public String getConversationId() { + return null; } - return beans.get(name); - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.config.Scope#getConversationId() - */ - public String getConversationId() { - return null; - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String, - * java.lang.Runnable) - */ - public void registerDestructionCallback(String name, Runnable callback) { - // do nothing - } + public void registerDestructionCallback(String name, Runnable callback) { + // do nothing + } - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.config.Scope#remove(java.lang.String) - */ - public Object remove(String name) { - return beans.remove(name); - } + public Object remove(String name) { + return beans.remove(name); + } - public Object resolveContextualObject(String key) { - // TODO Auto-generated method stub - return null; + public Object resolveContextualObject(String key) { + // TODO Auto-generated method stub + return null; + } } } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/customNameGeneratorTests.xml b/org.springframework.context/src/test/java/org/springframework/context/annotation/customNameGeneratorTests.xml index 08d511cdac7..1451dae93d2 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/customNameGeneratorTests.xml +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/customNameGeneratorTests.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> - diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation4/FactoryMethodComponent.java b/org.springframework.context/src/test/java/org/springframework/context/annotation4/FactoryMethodComponent.java index bc29fa2b559..a5be7247426 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation4/FactoryMethodComponent.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation4/FactoryMethodComponent.java @@ -17,63 +17,52 @@ package org.springframework.context.annotation4; import org.springframework.beans.TestBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.FactoryMethod; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.ScopedProxy; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.BeanAge; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.stereotype.Component; /** - * Class used to test the functionality of @FactoryMethod bean definitions declared inside - * a Spring @Component class. - * + * Class used to test the functionality of factory method bean definitions + * declared inside a Spring component class. + * * @author Mark Pollack + * @author Juergen Hoeller */ @Component -public class FactoryMethodComponent { +public final class FactoryMethodComponent { - private static TestBean staticTestBean = new TestBean("staticInstance",1); - - @Autowired @Qualifier("public") - public TestBean autowiredTestBean; - - private static int i; + private int i; - @FactoryMethod @Qualifier("static") - public static TestBean staticInstance() - { - return staticTestBean; - } - - public static TestBean nullInstance() - { + public static TestBean nullInstance() { return null; } - - @FactoryMethod @Qualifier("public") - public TestBean getPublicInstance() { + + @Bean @Qualifier("public") + public TestBean publicInstance() { return new TestBean("publicInstance"); } - @FactoryMethod @BeanAge(1) - protected TestBean getProtectedInstance() { - return new TestBean("protectedInstance", 1); + @Bean @BeanAge(1) + protected TestBean protectedInstance(@Qualifier("public") TestBean spouse, @Value("#{privateInstance.age}") String country) { + TestBean tb = new TestBean("protectedInstance", 1); + tb.setSpouse(tb); + tb.setCountry(country); + return tb; } - - @FactoryMethod @Scope("prototype") - private TestBean getPrivateInstance() { + + @Bean @Scope("prototype") + private TestBean privateInstance() { return new TestBean("privateInstance", i++); } - - @FactoryMethod @Scope("request") @ScopedProxy - public TestBean requestScopedInstance() - { - TestBean testBean = new TestBean("requestScopedInstance", 3); - return testBean; + + @Bean @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) + public TestBean requestScopedInstance() { + return new TestBean("requestScopedInstance", 3); } - - //TODO method for test that fails if use @ScopedProxy with singleton scope. - + } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation4/SimpleBean.java b/org.springframework.context/src/test/java/org/springframework/context/annotation4/SimpleBean.java index ddd52aafbf5..e6920157d4a 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation4/SimpleBean.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation4/SimpleBean.java @@ -14,24 +14,23 @@ * limitations under the License. */ - package org.springframework.context.annotation4; import org.springframework.beans.TestBean; -import org.springframework.beans.factory.annotation.FactoryMethod; +import org.springframework.context.annotation.Bean; /** * Class to test that @FactoryMethods are detected only when inside a class with an @Component * class annotation. - * + * * @author Mark Pollack */ public class SimpleBean { - - // This should *not* recognized as a @FactoryMethod since it does not reside inside an @Component - @FactoryMethod + // This should *not* recognized as a bean since it does not reside inside an @Component + @Bean public TestBean getPublicInstance() { return new TestBean("publicInstance"); } + } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/FactoryMethod.java b/org.springframework.context/src/test/java/org/springframework/context/annotation5/OtherFooDao.java similarity index 51% rename from org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/FactoryMethod.java rename to org.springframework.context/src/test/java/org/springframework/context/annotation5/OtherFooDao.java index bf37f999a51..c9972ff9d36 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/FactoryMethod.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation5/OtherFooDao.java @@ -1,36 +1,36 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.beans.factory.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Marks a method as being a factory-method of the class. Use during component scanning - * to create a bean definition that has factory-bean and factory-method metadata - * - * @author Mark Pollack - * @since 3.0 - * @see RequiredAnnotationBeanPostProcessor - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD}) -public @interface FactoryMethod { - -} +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation5; + +import example.scannable.FooDao; + +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Repository; + +/** + * @author Juergen Hoeller + */ +@Repository +@Primary @Lazy +public class OtherFooDao implements FooDao { + + public String findFoo(int id) { + return "other"; + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java b/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java index 69ccfc2f958..e202ebd7538 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java @@ -209,7 +209,7 @@ public class ApplicationContextExpressionTests { @Value("${code} #{systemProperties.country}") public String country; - @Qualifier("original") + @Autowired @Qualifier("original") public TestBean tb; } @@ -287,7 +287,7 @@ public class ApplicationContextExpressionTests { this.country = country; } - @Qualifier("original") + @Autowired @Qualifier("original") public void setTb(TestBean tb) { this.tb = tb; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 9333f657003..eaec9f41447 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -138,7 +138,7 @@ public abstract class AnnotationUtils { } } Class superClass = clazz.getSuperclass(); - if (superClass == null || superClass == Object.class) { + if (superClass == null || superClass.equals(Object.class)) { return null; } return findAnnotation(superClass, annotationType); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java index d02062f28a9..4db70031c35 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/AnnotationMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,8 +70,8 @@ public interface AnnotationMetadata extends ClassMetadata { * annotation is defined. */ Map getAnnotationAttributes(String annotationType); - - + + // TODO return null would be more consistent with other methods if no match is found /** diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java index 3a1e85e9909..58c89913086 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java @@ -1,8 +1,28 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.core.type; import java.util.Map; import java.util.Set; +/** + * @author Mark Pollack + * @since 3.0 + */ public interface MethodMetadata { int getModifiers(); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java index 0485a191586..043653b8b4c 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,16 +43,16 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements public Set getAnnotationTypes() { Set types = new HashSet(); Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (int i = 0; i < anns.length; i++) { - types.add(anns[i].annotationType().getName()); + for (Annotation ann : anns) { + types.add(ann.annotationType().getName()); } return types; } public boolean hasAnnotation(String annotationType) { Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (int i = 0; i < anns.length; i++) { - if (anns[i].annotationType().getName().equals(annotationType)) { + for (Annotation ann : anns) { + if (ann.annotationType().getName().equals(annotationType)) { return true; } } @@ -61,10 +61,10 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements public Set getMetaAnnotationTypes(String annotationType) { Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (int i = 0; i < anns.length; i++) { - if (anns[i].annotationType().getName().equals(annotationType)) { + for (Annotation ann : anns) { + if (ann.annotationType().getName().equals(annotationType)) { Set types = new HashSet(); - Annotation[] metaAnns = anns[i].annotationType().getAnnotations(); + Annotation[] metaAnns = ann.annotationType().getAnnotations(); for (Annotation meta : metaAnns) { types.add(meta.annotationType().getName()); } @@ -76,8 +76,8 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements public boolean hasMetaAnnotation(String annotationType) { Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (int i = 0; i < anns.length; i++) { - Annotation[] metaAnns = anns[i].annotationType().getAnnotations(); + for (Annotation ann : anns) { + Annotation[] metaAnns = ann.annotationType().getAnnotations(); for (Annotation meta : metaAnns) { if (meta.annotationType().getName().equals(annotationType)) { return true; @@ -89,8 +89,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements public Map getAnnotationAttributes(String annotationType) { Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (int i = 0; i < anns.length; i++) { - Annotation ann = anns[i]; + for (Annotation ann : anns) { if (ann.annotationType().getName().equals(annotationType)) { return AnnotationUtils.getAnnotationAttributes(ann); } @@ -101,11 +100,9 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements public Set getAnnotatedMethods(String annotationType) { Method[] methods = getIntrospectedClass().getMethods(); Set annotatedMethods = new LinkedHashSet(); - for (int i = 0; i < methods.length; i++) { - Method method = methods[i]; + for (Method method : methods) { Annotation[] methodAnnotations = method.getAnnotations(); - for (int j = 0; j < methodAnnotations.length; j++) { - Annotation ann = methodAnnotations[j]; + for (Annotation ann : methodAnnotations) { if (ann.annotationType().getName().equals(annotationType)) { MethodMetadata mm = new StandardMethodMetadata(method); annotatedMethods.add(mm); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java index 50af7421004..4a005effd46 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.core.type; import java.lang.annotation.Annotation; @@ -9,12 +25,18 @@ import java.util.Map; import java.util.Set; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; +/** + * @author Mark Pollack + * @since 3.0 + */ public class StandardMethodMetadata implements MethodMetadata { private final Method introspectedMethod; public StandardMethodMetadata(Method method) { + Assert.notNull(method, "Method must not be null"); introspectedMethod = method; } @@ -25,8 +47,7 @@ public class StandardMethodMetadata implements MethodMetadata { public Map getAnnotationAttributes(String annotationType) { Annotation[] anns = getIntrospectedMethod().getAnnotations(); - for (int i = 0; i < anns.length; i++) { - Annotation ann = anns[i]; + for (Annotation ann : anns) { if (ann.annotationType().getName().equals(annotationType)) { return AnnotationUtils.getAnnotationAttributes(ann); } @@ -37,14 +58,11 @@ public class StandardMethodMetadata implements MethodMetadata { public Set getAnnotationTypes() { Set types = new HashSet(); Annotation[] anns = getIntrospectedMethod().getAnnotations(); - for (int i = 0; i < anns.length; i++) { - types.add(anns[i].annotationType().getName()); + for (Annotation ann : anns) { + types.add(ann.annotationType().getName()); } return types; } - - - public String getMethodName() { return introspectedMethod.getName(); @@ -56,8 +74,8 @@ public class StandardMethodMetadata implements MethodMetadata { public boolean hasAnnotation(String annotationType) { Annotation[] anns = getIntrospectedMethod().getAnnotations(); - for (int i = 0; i < anns.length; i++) { - if (anns[i].annotationType().getName().equals(annotationType)) { + for (Annotation ann : anns) { + if (ann.annotationType().getName().equals(annotationType)) { return true; } } @@ -70,10 +88,10 @@ public class StandardMethodMetadata implements MethodMetadata { public Set getMetaAnnotationTypes(String annotationType) { Annotation[] anns = getIntrospectedMethod().getAnnotations(); - for (int i = 0; i < anns.length; i++) { - if (anns[i].annotationType().getName().equals(annotationType)) { + for (Annotation ann : anns) { + if (ann.annotationType().getName().equals(annotationType)) { Set types = new HashSet(); - Annotation[] metaAnns = anns[i].annotationType().getAnnotations(); + Annotation[] metaAnns = ann.annotationType().getAnnotations(); for (Annotation meta : metaAnns) { types.add(meta.annotationType().getName()); } @@ -83,13 +101,11 @@ public class StandardMethodMetadata implements MethodMetadata { return null; } - public boolean hasMetaAnnotation(String metaAnnotationType) { - //TODO can refactor into shared (utility) method with StandardAnnotationMetadata Annotation[] anns = getIntrospectedMethod().getAnnotations(); - for (int i = 0; i < anns.length; i++) { - Annotation[] metaAnns = anns[i].annotationType().getAnnotations(); + for (Annotation ann : anns) { + Annotation[] metaAnns = ann.annotationType().getAnnotations(); for (Annotation meta : metaAnns) { if (meta.annotationType().getName().equals(metaAnnotationType)) { return true; @@ -99,8 +115,7 @@ public class StandardMethodMetadata implements MethodMetadata { return false; } - public Set getAnnotationTypesWithMetaAnnotation( - String qualifierClassName) { + public Set getAnnotationTypesWithMetaAnnotation(String qualifierClassName) { // TODO Auto-generated method stub return null; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java index 9f643ad2256..30addb57175 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java @@ -17,6 +17,7 @@ package org.springframework.core.type.classreading; import java.lang.annotation.Annotation; +import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collection; @@ -32,6 +33,7 @@ import org.springframework.asm.Type; import org.springframework.asm.commons.EmptyVisitor; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; +import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; /** @@ -59,23 +61,18 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple } - @Override - public MethodVisitor visitMethod(int access, String name, String desc, - String signature, String[] exceptions) { + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodMetadataReadingVisitor md = new MethodMetadataReadingVisitor(classLoader, name, access); methodMetadataSet.add(md); return md; } - - @Override public AnnotationVisitor visitAnnotation(final String desc, boolean visible) { final String className = Type.getType(desc).getClassName(); final Map attributes = new LinkedHashMap(); - return new EmptyVisitor() { - @Override + return new AnnotationVisitor() { public void visit(String name, Object value) { // Explicitly defined annotation attribute value. Object valueToUse = value; @@ -89,7 +86,6 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple } attributes.put(name, valueToUse); } - @Override public void visitEnum(String name, String desc, String value) { Object valueToUse = value; try { @@ -104,7 +100,36 @@ class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor imple } attributes.put(name, valueToUse); } - @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { + return new EmptyVisitor(); + } + public AnnotationVisitor visitArray(final String attrName) { + return new AnnotationVisitor() { + public void visit(String name, Object value) { + Object newValue = value; + Object existingValue = attributes.get(attrName); + if (existingValue != null) { + newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue); + } + else { + Object[] newArray = (Object[]) Array.newInstance(newValue.getClass(), 1); + newArray[0] = newValue; + newValue = newArray; + } + attributes.put(attrName, newValue); + } + public void visitEnum(String name, String desc, String value) { + } + public AnnotationVisitor visitAnnotation(String name, String desc) { + return new EmptyVisitor(); + } + public AnnotationVisitor visitArray(String name) { + return new EmptyVisitor(); + } + public void visitEnd() { + } + }; + } public void visitEnd() { try { Class annotationClass = classLoader.loadClass(className); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java index f10786d4c4c..3766215a1a3 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java @@ -16,7 +16,11 @@ package org.springframework.core.type.classreading; -import org.springframework.asm.ClassAdapter; +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.Attribute; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.FieldVisitor; +import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; import org.springframework.asm.commons.EmptyVisitor; import org.springframework.core.type.ClassMetadata; @@ -33,7 +37,7 @@ import org.springframework.util.ClassUtils; * @author Ramnivas Laddad * @since 2.5 */ -class ClassMetadataReadingVisitor extends ClassAdapter implements ClassMetadata { +class ClassMetadataReadingVisitor implements ClassVisitor, ClassMetadata { private String className; @@ -49,12 +53,7 @@ class ClassMetadataReadingVisitor extends ClassAdapter implements ClassMetadata private String[] interfaces; - public ClassMetadataReadingVisitor() - { - super(new EmptyVisitor()); - } - - @Override + public void visit(int version, int access, String name, String signature, String supername, String[] interfaces) { this.className = ClassUtils.convertResourcePathToClassName(name); this.isInterface = ((access & Opcodes.ACC_INTERFACE) != 0); @@ -68,12 +67,10 @@ class ClassMetadataReadingVisitor extends ClassAdapter implements ClassMetadata } } - @Override public void visitOuterClass(String owner, String name, String desc) { this.enclosingClassName = ClassUtils.convertResourcePathToClassName(owner); } - @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { if (outerName != null && this.className.equals(ClassUtils.convertResourcePathToClassName(name))) { this.enclosingClassName = ClassUtils.convertResourcePathToClassName(outerName); @@ -81,6 +78,33 @@ class ClassMetadataReadingVisitor extends ClassAdapter implements ClassMetadata } } + public void visitSource(String source, String debug) { + // no-op + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + // no-op + return new EmptyVisitor(); + } + + public void visitAttribute(Attribute attr) { + // no-op + } + + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + // no-op + return new EmptyVisitor(); + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + // no-op + return new EmptyVisitor(); + } + + public void visitEnd() { + // no-op + } + public String getClassName() { return this.className; diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java index 050c9dc8b2c..a3f38e9c6b7 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java @@ -17,10 +17,10 @@ package org.springframework.core.type.classreading; import java.lang.annotation.Annotation; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -32,18 +32,27 @@ import org.springframework.asm.Opcodes; import org.springframework.asm.Type; import org.springframework.asm.commons.EmptyVisitor; import org.springframework.core.type.MethodMetadata; +import org.springframework.util.ReflectionUtils; -public class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata { - - private final Map> attributesMap = new LinkedHashMap>(); - - private final Map> metaAnnotationMap = new LinkedHashMap>(); +/** + * @author Mark Pollack + * @since 3.0 + */ +class MethodMetadataReadingVisitor extends MethodAdapter implements MethodMetadata { private ClassLoader classLoader; + private String name; + private int access; + private boolean isStatic; + private final Map> attributesMap = new LinkedHashMap>(); + + private final Map> metaAnnotationMap = new LinkedHashMap>(); + + public MethodMetadataReadingVisitor(ClassLoader classLoader, String name, int access) { super(new EmptyVisitor()); this.classLoader = classLoader; @@ -52,6 +61,7 @@ public class MethodMetadataReadingVisitor extends MethodAdapter implements Metho this.isStatic = ((access & Opcodes.ACC_STATIC) != 0); } + public Map getAnnotationAttributes(String annotationType) { return this.attributesMap.get(annotationType); } @@ -87,29 +97,22 @@ public class MethodMetadataReadingVisitor extends MethodAdapter implements Metho } public boolean isStatic() { - return isStatic; + return this.isStatic; } - public Set getAnnotationTypesWithMetaAnnotation(String metaAnnotationType) { - - ///metaAnnotationMap.put(className, metaAnnotationTypeNames); Set annotationTypes = new LinkedHashSet(); - Set< Map.Entry> > metaValues = metaAnnotationMap.entrySet(); - Iterator> > metaIterator = metaValues.iterator(); - while (metaIterator.hasNext()) - { - Map.Entry> entry = metaIterator.next(); + for (Map.Entry> entry : metaAnnotationMap.entrySet()) { String attributeType = entry.getKey(); Set metaAttributes = entry.getValue(); - if (metaAttributes.contains(metaAnnotationType)) - { + if (metaAttributes.contains(metaAnnotationType)) { annotationTypes.add(attributeType); } } return annotationTypes; } + @Override public AnnotationVisitor visitAnnotation(final String desc, boolean visible) { final String className = Type.getType(desc).getClassName(); @@ -121,13 +124,27 @@ public class MethodMetadataReadingVisitor extends MethodAdapter implements Metho attributes.put(name, value); } @Override + public void visitEnum(String name, String desc, String value) { + Object valueToUse = value; + try { + Class enumType = classLoader.loadClass(Type.getType(desc).getClassName()); + Field enumConstant = ReflectionUtils.findField(enumType, value); + if (enumConstant != null) { + valueToUse = enumConstant.get(null); + } + } + catch (Exception ex) { + // Class not found - can't resolve class reference in annotation attribute. + } + attributes.put(name, valueToUse); + } + @Override public void visitEnd() { try { Class annotationClass = classLoader.loadClass(className); // Check declared default values of attributes in the annotation type. Method[] annotationAttributes = annotationClass.getMethods(); - for (int i = 0; i < annotationAttributes.length; i++) { - Method annotationAttribute = annotationAttributes[i]; + for (Method annotationAttribute : annotationAttributes) { String attributeName = annotationAttribute.getName(); Object defaultValue = annotationAttribute.getDefaultValue(); if (defaultValue != null && !attributes.containsKey(attributeName)) { @@ -150,5 +167,4 @@ public class MethodMetadataReadingVisitor extends MethodAdapter implements Metho }; } - }