next cut of JavaConfig metadata reading revision: using cached MetadataReaders

This commit is contained in:
Juergen Hoeller
2009-04-22 10:46:24 +00:00
parent 4c42597cbc
commit ea9d8925a2
17 changed files with 345 additions and 292 deletions

View File

@@ -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<Annotation> annotations = new HashSet<Annotation>();
private final Set<Annotation> annotations = new HashSet<Annotation>();
private Set<ConfigurationClassMethod> methods = new HashSet<ConfigurationClassMethod>();
private final Set<ConfigurationClassMethod> methods = new LinkedHashSet<ConfigurationClassMethod>();
private ConfigurationClass declaringClass;
private final Map<String, Integer> overloadedMethodMap = new LinkedHashMap<String, Integer>();
/**
* 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<ConfigurationClassMethod> 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<String, Integer> 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());
}
}
}

View File

@@ -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<? extends Enum> enumClass = ConfigurationClassReaderUtils.loadToolingSafeClass(enumClassName, classLoader);
if (enumClass == null) {
return;
String enumTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(enumTypeDescriptor);
try {
Class<? extends Enum> enumType = (Class<? extends Enum>) 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<? extends Annotation> annoType = ConfigurationClassReaderUtils.loadToolingSafeClass(annoTypeName, classLoader);
if (annoType == null) {
public AnnotationVisitor visitAnnotation(String attribName, String annoTypeDesc) {
Class<? extends Annotation> 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<? extends Annotation> annoType = ConfigurationClassReaderUtils.loadToolingSafeClass(annoTypeName, classLoader);
if (annoType == null) {
Class<? extends Annotation> 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);
}

View File

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

View File

@@ -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<ConfigurationClass> model;
private final SimpleMetadataReaderFactory metadataReaderFactory;
private final ProblemReporter problemReporter;
private final ClassLoader classLoader;
private final Set<ConfigurationClass> 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<ConfigurationClass>();
public ConfigurationClassParser(SimpleMetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter) {
this.metadataReaderFactory = metadataReaderFactory;
this.problemReporter = problemReporter;
this.classLoader = classLoader;
this.model = new LinkedHashSet<ConfigurationClass>();
}
@@ -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);
}

View File

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

View File

@@ -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 <var>pathToClass</var>. 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 <var>is</var>, 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 <var>pathToClass</var>. 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 <var>pathToClass</var>
* @throws RuntimeException if <var>pathToClass</var> 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.
* <p>
* Because {@link ClassNotFoundException} is compensated for by returning null, callers
* must take care to handle the null case appropriately.
* <p>
* In cases where the WARN logging statement is not desired, use the
* {@link #loadClass(String)} method, which returns null but issues no logging
* statements.
* <p>
* 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 <T> 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 <T> Class<? extends T> loadToolingSafeClass(String fqClassName, ClassLoader classLoader) {
public static Class<? extends Annotation> loadAnnotationType(String annoTypeDesc, ClassLoader classLoader) {
String annoTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc);
try {
return (Class<? extends T>) classLoader.loadClass(fqClassName);
return (Class<? extends Annotation>) 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<String> attribs = new ArrayList<String>();
for (String attribName : attributes.keySet())
List<String> attribs = new ArrayList<String>();
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<? extends Annotation> 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();
}
}

View File

@@ -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<ConfigurationClass> importStack;
public ConfigurationClassVisitor(ConfigurationClass configClass, Set<ConfigurationClass> 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<ConfigurationClass> model,
ProblemReporter problemReporter, ClassLoader classLoader, Stack<ConfigurationClass> importStack) {
ProblemReporter problemReporter, SimpleMetadataReaderFactory metadataReaderFactory,
Stack<ConfigurationClass> 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<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadToolingSafeClass(annoTypeName, classLoader);
ClassLoader classLoader = metadataReaderFactory.getResourceLoader().getClassLoader();
Class<? extends Annotation> 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<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadToolingSafeClass(annoClassName, classLoader);
Class<? extends Annotation> 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<String> classesToImport = new ArrayList<String>();
public ImportAnnotationVisitor(
Set<ConfigurationClass> 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);
}
}
}