next cut of JavaConfig metadata reading revision: using cached MetadataReaders
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user