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 bda5d2b7fc..91265957f6 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 @@ -192,8 +192,8 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan } if (qualifier == null) { Annotation targetAnnotation = null; - if (bd.getFactoryMethodForIntrospection() != null) { - targetAnnotation = bd.getFactoryMethodForIntrospection().getAnnotation(type); + if (bd.getResolvedFactoryMethod() != null) { + targetAnnotation = bd.getResolvedFactoryMethod().getAnnotation(type); } if (targetAnnotation == null && bd.hasBeanClass()) { // look for matching annotation on the target class diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 78719ea041..e1c5db7069 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -1025,7 +1025,12 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp if (mbd == null) { if (bd.getParentName() == null) { // Use copy of given root bean definition. - mbd = new RootBeanDefinition(bd); + if (bd instanceof RootBeanDefinition) { + mbd = ((RootBeanDefinition) bd).cloneBeanDefinition(); + } + else { + mbd = new RootBeanDefinition(bd); + } } else { // Child bean definition: needs to be merged with parent. 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 36ca8e2dfc..37c7565ce0 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 @@ -21,6 +21,7 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -43,10 +44,10 @@ import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; +import org.springframework.util.ClassUtils; 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. @@ -247,6 +248,36 @@ class ConstructorResolver { } } + /** + * Resolve the factory method in the specified bean definition, if possible. + * {@link RootBeanDefinition#getResolvedFactoryMethod()} can be checked for the result. + * @param mbd the bean definition to check + */ + public void resolveFactoryMethodIfPossible(RootBeanDefinition mbd) { + Class factoryClass; + if (mbd.getFactoryBeanName() != null) { + factoryClass = this.beanFactory.getType(mbd.getFactoryBeanName()); + } + else { + factoryClass = mbd.getBeanClass(); + } + factoryClass = ClassUtils.getUserClass(factoryClass); + Method[] candidates = ReflectionUtils.getAllDeclaredMethods(factoryClass); + Method uniqueCandidate = null; + for (Method candidate : candidates) { + if (mbd.isFactoryMethod(candidate)) { + if (uniqueCandidate == null) { + uniqueCandidate = candidate; + } + else if (!Arrays.equals(uniqueCandidate.getParameterTypes(), candidate.getParameterTypes())) { + uniqueCandidate = null; + break; + } + } + } + mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate; + } + /** * Instantiate the bean using a named factory method. The method may be static, if the * bean definition parameter specifies a class, rather than a "factory-bean", or @@ -306,13 +337,13 @@ class ConstructorResolver { if (factoryMethodToUse != null) { // Found a cached factory method... argsToUse = mbd.resolvedConstructorArguments; - if (argsToUse == null) { + if (argsToUse == null && mbd.preparedConstructorArguments != null) { argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse); } } } - if (factoryMethodToUse == null) { + if (factoryMethodToUse == null || argsToUse == null) { // Need to determine the factory method... // Try all methods with this name to see if they match the given arguments. factoryClass = ClassUtils.getUserClass(factoryClass); @@ -320,7 +351,8 @@ class ConstructorResolver { List candidateSet = new ArrayList(); for (Method candidate : rawCandidates) { if (Modifier.isStatic(candidate.getModifiers()) == isStatic && - candidate.getName().equals(mbd.getFactoryMethodName())) { + candidate.getName().equals(mbd.getFactoryMethodName()) && + mbd.isFactoryMethod(candidate)) { candidateSet.add(candidate); } } 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 6dcd240e64..11072dabc4 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,9 +17,7 @@ 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; @@ -49,9 +47,7 @@ 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; /** @@ -432,31 +428,8 @@ 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; + if (mbd.isFactoryMethodUnique && mbd.resolvedConstructorOrFactoryMethod == null) { + new ConstructorResolver(this, null, null, null).resolveFactoryMethodIfPossible(mbd); } 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 a3a146ae1d..d8794a6b80 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 @@ -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. @@ -25,6 +25,7 @@ import java.util.Set; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.util.Assert; /** * A root bean definition represents the merged bean definition that backs @@ -52,7 +53,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { private final Set externallyManagedDestroyMethods = Collections.synchronizedSet(new HashSet()); - volatile Method factoryMethodForIntrospection; + boolean isFactoryMethodUnique; /** Package-visible field for caching the resolved constructor or factory method */ volatile Object resolvedConstructorOrFactoryMethod; @@ -198,7 +199,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { * @param original the original bean definition to copy from */ public RootBeanDefinition(RootBeanDefinition original) { - super((BeanDefinition) original); + this((BeanDefinition) original); } /** @@ -208,6 +209,9 @@ public class RootBeanDefinition extends AbstractBeanDefinition { */ RootBeanDefinition(BeanDefinition original) { super(original); + if (original instanceof RootBeanDefinition) { + this.isFactoryMethodUnique = ((RootBeanDefinition) original).isFactoryMethodUnique; + } } @@ -221,8 +225,26 @@ public class RootBeanDefinition extends AbstractBeanDefinition { } } - public Method getFactoryMethodForIntrospection() { - return this.factoryMethodForIntrospection; + /** + * Specify a factory method name that refers to a non-overloaded method. + */ + public void setUniqueFactoryMethodName(String name) { + Assert.hasText(name, "Factory method name must not be empty"); + setFactoryMethodName(name); + this.isFactoryMethodUnique = true; + } + + public boolean isFactoryMethod(Method candidate) { + return (candidate != null && candidate.getName().equals(getFactoryMethodName())); + } + + /** + * Return the resolved factory method as a Java Method object, if available. + * @return the factory method, or null if not found or not resolved yet + */ + public Method getResolvedFactoryMethod() { + Object candidate = this.resolvedConstructorOrFactoryMethod; + return (candidate instanceof Method ? (Method) candidate : null); } @@ -252,7 +274,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { @Override - public AbstractBeanDefinition cloneBeanDefinition() { + public RootBeanDefinition cloneBeanDefinition() { return new RootBeanDefinition(this); } 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 520cbacc06..1f7d838e26 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 @@ -19,6 +19,9 @@ package org.springframework.context.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; import org.springframework.beans.BeanMetadataElement; @@ -46,26 +49,21 @@ final class ConfigurationClass implements BeanMetadataElement { private String name; - private transient Object source; + private ConfigurationClass declaringClass; + + private Object source; private String beanName; private int modifiers; - private Set annotations = new HashSet(); + private final Set annotations = new HashSet(); - private Set methods = new HashSet(); + private final Set methods = new LinkedHashSet(); - private ConfigurationClass declaringClass; + private final Map overloadedMethodMap = new LinkedHashMap(); - /** - * Returns the fully-qualified name of this class. - */ - public String getName() { - return name; - } - /** * Sets the fully-qualified name of this class. */ @@ -73,6 +71,13 @@ final class ConfigurationClass implements BeanMetadataElement { this.name = className; } + /** + * Returns the fully-qualified name of this class. + */ + public String getName() { + return name; + } + /** * Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'. */ @@ -80,12 +85,12 @@ final class ConfigurationClass implements BeanMetadataElement { 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; + public void setDeclaringClass(ConfigurationClass configurationClass) { + this.declaringClass = configurationClass; + } + + public ConfigurationClass getDeclaringClass() { + return declaringClass; } /** @@ -96,6 +101,14 @@ final class ConfigurationClass implements BeanMetadataElement { this.source = source; } + /** + * Returns a resource path-formatted representation of the .java file that declares this + * class + */ + public Object getSource() { + return source; + } + public Location getLocation() { if (getName() == null) { throw new IllegalStateException("'name' property is null. Call setName() before calling getLocation()"); @@ -103,23 +116,23 @@ final class ConfigurationClass implements BeanMetadataElement { return new Location(new ClassPathResource(ClassUtils.convertClassNameToResourcePath(getName())), getSource()); } - public String getBeanName() { - return beanName; - } - public void setBeanName(String beanName) { this.beanName = beanName; } - public int getModifiers() { - return modifiers; + public String getBeanName() { + return beanName; } public void setModifiers(int modifiers) { Assert.isTrue(modifiers >= 0, "modifiers must be non-negative"); this.modifiers = modifiers; } - + + public int getModifiers() { + return modifiers; + } + public void addAnnotation(Annotation annotation) { this.annotations.add(annotation); } @@ -153,26 +166,34 @@ final class ConfigurationClass implements BeanMetadataElement { return anno; } + public ConfigurationClass addMethod(ConfigurationClassMethod method) { + method.setDeclaringClass(this); + methods.add(method); + Integer count = overloadedMethodMap.get(method.getName()); + if (count != null) { + overloadedMethodMap.put(method.getName(), count + 1); + } + else { + overloadedMethodMap.put(method.getName(), 1); + } + return this; + } + public Set getBeanMethods() { return methods; } - public ConfigurationClass addMethod(ConfigurationClassMethod method) { - method.setDeclaringClass(this); - methods.add(method); - return this; - } - - public ConfigurationClass getDeclaringClass() { - return declaringClass; - } - - public void setDeclaringClass(ConfigurationClass configurationClass) { - this.declaringClass = configurationClass; - } - public void validate(ProblemReporter problemReporter) { - // a configuration class may not be final (CGLIB limitation) + // No overloading of factory methods allowed + for (Map.Entry entry : overloadedMethodMap.entrySet()) { + String methodName = entry.getKey(); + int count = entry.getValue(); + if (count > 1) { + problemReporter.error(new OverloadedMethodProblem(methodName, count)); + } + } + + // A configuration class may not be final (CGLIB limitation) if (getAnnotation(Configuration.class) != null) { if (Modifier.isFinal(modifiers)) { problemReporter.error(new FinalConfigurationProblem()); @@ -188,10 +209,21 @@ final class ConfigurationClass implements BeanMetadataElement { private class FinalConfigurationProblem extends Problem { public FinalConfigurationProblem() { - super(String.format("@Configuration class [%s] may not be final. Remove the final modifier to continue.", + super(String.format("@Configuration class '%s' may not be final. Remove the final modifier to continue.", getSimpleName()), ConfigurationClass.this.getLocation()); } - } + + /** Factory methods on configuration classes must not be overloaded. */ + private class OverloadedMethodProblem extends Problem { + + public OverloadedMethodProblem(String methodName, int count) { + super(String.format("@Configuration class '%s' has %s overloaded factory methods of name '%s'. " + + "Only one factory method of the same name allowed.", + getSimpleName(), count, methodName), ConfigurationClass.this.getLocation()); + } + } + + } 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 index df3787ccc8..a838750bba 100644 --- 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 @@ -17,10 +17,10 @@ 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.lang.reflect.Field; import java.util.ArrayList; +import java.util.List; import org.springframework.asm.AnnotationVisitor; import org.springframework.asm.Type; @@ -57,40 +57,47 @@ class ConfigurationClassAnnotationVisitor implements AnnotationVisitor { 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; + String className = ((Type) attribValue).getClassName(); + try { + Class classVal = classLoader.loadClass(className); + if (classVal != null) { + mutableAnno.setAttributeValue(attribName, classVal); + } + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Cannot resolve attribute type [" + className + "]", ex); } - mutableAnno.setAttributeValue(attribName, classVal); - return; } - - // otherwise, assume the value can be set literally - mutableAnno.setAttributeValue(attribName, attribValue); + else { + // 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; + String enumTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(enumTypeDescriptor); + try { + Class enumType = (Class) classLoader.loadClass(enumTypeName); + if (enumType == null) { + return; + } + Enum enumValue = Enum.valueOf(enumType, strEnumValue); + mutableAnno.setAttributeValue(attribName, enumValue); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Cannot resolve enum type [" + enumTypeName + "]", ex); } - 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) { + public AnnotationVisitor visitAnnotation(String attribName, String annoTypeDesc) { + Class annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); + if (annoClass == null) { return new EmptyVisitor(); } - ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoType, classLoader); + ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); try { Field attribute = mutableAnno.getClass().getField(attribName); attribute.set(mutableAnno, anno); @@ -123,14 +130,12 @@ class ConfigurationClassAnnotationVisitor implements AnnotationVisitor { 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); } @@ -139,12 +144,11 @@ class ConfigurationClassAnnotationVisitor implements AnnotationVisitor { } public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) { - String annoTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc); - Class annoType = ConfigurationClassReaderUtils.loadToolingSafeClass(annoTypeName, classLoader); - if (annoType == null) { + Class annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); + if (annoClass == null) { return new EmptyVisitor(); } - ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoType, classLoader); + ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader); values.add(anno); return new ConfigurationClassAnnotationVisitor(anno, classLoader); } 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 index afebf5feaa..151155df9c 100644 --- 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 @@ -16,6 +16,7 @@ package org.springframework.context.annotation; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -25,6 +26,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.BeanDefinitionReader; @@ -32,7 +34,6 @@ 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; @@ -105,7 +106,7 @@ class ConfigurationClassBeanDefinitionReader { RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(); ConfigurationClass configClass = method.getDeclaringClass(); beanDef.setFactoryBeanName(configClass.getBeanName()); - beanDef.setFactoryMethodName(method.getName()); + beanDef.setUniqueFactoryMethodName(method.getName()); beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); // consider name and any aliases @@ -197,11 +198,27 @@ class ConfigurationClassBeanDefinitionReader { /** * {@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). + * where it's necessary to determine whether the bean definition was created externally. */ @SuppressWarnings("serial") private class ConfigurationClassBeanDefinition extends RootBeanDefinition { + + public ConfigurationClassBeanDefinition() { + } + + private ConfigurationClassBeanDefinition(ConfigurationClassBeanDefinition original) { + super(original); + } + + @Override + public boolean isFactoryMethod(Method candidate) { + return (super.isFactoryMethod(candidate) && candidate.isAnnotationPresent(Bean.class)); + } + + @Override + public ConfigurationClassBeanDefinition cloneBeanDefinition() { + return new ConfigurationClassBeanDefinition(this); + } } } 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 475fb88af5..6fab47d19c 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,14 +16,14 @@ package org.springframework.context.annotation; +import java.io.IOException; 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; +import org.springframework.core.type.classreading.SimpleMetadataReader; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; /** * Parses a {@link Configuration} class definition, populating a configuration model. @@ -41,11 +41,11 @@ import org.springframework.util.ClassUtils; */ class ConfigurationClassParser { - private final Set model; + private final SimpleMetadataReaderFactory metadataReaderFactory; private final ProblemReporter problemReporter; - private final ClassLoader classLoader; + private final Set model; /** @@ -53,10 +53,10 @@ class ConfigurationClassParser { * configuration model. * @param model model to be populated by each successive call to {@link #parse} */ - public ConfigurationClassParser(ProblemReporter problemReporter, ClassLoader classLoader) { - this.model = new LinkedHashSet(); + public ConfigurationClassParser(SimpleMetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter) { + this.metadataReaderFactory = metadataReaderFactory; this.problemReporter = problemReporter; - this.classLoader = classLoader; + this.model = new LinkedHashSet(); } @@ -66,12 +66,11 @@ class ConfigurationClassParser { * @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 beanName) { - String resourcePath = ClassUtils.convertClassNameToResourcePath(className); - ClassReader configClassReader = ConfigurationClassReaderUtils.newAsmClassReader(ConfigurationClassReaderUtils.getClassAsStream(resourcePath, classLoader)); + public void parse(String className, String beanName) throws IOException { + SimpleMetadataReader reader = this.metadataReaderFactory.getMetadataReader(className); ConfigurationClass configClass = new ConfigurationClass(); configClass.setBeanName(beanName); - configClassReader.accept(new ConfigurationClassVisitor(configClass, model, problemReporter, classLoader), false); + reader.getClassReader().accept(new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory), false); model.add(configClass); } 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 0b0d15b432..40f715f20a 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 @@ -37,6 +37,7 @@ 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.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.stereotype.Component; @@ -81,6 +82,8 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + private SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); + /** * Override the default {@link ProblemReporter}. @@ -92,6 +95,7 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor public void setBeanClassLoader(ClassLoader beanClassLoader) { this.beanClassLoader = beanClassLoader; + this.metadataReaderFactory = new CachingMetadataReaderFactory(beanClassLoader); } public int getOrder() { @@ -132,9 +136,15 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor } // Populate a new configuration model by parsing each @Configuration classes - ConfigurationClassParser parser = new ConfigurationClassParser(this.problemReporter, this.beanClassLoader); + ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter); for (BeanDefinitionHolder holder : configBeanDefs) { - parser.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); + String beanClassName = holder.getBeanDefinition().getBeanClassName(); + try { + parser.parse(beanClassName, holder.getBeanName()); + } + catch (IOException ex) { + throw new BeanDefinitionStoreException("Failed to load bean class [" + beanClassName + "]", ex); + } } parser.validate(); @@ -208,11 +218,10 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor return false; } } - SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(this.beanClassLoader); String className = beanDef.getBeanClassName(); while (className != null && !(className.equals(Object.class.getName()))) { try { - MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className); + MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(className); AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); if (metadata.hasAnnotation(Configuration.class.getName())) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full"); 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 index 08f97f1da2..ecdb084741 100644 --- 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 @@ -16,8 +16,6 @@ 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; @@ -26,17 +24,11 @@ import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; 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.core.annotation.AnnotationUtils; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -48,8 +40,6 @@ import org.springframework.util.StringUtils; */ class ConfigurationClassReaderUtils { - private static final Log logger = LogFactory.getLog(ConfigurationClassReaderUtils.class); - /** * Convert a type descriptor to a classname suitable for classloading with * Class.forName(). @@ -92,94 +82,14 @@ class ConfigurationClassReaderUtils { 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) { + public static Class loadAnnotationType(String annoTypeDesc, ClassLoader classLoader) { + String annoTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc); try { - return (Class) classLoader.loadClass(fqClassName); + return (Class) classLoader.loadClass(annoTypeName); } 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; + throw new IllegalStateException("Could not load annotation type [" + annoTypeName + "]", ex); } } @@ -212,7 +122,7 @@ class ConfigurationClassReaderUtils { // 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.attributes.put(attrib.getName(), AnnotationUtils.getDefaultValue(annoType, attrib.getName())); this.attributeTypes.put(attrib.getName(), getAttributeType(annoType, attrib.getName())); } @@ -349,11 +259,10 @@ class ConfigurationClassReaderUtils { } private String getAttribs() { - ArrayList attribs = new ArrayList(); - - for (String attribName : attributes.keySet()) + List attribs = new ArrayList(); + for (String attribName : attributes.keySet()) { attribs.add(format("%s=%s", attribName, attributes.get(attribName))); - + } return StringUtils.collectionToDelimitedString(attribs, ", "); } @@ -361,15 +270,12 @@ class ConfigurationClassReaderUtils { * 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 annotationType.getDeclaredMethod(attributeName).getReturnType(); + } + catch (Exception ex) { + throw new IllegalStateException("Could not introspect return type", 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 8974310127..4062d9a5c0 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,6 +16,7 @@ package org.springframework.context.annotation; +import java.io.IOException; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; @@ -37,10 +38,13 @@ 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.BeanDefinitionStoreException; 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.core.type.classreading.SimpleMetadataReader; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -64,27 +68,28 @@ class ConfigurationClassVisitor implements ClassVisitor { private final ProblemReporter problemReporter; - private final ClassLoader classLoader; + private final SimpleMetadataReaderFactory metadataReaderFactory; private final Stack importStack; public ConfigurationClassVisitor(ConfigurationClass configClass, Set model, - ProblemReporter problemReporter, ClassLoader classLoader) { + ProblemReporter problemReporter, SimpleMetadataReaderFactory metadataReaderFactory) { this.configClass = configClass; this.model = model; this.problemReporter = problemReporter; - this.classLoader = classLoader; + this.metadataReaderFactory = metadataReaderFactory; this.importStack = new ImportStack(); } private ConfigurationClassVisitor(ConfigurationClass configClass, Set model, - ProblemReporter problemReporter, ClassLoader classLoader, Stack importStack) { + ProblemReporter problemReporter, SimpleMetadataReaderFactory metadataReaderFactory, + Stack importStack) { this.configClass = configClass; this.model = model; this.problemReporter = problemReporter; - this.classLoader = classLoader; + this.metadataReaderFactory = metadataReaderFactory; this.importStack = importStack; } @@ -105,9 +110,15 @@ class ConfigurationClassVisitor implements ClassVisitor { 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); + ConfigurationClassVisitor visitor = new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory, importStack); + String superClassName = ClassUtils.convertResourcePathToClassName(superTypeDesc); + try { + SimpleMetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName); + reader.getClassReader().accept(visitor, false); + } + catch (IOException ex) { + throw new BeanDefinitionStoreException("Failed to load bean super class [" + superClassName + "]", ex); + } } public void visitSource(String sourceFile, String debug) { @@ -132,15 +143,14 @@ class ConfigurationClassVisitor implements ClassVisitor { * @see Lazy * @see Import */ + @SuppressWarnings("unchecked") public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) { - String annoTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc); - Class annoClass = ConfigurationClassReaderUtils.loadToolingSafeClass(annoTypeName, classLoader); - + ClassLoader classLoader = metadataReaderFactory.getResourceLoader().getClassLoader(); + Class annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); if (annoClass == null) { // annotation was unable to be loaded -> probably Spring IDE unable to load a user-defined annotation return new EmptyVisitor(); } - if (Import.class.equals(annoClass)) { if (!importStack.contains(configClass)) { importStack.push(configClass); @@ -169,6 +179,7 @@ class ConfigurationClassVisitor implements ClassVisitor { * {@link ConfigurationClassMethodVisitor}. */ public MethodVisitor visitMethod(int modifiers, String methodName, String methodDescriptor, String arg3, String[] arg4) { + ClassLoader classLoader = metadataReaderFactory.getResourceLoader().getClassLoader(); return new ConfigurationClassMethodVisitor(configClass, methodName, methodDescriptor, modifiers, classLoader); } @@ -181,7 +192,7 @@ class ConfigurationClassVisitor implements ClassVisitor { * {@link Configuration} class. Determines whether the method is a {@link Bean} * method and if so, adds it to the {@link ConfigurationClass}. */ - private static class ConfigurationClassMethodVisitor extends MethodAdapter { + private class ConfigurationClassMethodVisitor extends MethodAdapter { private final ConfigurationClass configClass; private final String methodName; @@ -214,9 +225,9 @@ class ConfigurationClassVisitor implements ClassVisitor { * present (regardless of its RetentionPolicy). */ @Override + @SuppressWarnings("unchecked") public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) { - String annoClassName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc); - Class annoClass = ConfigurationClassReaderUtils.loadToolingSafeClass(annoClassName, classLoader); + Class annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader); if (annoClass == null) { return super.visitAnnotation(annoTypeDesc, visible); } @@ -262,14 +273,19 @@ class ConfigurationClassVisitor implements ClassVisitor { 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; + try { + metadataReaderFactory.getMetadataReader(returnType.getName()).getClassReader().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; + } + catch (IOException ex) { + throw new BeanDefinitionStoreException("Failed to load bean return type [" + returnType.getName() + "]", ex); + } } } @@ -285,17 +301,13 @@ class ConfigurationClassVisitor implements ClassVisitor { 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) { @@ -337,13 +349,18 @@ class ConfigurationClassVisitor implements ClassVisitor { 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())); + try { + ClassReader reader = metadataReaderFactory.getMetadataReader(classToImport).getClassReader(); + reader.accept(new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory, importStack), false); + if (configClass.getAnnotation(Configuration.class) == null) { + problemReporter.error(new NonAnnotatedConfigurationProblem(configClass.getName(), configClass.getLocation())); + } + else { + model.add(configClass); + } } - else { - model.add(configClass); + catch (IOException ex) { + throw new BeanDefinitionStoreException("Failed to load imported configuration class [" + classToImport + "]", ex); } } } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java index dfc0d72d04..390a5cf922 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java @@ -16,10 +16,7 @@ package org.springframework.context.annotation; import org.springframework.beans.factory.parsing.FailFastProblemReporter; -import org.springframework.context.annotation.ConfigurationClassParser; -import org.springframework.context.annotation.Import; -import org.springframework.util.ClassUtils; - +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; /** * Unit test proving that ASM-based {@link ConfigurationClassParser} correctly detects circular use of @@ -32,9 +29,10 @@ import org.springframework.util.ClassUtils; * @author Chris Beams */ public class AsmCircularImportDetectionTests extends AbstractCircularImportDetectionTests { + @Override protected ConfigurationClassParser newParser() { - return new ConfigurationClassParser(new FailFastProblemReporter(), ClassUtils.getDefaultClassLoader()); + return new ConfigurationClassParser(new CachingMetadataReaderFactory(), new FailFastProblemReporter()); } @Override 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 a5be724742..cc28c56f9e 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 @@ -21,7 +21,6 @@ import org.springframework.beans.factory.annotation.Qualifier; 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; @@ -47,6 +46,11 @@ public final class FactoryMethodComponent { return new TestBean("publicInstance"); } + // to be ignored + public TestBean publicInstance(boolean doIt) { + return new TestBean("publicInstance"); + } + @Bean @BeanAge(1) protected TestBean protectedInstance(@Qualifier("public") TestBean spouse, @Value("#{privateInstance.age}") String country) { TestBean tb = new TestBean("protectedInstance", 1); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java index 7744811ff0..92e06e8675 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java @@ -33,7 +33,7 @@ import org.springframework.core.io.ResourceLoader; */ public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory { - private final Map classReaderCache = new HashMap(); + private final Map classReaderCache = new HashMap(); /** @@ -62,9 +62,9 @@ public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory { @Override - public MetadataReader getMetadataReader(Resource resource) throws IOException { + public SimpleMetadataReader getMetadataReader(Resource resource) throws IOException { synchronized (this.classReaderCache) { - MetadataReader metadataReader = this.classReaderCache.get(resource); + SimpleMetadataReader metadataReader = this.classReaderCache.get(resource); if (metadataReader == null) { metadataReader = super.getMetadataReader(resource); this.classReaderCache.put(resource, metadataReader); diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java index 45c080171d..3ef5211153 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java @@ -30,29 +30,55 @@ import org.springframework.core.type.ClassMetadata; * @author Juergen Hoeller * @since 2.5 */ -class SimpleMetadataReader implements MetadataReader { +public class SimpleMetadataReader implements MetadataReader { private final ClassReader classReader; private final ClassLoader classLoader; + private ClassMetadata classMetadata; - public SimpleMetadataReader(ClassReader classReader, ClassLoader classLoader) { + private AnnotationMetadata annotationMetadata; + + + SimpleMetadataReader(ClassReader classReader, ClassLoader classLoader) { this.classReader = classReader; this.classLoader = classLoader; } + /** + * Return the underlying ASM ClassReader. + */ + public final ClassReader getClassReader() { + return this.classReader; + } + + /** + * Return the underlying ClassLoader. + */ + public final ClassLoader getClassLoader() { + return this.classLoader; + } + + public ClassMetadata getClassMetadata() { - ClassMetadataReadingVisitor visitor = new ClassMetadataReadingVisitor(); - this.classReader.accept(visitor, true); - return visitor; + if (this.classMetadata == null) { + ClassMetadataReadingVisitor visitor = new ClassMetadataReadingVisitor(); + this.classReader.accept(visitor, true); + this.classMetadata = visitor; + } + return this.classMetadata; } public AnnotationMetadata getAnnotationMetadata() { - AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(this.classLoader); - this.classReader.accept(visitor, true); - return visitor; + if (this.annotationMetadata == null) { + AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(this.classLoader); + this.classReader.accept(visitor, true); + this.annotationMetadata = visitor; + this.classMetadata = visitor; + } + return this.annotationMetadata; } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java index a85a84b28e..b7b1d19047 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java +++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java @@ -63,13 +63,22 @@ public class SimpleMetadataReaderFactory implements MetadataReaderFactory { } - public MetadataReader getMetadataReader(String className) throws IOException { + /** + * Return the ResourceLoader that this MetadataReaderFactory has been + * constructed with. + */ + public final ResourceLoader getResourceLoader() { + return this.resourceLoader; + } + + + public SimpleMetadataReader getMetadataReader(String className) throws IOException { String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX; return getMetadataReader(this.resourceLoader.getResource(resourcePath)); } - public MetadataReader getMetadataReader(Resource resource) throws IOException { + public SimpleMetadataReader getMetadataReader(Resource resource) throws IOException { InputStream is = resource.getInputStream(); try { return new SimpleMetadataReader(new ClassReader(is), this.resourceLoader.getClassLoader());