提交 ea9d8925 编写于 作者: J Juergen Hoeller

next cut of JavaConfig metadata reading revision: using cached MetadataReaders

上级 4c42597c
......@@ -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
......
......@@ -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.
......
......@@ -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<Method> candidateSet = new ArrayList<Method>();
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);
}
}
......
......@@ -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);
......
/*
* 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<String> externallyManagedDestroyMethods = Collections.synchronizedSet(new HashSet<String>());
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 <code>null</code> 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);
}
......
......@@ -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,31 +49,33 @@ 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.
* Sets the fully-qualified name of this class.
*/
public String getName() {
return name;
public void setName(String className) {
this.name = className;
}
/**
* Sets the fully-qualified name of this class.
* Returns the fully-qualified name of this class.
*/
public void setName(String className) {
this.name = className;
public String getName() {
return name;
}
/**
......@@ -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 Set<ConfigurationClassMethod> getBeanMethods() {
return methods;
}
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 ConfigurationClass getDeclaringClass() {
return declaringClass;
}
public void setDeclaringClass(ConfigurationClass configurationClass) {
this.declaringClass = configurationClass;
public Set<ConfigurationClassMethod> getBeanMethods() {
return methods;
}
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);
}
}
}
......
......@@ -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
......
......@@ -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);
......
......@@ -33,7 +33,7 @@ import org.springframework.core.io.ResourceLoader;
*/
public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory {
private final Map<Resource, MetadataReader> classReaderCache = new HashMap<Resource, MetadataReader>();
private final Map<Resource, SimpleMetadataReader> classReaderCache = new HashMap<Resource, SimpleMetadataReader>();
/**
......@@ -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);
......
......@@ -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;
}
}
......@@ -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());
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册