@Configuration parsing fully relies on Spring's MetadataReader abstraction now
This commit is contained in:
@@ -18,7 +18,6 @@ package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
@@ -27,7 +26,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that a class declares one or more {@link Bean} methods and may be processed
|
||||
* by the Spring container to generate bean definitions and service requests for those beans
|
||||
@@ -59,7 +57,6 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Component
|
||||
public @interface Configuration {
|
||||
|
||||
@@ -16,20 +16,19 @@
|
||||
|
||||
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;
|
||||
import org.springframework.beans.factory.parsing.Location;
|
||||
import org.springframework.beans.factory.parsing.Problem;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.core.io.DescriptiveResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
@@ -45,75 +44,42 @@ import org.springframework.util.ClassUtils;
|
||||
* @see ConfigurationClassMethod
|
||||
* @see ConfigurationClassParser
|
||||
*/
|
||||
final class ConfigurationClass implements BeanMetadataElement {
|
||||
final class ConfigurationClass {
|
||||
|
||||
private String name;
|
||||
private final AnnotationMetadata metadata;
|
||||
|
||||
private ConfigurationClass declaringClass;
|
||||
|
||||
private Object source;
|
||||
private final Resource resource;
|
||||
|
||||
private String beanName;
|
||||
|
||||
private int modifiers;
|
||||
|
||||
private final Set<Annotation> annotations = new HashSet<Annotation>();
|
||||
|
||||
private final Set<ConfigurationClassMethod> methods = new LinkedHashSet<ConfigurationClassMethod>();
|
||||
|
||||
private final Map<String, Integer> overloadedMethodMap = new LinkedHashMap<String, Integer>();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the fully-qualified name of this class.
|
||||
*/
|
||||
public void setName(String className) {
|
||||
this.name = className;
|
||||
public ConfigurationClass(MetadataReader metadataReader, String beanName) {
|
||||
this.metadata = metadataReader.getAnnotationMetadata();
|
||||
this.resource = metadataReader.getResource();
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully-qualified name of this class.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
public ConfigurationClass(Class clazz, String beanName) {
|
||||
this.metadata = new StandardAnnotationMetadata(clazz);
|
||||
this.resource = new DescriptiveResource(clazz.toString());
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
|
||||
public AnnotationMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
public Resource getResource() {
|
||||
return this.resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'.
|
||||
*/
|
||||
public String getSimpleName() {
|
||||
return name == null ? null : ClassUtils.getShortName(name);
|
||||
}
|
||||
|
||||
public void setDeclaringClass(ConfigurationClass configurationClass) {
|
||||
this.declaringClass = configurationClass;
|
||||
}
|
||||
|
||||
public ConfigurationClass getDeclaringClass() {
|
||||
return declaringClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the source location for this class. Must be a resource-path formatted string.
|
||||
* @param source resource path to the .java file that declares this class.
|
||||
*/
|
||||
public void setSource(Object source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()");
|
||||
}
|
||||
return new Location(new ClassPathResource(ClassUtils.convertClassNameToResourcePath(getName())), getSource());
|
||||
return ClassUtils.getShortName(getMetadata().getClassName());
|
||||
}
|
||||
|
||||
public void setBeanName(String beanName) {
|
||||
@@ -121,71 +87,29 @@ final class ConfigurationClass implements BeanMetadataElement {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the annotation on this class matching <var>annoType</var> or
|
||||
* {@literal null} if not present.
|
||||
* @see #getRequiredAnnotation(Class)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <A extends Annotation> A getAnnotation(Class<A> annoType) {
|
||||
for (Annotation annotation : annotations) {
|
||||
if (annotation.annotationType().equals(annoType)) {
|
||||
return (A) annotation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the annotation on this class matching <var>annoType</var>
|
||||
* @throws {@link IllegalStateException} if not present
|
||||
* @see #getAnnotation(Class)
|
||||
*/
|
||||
public <A extends Annotation> A getRequiredAnnotation(Class<A> annoType) {
|
||||
A anno = getAnnotation(annoType);
|
||||
if (anno == null) {
|
||||
throw new IllegalStateException(
|
||||
String.format("Required annotation %s is not present on %s", annoType.getSimpleName(), this));
|
||||
}
|
||||
return anno;
|
||||
return this.beanName;
|
||||
}
|
||||
|
||||
public ConfigurationClass addMethod(ConfigurationClassMethod method) {
|
||||
method.setDeclaringClass(this);
|
||||
methods.add(method);
|
||||
Integer count = overloadedMethodMap.get(method.getName());
|
||||
this.methods.add(method);
|
||||
String name = method.getMetadata().getMethodName();
|
||||
Integer count = this.overloadedMethodMap.get(name);
|
||||
if (count != null) {
|
||||
overloadedMethodMap.put(method.getName(), count + 1);
|
||||
this.overloadedMethodMap.put(name, count + 1);
|
||||
}
|
||||
else {
|
||||
overloadedMethodMap.put(method.getName(), 1);
|
||||
this.overloadedMethodMap.put(name, 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set<ConfigurationClassMethod> getBeanMethods() {
|
||||
return methods;
|
||||
public Set<ConfigurationClassMethod> getConfigurationMethods() {
|
||||
return this.methods;
|
||||
}
|
||||
|
||||
public void validate(ProblemReporter problemReporter) {
|
||||
// No overloading of factory methods allowed
|
||||
for (Map.Entry<String, Integer> entry : overloadedMethodMap.entrySet()) {
|
||||
for (Map.Entry<String, Integer> entry : this.overloadedMethodMap.entrySet()) {
|
||||
String methodName = entry.getKey();
|
||||
int count = entry.getValue();
|
||||
if (count > 1) {
|
||||
@@ -194,11 +118,11 @@ final class ConfigurationClass implements BeanMetadataElement {
|
||||
}
|
||||
|
||||
// A configuration class may not be final (CGLIB limitation)
|
||||
if (getAnnotation(Configuration.class) != null) {
|
||||
if (Modifier.isFinal(modifiers)) {
|
||||
if (getMetadata().hasAnnotation(Configuration.class.getName())) {
|
||||
if (getMetadata().isFinal()) {
|
||||
problemReporter.error(new FinalConfigurationProblem());
|
||||
}
|
||||
for (ConfigurationClassMethod method : methods) {
|
||||
for (ConfigurationClassMethod method : this.methods) {
|
||||
method.validate(problemReporter);
|
||||
}
|
||||
}
|
||||
@@ -210,7 +134,7 @@ final class ConfigurationClass implements BeanMetadataElement {
|
||||
|
||||
public FinalConfigurationProblem() {
|
||||
super(String.format("@Configuration class '%s' may not be final. Remove the final modifier to continue.",
|
||||
getSimpleName()), ConfigurationClass.this.getLocation());
|
||||
getSimpleName()), new Location(getResource()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,9 +145,8 @@ final class ConfigurationClass implements BeanMetadataElement {
|
||||
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());
|
||||
getSimpleName(), count, methodName), new Location(getResource()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* Interface used when dynamically creating mutable instances of annotations associated
|
||||
* with {@link Configuration} class processing. This functionality is necessary given
|
||||
* that parsing of Configuration classes is done with ASM. Annotation metadata (including
|
||||
* attributes) is parsed from the class files, and instances of those annotations are
|
||||
* then created using this interface and its associated utilities. The annotation
|
||||
* instances are attached to the configuration model objects at runtime, namely
|
||||
* {@link ConfigurationClassMethod}. This approach is better than the alternative of
|
||||
* creating fine-grained model representations of all annotations and attributes.
|
||||
* It is better to simply attach annotation instances and read them as needed.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see ConfigurationClassAnnotationVisitor
|
||||
* @see ConfigurationClassReaderUtils#createMutableAnnotation
|
||||
*/
|
||||
public interface ConfigurationClassAnnotation extends Annotation {
|
||||
|
||||
void setAttributeValue(String attribName, Object attribValue);
|
||||
|
||||
Class<?> getAttributeType(String attributeName);
|
||||
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.asm.AnnotationVisitor;
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.asm.commons.EmptyVisitor;
|
||||
|
||||
/**
|
||||
* ASM {@link AnnotationVisitor} that populates a given {@link ConfigurationClassAnnotation} instance
|
||||
* with its attributes.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see ConfigurationClassAnnotation
|
||||
* @see ConfigurationClassReaderUtils#createMutableAnnotation
|
||||
*/
|
||||
class ConfigurationClassAnnotationVisitor implements AnnotationVisitor {
|
||||
|
||||
protected final ConfigurationClassAnnotation mutableAnno;
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConfigurationClassAnnotationVisitor} instance that will populate the the
|
||||
* attributes of the given <var>mutableAnno</var>. Accepts {@link Annotation} instead of
|
||||
* {@link ConfigurationClassAnnotation} to avoid the need for callers to typecast.
|
||||
* @param mutableAnno {@link ConfigurationClassAnnotation} instance to visit and populate
|
||||
* @see ConfigurationClassReaderUtils#createMutableAnnotation
|
||||
*/
|
||||
public ConfigurationClassAnnotationVisitor(ConfigurationClassAnnotation mutableAnno, ClassLoader classLoader) {
|
||||
this.mutableAnno = mutableAnno;
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public void visit(String attribName, Object attribValue) {
|
||||
Class<?> attribReturnType = mutableAnno.getAttributeType(attribName);
|
||||
if (attribReturnType.equals(Class.class)) {
|
||||
// the attribute type is Class -> load it and set it.
|
||||
String 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);
|
||||
}
|
||||
}
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
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(annoClass, classLoader);
|
||||
try {
|
||||
Field attribute = mutableAnno.getClass().getField(attribName);
|
||||
attribute.set(mutableAnno, anno);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not reflectively set annotation field", ex);
|
||||
}
|
||||
return new ConfigurationClassAnnotationVisitor(anno, classLoader);
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitArray(final String attribName) {
|
||||
return new MutableAnnotationArrayVisitor(mutableAnno, attribName, classLoader);
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ASM {@link AnnotationVisitor} that visits any annotation array values while populating
|
||||
* a new {@link ConfigurationClassAnnotation} instance.
|
||||
*/
|
||||
private static class MutableAnnotationArrayVisitor implements AnnotationVisitor {
|
||||
|
||||
private final List<Object> values = new ArrayList<Object>();
|
||||
|
||||
private final ConfigurationClassAnnotation mutableAnno;
|
||||
|
||||
private final String attribName;
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public MutableAnnotationArrayVisitor(ConfigurationClassAnnotation mutableAnno, String attribName, ClassLoader classLoader) {
|
||||
this.mutableAnno = mutableAnno;
|
||||
this.attribName = attribName;
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public void visit(String na, Object value) {
|
||||
values.add(value);
|
||||
}
|
||||
|
||||
public void visitEnum(String s, String s1, String s2) {
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String na, String annoTypeDesc) {
|
||||
Class<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader);
|
||||
if (annoClass == null) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
ConfigurationClassAnnotation anno = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader);
|
||||
values.add(anno);
|
||||
return new ConfigurationClassAnnotationVisitor(anno, classLoader);
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitArray(String s) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
Class<?> arrayType = mutableAnno.getAttributeType(attribName);
|
||||
Object[] array = (Object[]) Array.newInstance(arrayType.getComponentType(), 0);
|
||||
mutableAnno.setAttributeValue(attribName, values.toArray(array));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -35,6 +36,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -78,7 +80,7 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
*/
|
||||
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
|
||||
doLoadBeanDefinitionForConfigurationClass(configClass);
|
||||
for (ConfigurationClassMethod method : configClass.getBeanMethods()) {
|
||||
for (ConfigurationClassMethod method : configClass.getConfigurationMethods()) {
|
||||
loadBeanDefinitionsForModelMethod(method);
|
||||
}
|
||||
}
|
||||
@@ -89,7 +91,7 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
private void doLoadBeanDefinitionForConfigurationClass(ConfigurationClass configClass) {
|
||||
if (configClass.getBeanName() == null) {
|
||||
GenericBeanDefinition configBeanDef = new GenericBeanDefinition();
|
||||
configBeanDef.setBeanClassName(configClass.getName());
|
||||
configBeanDef.setBeanClassName(configClass.getMetadata().getClassName());
|
||||
String configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(configBeanDef, registry);
|
||||
configClass.setBeanName(configBeanName);
|
||||
if (logger.isDebugEnabled()) {
|
||||
@@ -103,24 +105,26 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
* {@link #registry} based on its contents.
|
||||
*/
|
||||
private void loadBeanDefinitionsForModelMethod(ConfigurationClassMethod method) {
|
||||
MethodMetadata metadata = method.getMetadata();
|
||||
|
||||
RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition();
|
||||
ConfigurationClass configClass = method.getDeclaringClass();
|
||||
beanDef.setFactoryBeanName(configClass.getBeanName());
|
||||
beanDef.setUniqueFactoryMethodName(method.getName());
|
||||
beanDef.setUniqueFactoryMethodName(metadata.getMethodName());
|
||||
beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
|
||||
|
||||
// consider name and any aliases
|
||||
Bean bean = method.getRequiredAnnotation(Bean.class);
|
||||
List<String> names = new ArrayList<String>(Arrays.asList(bean.name()));
|
||||
String beanName = (names.size() > 0) ? names.remove(0) : method.getName();
|
||||
for (String alias : bean.name()) {
|
||||
Map<String, Object> beanAttributes = metadata.getAnnotationAttributes(Bean.class.getName());
|
||||
List<String> names = new ArrayList<String>(Arrays.asList((String[]) beanAttributes.get("name")));
|
||||
String beanName = (names.size() > 0 ? names.remove(0) : method.getMetadata().getMethodName());
|
||||
for (String alias : names) {
|
||||
registry.registerAlias(beanName, alias);
|
||||
}
|
||||
|
||||
// has this already been overriden (i.e.: via XML)?
|
||||
if (registry.containsBeanDefinition(beanName)) {
|
||||
BeanDefinition existingBeanDef = registry.getBeanDefinition(beanName);
|
||||
// is the existing bean definition one that was created by JavaConfig?
|
||||
// is the existing bean definition one that was created from a configuration class?
|
||||
if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) {
|
||||
// no -> then it's an external override, probably XML
|
||||
// overriding is legal, return immediately
|
||||
@@ -132,48 +136,46 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
}
|
||||
}
|
||||
|
||||
if (method.getAnnotation(Primary.class) != null) {
|
||||
if (metadata.hasAnnotation(Primary.class.getName())) {
|
||||
beanDef.setPrimary(true);
|
||||
}
|
||||
|
||||
// is this bean to be instantiated lazily?
|
||||
Lazy lazy = method.getAnnotation(Lazy.class);
|
||||
if (lazy != null) {
|
||||
beanDef.setLazyInit(lazy.value());
|
||||
if (metadata.hasAnnotation(Lazy.class.getName())) {
|
||||
beanDef.setLazyInit((Boolean) metadata.getAnnotationAttributes(Lazy.class.getName()).get("value"));
|
||||
}
|
||||
else {
|
||||
Lazy defaultLazy = configClass.getAnnotation(Lazy.class);
|
||||
if (defaultLazy != null) {
|
||||
beanDef.setLazyInit(defaultLazy.value());
|
||||
else if (configClass.getMetadata().hasAnnotation(Lazy.class.getName())){
|
||||
beanDef.setLazyInit((Boolean) configClass.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value"));
|
||||
}
|
||||
|
||||
if (metadata.hasAnnotation(DependsOn.class.getName())) {
|
||||
String[] dependsOn = (String[]) metadata.getAnnotationAttributes(DependsOn.class.getName()).get("value");
|
||||
if (dependsOn.length > 0) {
|
||||
beanDef.setDependsOn(dependsOn);
|
||||
}
|
||||
}
|
||||
|
||||
DependsOn dependsOn = method.getAnnotation(DependsOn.class);
|
||||
if (dependsOn != null && dependsOn.value().length > 0) {
|
||||
beanDef.setDependsOn(dependsOn.value());
|
||||
}
|
||||
|
||||
Autowire autowire = bean.autowire();
|
||||
Autowire autowire = (Autowire) beanAttributes.get("autowire");
|
||||
if (autowire.isAutowire()) {
|
||||
beanDef.setAutowireMode(autowire.value());
|
||||
}
|
||||
|
||||
String initMethodName = bean.initMethod();
|
||||
String initMethodName = (String) beanAttributes.get("initMethod");
|
||||
if (StringUtils.hasText(initMethodName)) {
|
||||
beanDef.setInitMethodName(initMethodName);
|
||||
}
|
||||
|
||||
String destroyMethodName = bean.destroyMethod();
|
||||
String destroyMethodName = (String) beanAttributes.get("destroyMethod");
|
||||
if (StringUtils.hasText(destroyMethodName)) {
|
||||
beanDef.setDestroyMethodName(destroyMethodName);
|
||||
}
|
||||
|
||||
// consider scoping
|
||||
Scope scope = method.getAnnotation(Scope.class);
|
||||
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
|
||||
if (scope != null) {
|
||||
beanDef.setScope(scope.value());
|
||||
proxyMode = scope.proxyMode();
|
||||
if (metadata.hasAnnotation(Scope.class.getName())) {
|
||||
Map<String, Object> scopeAttributes = metadata.getAnnotationAttributes(Scope.class.getName());
|
||||
beanDef.setScope((String) scopeAttributes.get("value"));
|
||||
proxyMode = (ScopedProxyMode) scopeAttributes.get("proxyMode");
|
||||
if (proxyMode == ScopedProxyMode.DEFAULT) {
|
||||
proxyMode = ScopedProxyMode.NO;
|
||||
}
|
||||
@@ -188,7 +190,7 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getName(), beanName));
|
||||
logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getMetadata().getClassName(), beanName));
|
||||
}
|
||||
|
||||
registry.registerBeanDefinition(beanName, beanDefToRegister);
|
||||
@@ -196,11 +198,11 @@ 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.
|
||||
* {@link RootBeanDefinition} marker subclass used to signify that a bean definition
|
||||
* created from a configuration class 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.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private class ConfigurationClassBeanDefinition extends RootBeanDefinition {
|
||||
|
||||
public ConfigurationClassBeanDefinition() {
|
||||
|
||||
@@ -60,17 +60,14 @@ class ConfigurationClassEnhancer {
|
||||
*/
|
||||
public ConfigurationClassEnhancer(ConfigurableBeanFactory beanFactory) {
|
||||
Assert.notNull(beanFactory, "BeanFactory must not be null");
|
||||
|
||||
callbackInstances.add(new BeanMethodInterceptor(beanFactory));
|
||||
callbackInstances.add(NoOp.INSTANCE);
|
||||
|
||||
for (Callback callback : callbackInstances) {
|
||||
callbackTypes.add(callback.getClass());
|
||||
this.callbackInstances.add(new BeanMethodInterceptor(beanFactory));
|
||||
this.callbackInstances.add(NoOp.INSTANCE);
|
||||
for (Callback callback : this.callbackInstances) {
|
||||
this.callbackTypes.add(callback.getClass());
|
||||
}
|
||||
|
||||
// Set up the callback filter to return the index of the BeanMethodInterceptor when
|
||||
// handling a @Bean-annotated method; otherwise, return index of the NoOp callback.
|
||||
callbackFilter = new CallbackFilter() {
|
||||
this.callbackFilter = new CallbackFilter() {
|
||||
public int accept(Method candidateMethod) {
|
||||
return (AnnotationUtils.findAnnotation(candidateMethod, Bean.class) != null) ? 0 : 1;
|
||||
}
|
||||
@@ -84,12 +81,12 @@ class ConfigurationClassEnhancer {
|
||||
* @return fully-qualified name of the enhanced subclass
|
||||
*/
|
||||
public Class enhance(Class configClass) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Enhancing " + configClass.getName());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Enhancing " + configClass.getName());
|
||||
}
|
||||
Class<?> enhancedClass = createClass(newEnhancer(configClass));
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info(String.format("Successfully enhanced %s; enhanced class name is: %s",
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s",
|
||||
configClass.getName(), enhancedClass.getName()));
|
||||
}
|
||||
return enhancedClass;
|
||||
@@ -100,17 +97,14 @@ class ConfigurationClassEnhancer {
|
||||
*/
|
||||
private Enhancer newEnhancer(Class<?> superclass) {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
|
||||
// because callbackFilter and callbackTypes are dynamically populated
|
||||
// Because callbackFilter and callbackTypes are dynamically populated
|
||||
// there's no opportunity for caching. This does not appear to be causing
|
||||
// any performance problem.
|
||||
enhancer.setUseCache(false);
|
||||
|
||||
enhancer.setSuperclass(superclass);
|
||||
enhancer.setUseFactory(false);
|
||||
enhancer.setCallbackFilter(callbackFilter);
|
||||
enhancer.setCallbackTypes(callbackTypes.toArray(new Class[callbackTypes.size()]));
|
||||
|
||||
enhancer.setCallbackFilter(this.callbackFilter);
|
||||
enhancer.setCallbackTypes(this.callbackTypes.toArray(new Class[this.callbackTypes.size()]));
|
||||
return enhancer;
|
||||
}
|
||||
|
||||
@@ -120,7 +114,7 @@ class ConfigurationClassEnhancer {
|
||||
*/
|
||||
private Class<?> createClass(Enhancer enhancer) {
|
||||
Class<?> subclass = enhancer.createClass();
|
||||
Enhancer.registerCallbacks(subclass, callbackInstances.toArray(new Callback[callbackInstances.size()]));
|
||||
Enhancer.registerCallbacks(subclass, this.callbackInstances.toArray(new Callback[this.callbackInstances.size()]));
|
||||
return subclass;
|
||||
}
|
||||
|
||||
@@ -142,7 +136,6 @@ class ConfigurationClassEnhancer {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
|
||||
* existence of this bean object.
|
||||
@@ -161,7 +154,7 @@ class ConfigurationClassEnhancer {
|
||||
Scope scope = AnnotationUtils.findAnnotation(method, Scope.class);
|
||||
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
|
||||
String scopedBeanName = ScopedProxyUtils.getTargetBeanName(beanName);
|
||||
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
|
||||
if (this.beanFactory.isCurrentlyInCreation(scopedBeanName)) {
|
||||
beanName = scopedBeanName;
|
||||
}
|
||||
}
|
||||
@@ -170,7 +163,7 @@ class ConfigurationClassEnhancer {
|
||||
// container for already cached instances
|
||||
if (factoryContainsBean(beanName)) {
|
||||
// we have an already existing cached instance of this bean -> retrieve it
|
||||
Object cachedBean = beanFactory.getBean(beanName);
|
||||
Object cachedBean = this.beanFactory.getBean(beanName);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Returning cached object [%s] for @Bean method %s.%s",
|
||||
cachedBean, method.getDeclaringClass().getSimpleName(), beanName));
|
||||
@@ -185,18 +178,18 @@ class ConfigurationClassEnhancer {
|
||||
/**
|
||||
* Check the beanFactory to see whether the bean named <var>beanName</var> already
|
||||
* exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
|
||||
* we're in the middle of servicing the initial request for this bean. From JavaConfig's
|
||||
* perspective, this means that the bean does not actually yet exist, and that it is now
|
||||
* our job to create it for the first time by executing the logic in the corresponding
|
||||
* Bean method.
|
||||
* we're in the middle of servicing the initial request for this bean. From an enhanced
|
||||
* factory method's perspective, this means that the bean does not actually yet exist,
|
||||
* and that it is now our job to create it for the first time by executing the logic
|
||||
* in the corresponding factory method.
|
||||
* <p>Said another way, this check repurposes
|
||||
* {@link ConfigurableBeanFactory#isCurrentlyInCreation(String)} to determine whether
|
||||
* the container is calling this method or the user is calling this method.
|
||||
* @param beanName name of bean to check for
|
||||
* @return true if <var>beanName</var> already exists in the factory
|
||||
* @return whether <var>beanName</var> already exists in the factory
|
||||
*/
|
||||
private boolean factoryContainsBean(String beanName) {
|
||||
return beanFactory.containsBean(beanName) && !beanFactory.isCurrentlyInCreation(beanName);
|
||||
return (this.beanFactory.containsBean(beanName) && !this.beanFactory.isCurrentlyInCreation(beanName));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,17 +16,10 @@
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.parsing.Location;
|
||||
import org.springframework.beans.factory.parsing.Problem;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
|
||||
/**
|
||||
* Represents a {@link Configuration} class method marked with the {@link Bean} annotation.
|
||||
@@ -38,213 +31,46 @@ import org.springframework.core.io.ClassPathResource;
|
||||
* @see ConfigurationClassParser
|
||||
* @see ConfigurationClassBeanDefinitionReader
|
||||
*/
|
||||
final class ConfigurationClassMethod implements BeanMetadataElement {
|
||||
final class ConfigurationClassMethod {
|
||||
|
||||
private final String name;
|
||||
private final MethodMetadata metadata;
|
||||
|
||||
private final int modifiers;
|
||||
|
||||
private final ReturnType returnType;
|
||||
|
||||
private final ArrayList<Annotation> annotations = new ArrayList<Annotation>();
|
||||
|
||||
private transient ConfigurationClass declaringClass;
|
||||
|
||||
private transient Object source;
|
||||
private final ConfigurationClass declaringClass;
|
||||
|
||||
|
||||
public ConfigurationClassMethod(String name, int modifiers, ReturnType returnType, Annotation... annotations) {
|
||||
Assert.hasText(name);
|
||||
this.name = name;
|
||||
|
||||
Assert.notNull(annotations);
|
||||
for (Annotation annotation : annotations) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
Assert.isTrue(modifiers >= 0, "modifiers must be non-negative: " + modifiers);
|
||||
this.modifiers = modifiers;
|
||||
|
||||
Assert.notNull(returnType);
|
||||
this.returnType = returnType;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ReturnType getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.reflect.Modifier
|
||||
*/
|
||||
public int getModifiers() {
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the annotation on this method matching <var>annoType</var> or
|
||||
* {@literal null} if not present.
|
||||
* @see #getRequiredAnnotation(Class)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <A extends Annotation> A getAnnotation(Class<A> annoType) {
|
||||
for (Annotation anno : annotations)
|
||||
if (anno.annotationType().equals(annoType))
|
||||
return (A) anno;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the annotation on this method matching <var>annoType</var>
|
||||
* @throws {@link IllegalStateException} if not present
|
||||
* @see #getAnnotation(Class)
|
||||
*/
|
||||
public <T extends Annotation> T getRequiredAnnotation(Class<T> annoType) {
|
||||
T anno = getAnnotation(annoType);
|
||||
if (anno == null) {
|
||||
throw new IllegalStateException(
|
||||
String.format("required annotation %s is not present on %s", annoType.getSimpleName(), this));
|
||||
}
|
||||
return anno;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a bi-directional relationship between this method and its declaring class.
|
||||
*
|
||||
* @see ConfigurationClass#addMethod(ConfigurationClassMethod)
|
||||
*/
|
||||
public void setDeclaringClass(ConfigurationClass declaringClass) {
|
||||
public ConfigurationClassMethod(MethodMetadata metadata, ConfigurationClass declaringClass) {
|
||||
this.metadata = metadata;
|
||||
this.declaringClass = declaringClass;
|
||||
}
|
||||
|
||||
|
||||
public MethodMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
public ConfigurationClass getDeclaringClass() {
|
||||
return declaringClass;
|
||||
return this.declaringClass;
|
||||
}
|
||||
|
||||
public void setSource(Object source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public Object getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
if (declaringClass == null) {
|
||||
throw new IllegalStateException(
|
||||
"declaringClass property is null. Call setDeclaringClass() before calling getLocation()");
|
||||
}
|
||||
return new Location(declaringClass.getLocation().getResource(), getSource());
|
||||
public Location getResourceLocation() {
|
||||
return new Location(this.declaringClass.getResource(), this.metadata.getMethodName());
|
||||
}
|
||||
|
||||
public void validate(ProblemReporter problemReporter) {
|
||||
if (Modifier.isPrivate(getModifiers())) {
|
||||
problemReporter.error(new PrivateMethodError());
|
||||
}
|
||||
if (Modifier.isFinal(getModifiers())) {
|
||||
problemReporter.error(new FinalMethodError());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class ReturnType implements BeanMetadataElement {
|
||||
|
||||
private String name;
|
||||
|
||||
private boolean isInterface;
|
||||
|
||||
private transient Object source;
|
||||
|
||||
|
||||
public ReturnType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully-qualified name of this class.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fully-qualified name of this class.
|
||||
*/
|
||||
public void setName(String className) {
|
||||
this.name = className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the non-qualified name of this class. Given com.acme.Foo, returns 'Foo'.
|
||||
*/
|
||||
public String getSimpleName() {
|
||||
return name == null ? null : ClassUtils.getShortName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the class represented by this ModelClass instance is an interface.
|
||||
*/
|
||||
public boolean isInterface() {
|
||||
return isInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signifies that this class is (true) or is not (false) an interface.
|
||||
*/
|
||||
public void setInterface(boolean isInterface) {
|
||||
this.isInterface = isInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a resource path-formatted representation of the .java file that declares this
|
||||
* class
|
||||
*/
|
||||
public Object getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the source location for this class. Must be a resource-path formatted string.
|
||||
*
|
||||
* @param source resource path to the .java file that declares this class.
|
||||
*/
|
||||
public void setSource(Object source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
if (getName() == null) {
|
||||
throw new IllegalStateException("'name' property is null. Call setName() before calling getLocation()");
|
||||
}
|
||||
return new Location(new ClassPathResource(ClassUtils.convertClassNameToResourcePath(getName())), getSource());
|
||||
if (this.declaringClass.getMetadata().hasAnnotation(Configuration.class.getName()) && !getMetadata().isOverridable()) {
|
||||
problemReporter.error(new NonOverridableMethodError());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link Bean} methods must be non-private in order to accommodate CGLIB.
|
||||
* {@link Bean} methods must be overridable in order to accommodate CGLIB.
|
||||
*/
|
||||
private class PrivateMethodError extends Problem {
|
||||
private class NonOverridableMethodError extends Problem {
|
||||
|
||||
public PrivateMethodError() {
|
||||
super(String.format("Method '%s' must not be private; increase the method's visibility to continue",
|
||||
getName()), ConfigurationClassMethod.this.getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link Bean} methods must be non-final in order to accommodate CGLIB.
|
||||
*/
|
||||
private class FinalMethodError extends Problem {
|
||||
|
||||
public FinalMethodError() {
|
||||
super(String.format("Method '%s' must not be final; remove the final modifier to continue",
|
||||
getName()), ConfigurationClassMethod.this.getLocation());
|
||||
public NonOverridableMethodError() {
|
||||
super(String.format("Method '%s' must not be private, final or static; change the method's modifiers to continue",
|
||||
getMetadata().getMethodName()), getResourceLocation());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,23 @@
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
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.type.classreading.SimpleMetadataReader;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
|
||||
/**
|
||||
* Parses a {@link Configuration} class definition, populating a configuration model.
|
||||
@@ -41,19 +51,20 @@ import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
*/
|
||||
class ConfigurationClassParser {
|
||||
|
||||
private final SimpleMetadataReaderFactory metadataReaderFactory;
|
||||
private final MetadataReaderFactory metadataReaderFactory;
|
||||
|
||||
private final ProblemReporter problemReporter;
|
||||
|
||||
private final Set<ConfigurationClass> model;
|
||||
|
||||
private final Stack<ConfigurationClass> importStack = new ImportStack();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigurationClassParser} instance that will be used to populate a
|
||||
* configuration model.
|
||||
* @param model model to be populated by each successive call to {@link #parse}
|
||||
* Create a new {@link ConfigurationClassParser} instance that will be used
|
||||
* to populate a configuration model.
|
||||
*/
|
||||
public ConfigurationClassParser(SimpleMetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter) {
|
||||
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter) {
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
this.problemReporter = problemReporter;
|
||||
this.model = new LinkedHashSet<ConfigurationClass>();
|
||||
@@ -67,17 +78,80 @@ class ConfigurationClassParser {
|
||||
* (assumes that this configuration class was configured via XML)
|
||||
*/
|
||||
public void parse(String className, String beanName) throws IOException {
|
||||
SimpleMetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
|
||||
ConfigurationClass configClass = new ConfigurationClass();
|
||||
configClass.setBeanName(beanName);
|
||||
reader.getClassReader().accept(new ConfigurationClassVisitor(configClass, model, problemReporter, metadataReaderFactory), false);
|
||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
|
||||
processConfigurationClass(new ConfigurationClass(reader, beanName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the specified {@link Configuration @Configuration} class.
|
||||
* @param clazz the Clazz to parse
|
||||
* @param beanName may be null, but if populated represents the bean id
|
||||
* (assumes that this configuration class was configured via XML)
|
||||
*/
|
||||
public void parse(Class clazz, String beanName) throws IOException {
|
||||
processConfigurationClass(new ConfigurationClass(clazz, beanName));
|
||||
}
|
||||
|
||||
|
||||
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
|
||||
AnnotationMetadata metadata = configClass.getMetadata();
|
||||
while (metadata != null) {
|
||||
doProcessConfigurationClass(configClass, metadata);
|
||||
String superClassName = metadata.getSuperClassName();
|
||||
if (superClassName != null && !Object.class.getName().equals(superClassName)) {
|
||||
if (metadata instanceof StandardAnnotationMetadata) {
|
||||
Class clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass();
|
||||
metadata = new StandardAnnotationMetadata(clazz.getSuperclass());
|
||||
}
|
||||
else {
|
||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superClassName);
|
||||
metadata = reader.getAnnotationMetadata();
|
||||
}
|
||||
}
|
||||
else {
|
||||
metadata = null;
|
||||
}
|
||||
}
|
||||
model.add(configClass);
|
||||
}
|
||||
|
||||
protected void doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException {
|
||||
if (metadata.hasAnnotation(Import.class.getName())) {
|
||||
processImport(configClass, (String[]) metadata.getAnnotationAttributes(Import.class.getName()).get("value"));
|
||||
}
|
||||
Set<MethodMetadata> methods = metadata.getAnnotatedMethods(Bean.class.getName());
|
||||
for (MethodMetadata methodMetadata : methods) {
|
||||
configClass.addMethod(new ConfigurationClassMethod(methodMetadata, configClass));
|
||||
}
|
||||
}
|
||||
|
||||
public void processImport(ConfigurationClass configClass, String[] classesToImport) throws IOException {
|
||||
if (this.importStack.contains(configClass)) {
|
||||
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
|
||||
}
|
||||
else {
|
||||
this.importStack.push(configClass);
|
||||
for (String classToImport : classesToImport) {
|
||||
processClassToImport(classToImport);
|
||||
}
|
||||
this.importStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
private void processClassToImport(String classToImport) throws IOException {
|
||||
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(classToImport);
|
||||
AnnotationMetadata metadata = reader.getAnnotationMetadata();
|
||||
if (!metadata.hasAnnotation(Configuration.class.getName())) {
|
||||
this.problemReporter.error(
|
||||
new NonAnnotatedConfigurationProblem(metadata.getClassName(), reader.getResource()));
|
||||
}
|
||||
else {
|
||||
processConfigurationClass(new ConfigurationClass(reader, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurse through the model validating each {@link ConfigurationClass}.
|
||||
* @param problemReporter {@link ProblemReporter} against which any validation errors
|
||||
* will be registered
|
||||
* @see ConfigurationClass#validate
|
||||
*/
|
||||
public void validate() {
|
||||
@@ -90,4 +164,78 @@ class ConfigurationClassParser {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class ImportStack extends Stack<ConfigurationClass> {
|
||||
|
||||
/**
|
||||
* Simplified contains() implementation that tests to see if any {@link ConfigurationClass}
|
||||
* exists within this stack that has the same name as <var>elem</var>. Elem must be of
|
||||
* type ConfigurationClass.
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(Object elem) {
|
||||
ConfigurationClass configClass = (ConfigurationClass) elem;
|
||||
Comparator<ConfigurationClass> comparator = new Comparator<ConfigurationClass>() {
|
||||
public int compare(ConfigurationClass first, ConfigurationClass second) {
|
||||
return first.getMetadata().getClassName().equals(second.getMetadata().getClassName()) ? 0 : 1;
|
||||
}
|
||||
};
|
||||
return (Collections.binarySearch(this, configClass, comparator) != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a stack containing (in order)
|
||||
* <ol>
|
||||
* <li>com.acme.Foo</li>
|
||||
* <li>com.acme.Bar</li>
|
||||
* <li>com.acme.Baz</li>
|
||||
* </ol>
|
||||
* Returns "Foo->Bar->Baz". In the case of an empty stack, returns empty string.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Iterator<ConfigurationClass> iterator = iterator();
|
||||
while (iterator.hasNext()) {
|
||||
builder.append(iterator.next().getSimpleName());
|
||||
if (iterator.hasNext()) {
|
||||
builder.append("->");
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configuration classes must be annotated with {@link Configuration @Configuration}.
|
||||
*/
|
||||
private static class NonAnnotatedConfigurationProblem extends Problem {
|
||||
|
||||
public NonAnnotatedConfigurationProblem(String className, Resource resource) {
|
||||
super(String.format("%s was imported as a @Configuration class but was not actually annotated " +
|
||||
"with @Configuration. Annotate the class or do not attempt to process it.", className),
|
||||
new Location(resource));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link Problem} registered upon detection of a circular {@link Import}.
|
||||
*/
|
||||
private static class CircularImportProblem extends Problem {
|
||||
|
||||
public CircularImportProblem(ConfigurationClass attemptedImport, Stack<ConfigurationClass> importStack) {
|
||||
super(String.format("A circular @Import has been detected: " +
|
||||
"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " +
|
||||
"already present in the current import stack [%s]",
|
||||
importStack.peek().getSimpleName(), attemptedImport.getSimpleName(),
|
||||
attemptedImport.getSimpleName(), importStack),
|
||||
new Location(importStack.peek().getResource())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,23 +35,23 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
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.StandardAnnotationMetadata;
|
||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
|
||||
* {@link Configuration @Configuration} classes.
|
||||
* <p>
|
||||
* Registered by default when using {@literal <context:annotation-config/>} or
|
||||
*
|
||||
* <p>Registered by default when using {@literal <context:annotation-config/>} or
|
||||
* {@literal <context:component-scan/>}. Otherwise, may be declared manually as
|
||||
* with any other BeanFactoryPostProcessor.
|
||||
* <p>
|
||||
* This post processor is {@link Ordered#HIGHEST_PRECEDENCE} as it's important
|
||||
*
|
||||
* <p>This post processor is {@link Ordered#HIGHEST_PRECEDENCE} as it is important
|
||||
* that any {@link Bean} methods declared in Configuration classes have their
|
||||
* respective bean definitions registered before any other BeanFactoryPostProcessor
|
||||
* executes.
|
||||
@@ -62,32 +62,33 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor, BeanClassLoaderAware {
|
||||
|
||||
public static final String CONFIGURATION_CLASS_ATTRIBUTE =
|
||||
private static final String CONFIGURATION_CLASS_ATTRIBUTE =
|
||||
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");
|
||||
|
||||
private static final String CONFIGURATION_CLASS_FULL = "full";
|
||||
|
||||
private static final String CONFIGURATION_CLASS_LITE = "lite";
|
||||
|
||||
|
||||
/** Whether the CGLIB2 library is present on the classpath */
|
||||
private static final boolean cglibAvailable = ClassUtils.isPresent(
|
||||
"net.sf.cglib.proxy.Enhancer", ConfigurationClassPostProcessor.class.getClassLoader());
|
||||
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ConfigurationClassPostProcessor.class);
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/**
|
||||
* Used to register any problems detected with {@link Configuration} or {@link Bean}
|
||||
* declarations. For instance, a Bean method marked as {@literal final} is illegal
|
||||
* and would be reported as a problem. Defaults to {@link FailFastProblemReporter},
|
||||
* but is overridable with {@link #setProblemReporter}
|
||||
*/
|
||||
private ProblemReporter problemReporter = new FailFastProblemReporter();
|
||||
|
||||
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
|
||||
|
||||
private SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
|
||||
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
|
||||
|
||||
|
||||
/**
|
||||
* Override the default {@link ProblemReporter}.
|
||||
* @param problemReporter custom problem reporter
|
||||
* Set the {@link ProblemReporter} to use.
|
||||
* <p>Used to register any problems detected with {@link Configuration} or {@link Bean}
|
||||
* declarations. For instance, a Bean method marked as {@literal final} is illegal
|
||||
* and would be reported as a problem. Defaults to {@link FailFastProblemReporter}.
|
||||
*/
|
||||
public void setProblemReporter(ProblemReporter problemReporter) {
|
||||
this.problemReporter = problemReporter;
|
||||
@@ -116,34 +117,38 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor
|
||||
enhanceConfigurationClasses(beanFactory);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build and validate a configuration model based on the registry of
|
||||
* {@link Configuration} classes.
|
||||
*/
|
||||
protected final void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
|
||||
Set<BeanDefinitionHolder> configBeanDefs = new LinkedHashSet<BeanDefinitionHolder>();
|
||||
Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();
|
||||
for (String beanName : registry.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
|
||||
if (checkConfigurationClassBeanDefinition(beanDef)) {
|
||||
configBeanDefs.add(new BeanDefinitionHolder(beanDef, beanName));
|
||||
if (checkConfigurationClassCandidate(beanDef)) {
|
||||
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
|
||||
}
|
||||
}
|
||||
|
||||
// Return immediately if no @Configuration classes were found
|
||||
if (configBeanDefs.isEmpty()) {
|
||||
if (configCandidates.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate a new configuration model by parsing each @Configuration classes
|
||||
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter);
|
||||
for (BeanDefinitionHolder holder : configBeanDefs) {
|
||||
String beanClassName = holder.getBeanDefinition().getBeanClassName();
|
||||
for (BeanDefinitionHolder holder : configCandidates) {
|
||||
BeanDefinition bd = holder.getBeanDefinition();
|
||||
try {
|
||||
parser.parse(beanClassName, holder.getBeanName());
|
||||
if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
|
||||
parser.parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
|
||||
}
|
||||
else {
|
||||
parser.parse(bd.getBeanClassName(), holder.getBeanName());
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load bean class [" + beanClassName + "]", ex);
|
||||
throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);
|
||||
}
|
||||
}
|
||||
parser.validate();
|
||||
@@ -152,6 +157,42 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor
|
||||
new ConfigurationClassBeanDefinitionReader(registry).loadBeanDefinitions(parser.getModel());
|
||||
}
|
||||
|
||||
private boolean checkConfigurationClassCandidate(BeanDefinition beanDef) {
|
||||
AnnotationMetadata metadata;
|
||||
|
||||
// Check already loaded Class if present...
|
||||
// since we possibly can't even load the class file for this Class.
|
||||
if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
|
||||
Class beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
|
||||
metadata = new StandardAnnotationMetadata(beanClass);
|
||||
}
|
||||
else {
|
||||
String className = beanDef.getBeanClassName();
|
||||
try {
|
||||
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(className);
|
||||
metadata = metadataReader.getAnnotationMetadata();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Could not find class file for introspecting factory methods: " + className, ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata.hasAnnotation(Configuration.class.getName())) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
|
||||
return true;
|
||||
}
|
||||
else if (metadata.hasAnnotation(Component.class.getName())) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-processes a BeanFactory in search of Configuration class BeanDefinitions;
|
||||
* any candidates are then enhanced by a {@link ConfigurationClassEnhancer}.
|
||||
@@ -162,7 +203,7 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor
|
||||
Set<BeanDefinitionHolder> configBeanDefs = new LinkedHashSet<BeanDefinitionHolder>();
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
|
||||
if ("full".equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE))) {
|
||||
if (CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE))) {
|
||||
configBeanDefs.add(new BeanDefinitionHolder(beanDef, beanName));
|
||||
}
|
||||
}
|
||||
@@ -197,48 +238,4 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the BeanDefinition's beanClass (or its ancestry) is
|
||||
* {@link Configuration}-annotated, false if no beanClass is specified.
|
||||
*/
|
||||
private boolean checkConfigurationClassBeanDefinition(BeanDefinition beanDef) {
|
||||
// accommodating SPR-5655
|
||||
if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
|
||||
if (AnnotationUtils.findAnnotation(
|
||||
((AbstractBeanDefinition) beanDef).getBeanClass(), Configuration.class) != null) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full");
|
||||
return true;
|
||||
}
|
||||
else if (AnnotationUtils.findAnnotation(
|
||||
((AbstractBeanDefinition) beanDef).getBeanClass(), Component.class) != null) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "lite");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
String className = beanDef.getBeanClassName();
|
||||
while (className != null && !(className.equals(Object.class.getName()))) {
|
||||
try {
|
||||
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(className);
|
||||
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
|
||||
if (metadata.hasAnnotation(Configuration.class.getName())) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full");
|
||||
return true;
|
||||
}
|
||||
if (metadata.hasAnnotation(Component.class.getName())) {
|
||||
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "lite");
|
||||
return true;
|
||||
}
|
||||
className = metadata.getSuperClassName();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load class file [" + className + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,282 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import static java.lang.String.*;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Various utility methods commonly used when interacting with ASM, classloading
|
||||
* and creating {@link ConfigurationClassAnnotation} instances.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @since 3.0
|
||||
*/
|
||||
class ConfigurationClassReaderUtils {
|
||||
|
||||
/**
|
||||
* Convert a type descriptor to a classname suitable for classloading with
|
||||
* Class.forName().
|
||||
*
|
||||
* @param typeDescriptor see ASM guide section 2.1.3
|
||||
*/
|
||||
public static String convertAsmTypeDescriptorToClassName(String typeDescriptor) {
|
||||
final String internalName; // See ASM guide section 2.1.2
|
||||
|
||||
if ("V".equals(typeDescriptor))
|
||||
return Void.class.getName();
|
||||
if ("I".equals(typeDescriptor))
|
||||
return Integer.class.getName();
|
||||
if ("Z".equals(typeDescriptor))
|
||||
return Boolean.class.getName();
|
||||
|
||||
// strip the leading array/object/primitive identifier
|
||||
if (typeDescriptor.startsWith("[["))
|
||||
internalName = typeDescriptor.substring(3);
|
||||
else if (typeDescriptor.startsWith("["))
|
||||
internalName = typeDescriptor.substring(2);
|
||||
else
|
||||
internalName = typeDescriptor.substring(1);
|
||||
|
||||
// convert slashes to dots
|
||||
String className = internalName.replace('/', '.');
|
||||
|
||||
// and strip trailing semicolon (if present)
|
||||
if (className.endsWith(";"))
|
||||
className = className.substring(0, internalName.length() - 1);
|
||||
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param methodDescriptor see ASM guide section 2.1.4
|
||||
*/
|
||||
public static String getReturnTypeFromAsmMethodDescriptor(String methodDescriptor) {
|
||||
String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')') + 1);
|
||||
return convertAsmTypeDescriptorToClassName(returnTypeDescriptor);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Class<? extends Annotation> loadAnnotationType(String annoTypeDesc, ClassLoader classLoader) {
|
||||
String annoTypeName = ConfigurationClassReaderUtils.convertAsmTypeDescriptorToClassName(annoTypeDesc);
|
||||
try {
|
||||
return (Class<? extends Annotation>) classLoader.loadClass(annoTypeName);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException("Could not load annotation type [" + annoTypeName + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ConfigurationClassAnnotation} for {@code annoType}. JDK dynamic proxies are used,
|
||||
* and the returned proxy implements both {@link ConfigurationClassAnnotation} and the annotation type.
|
||||
* @param annoType annotation type that must be supplied and returned
|
||||
* @param annoType type of annotation to create
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static ConfigurationClassAnnotation createMutableAnnotation(Class<? extends Annotation> annoType, ClassLoader classLoader) {
|
||||
MutableAnnotationInvocationHandler handler = new MutableAnnotationInvocationHandler(annoType);
|
||||
Class<?>[] interfaces = new Class<?>[] {annoType, ConfigurationClassAnnotation.class};
|
||||
return (ConfigurationClassAnnotation) Proxy.newProxyInstance(classLoader, interfaces, handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles calls to {@link ConfigurationClassAnnotation} attribute methods at runtime. Essentially
|
||||
* emulates what JDK annotation dynamic proxies do.
|
||||
*/
|
||||
private static final class MutableAnnotationInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Class<? extends Annotation> annoType;
|
||||
private final Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
private final Map<String, Class<?>> attributeTypes = new HashMap<String, Class<?>>();
|
||||
|
||||
public MutableAnnotationInvocationHandler(Class<? extends Annotation> annoType) {
|
||||
// pre-populate the attributes hash will all the names
|
||||
// and default values of the attributes defined in 'annoType'
|
||||
Method[] attribs = annoType.getDeclaredMethods();
|
||||
for (Method attrib : attribs) {
|
||||
this.attributes.put(attrib.getName(), AnnotationUtils.getDefaultValue(annoType, attrib.getName()));
|
||||
this.attributeTypes.put(attrib.getName(), getAttributeType(annoType, attrib.getName()));
|
||||
}
|
||||
|
||||
this.annoType = annoType;
|
||||
}
|
||||
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
Assert.isInstanceOf(Annotation.class, proxy);
|
||||
|
||||
String methodName = method.getName();
|
||||
|
||||
// first -> check to see if this method is an attribute on our annotation
|
||||
if (attributes.containsKey(methodName))
|
||||
return attributes.get(methodName);
|
||||
|
||||
|
||||
// second -> is it a method from java.lang.annotation.Annotation?
|
||||
if (methodName.equals("annotationType"))
|
||||
return annoType;
|
||||
|
||||
|
||||
// third -> is it a method from java.lang.Object?
|
||||
if (methodName.equals("toString"))
|
||||
return format("@%s(%s)", annoType.getName(), getAttribs());
|
||||
|
||||
if (methodName.equals("equals"))
|
||||
return isEqualTo(proxy, args[0]);
|
||||
|
||||
if (methodName.equals("hashCode"))
|
||||
return calculateHashCode(proxy);
|
||||
|
||||
|
||||
// finally -> is it a method specified by MutableAnno?
|
||||
if (methodName.equals("setAttributeValue")) {
|
||||
attributes.put((String) args[0], args[1]);
|
||||
return null; // setAttributeValue has a 'void' return type
|
||||
}
|
||||
|
||||
if (methodName.equals("getAttributeType"))
|
||||
return attributeTypes.get(args[0]);
|
||||
|
||||
throw new UnsupportedOperationException("this proxy does not support method: " + methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conforms to the hashCode() specification for Annotation.
|
||||
*
|
||||
* @see Annotation#hashCode()
|
||||
*/
|
||||
private Object calculateHashCode(Object proxy) {
|
||||
int sum = 0;
|
||||
|
||||
for (String attribName : attributes.keySet()) {
|
||||
Object attribValue = attributes.get(attribName);
|
||||
|
||||
final int attribNameHashCode = attribName.hashCode();
|
||||
final int attribValueHashCode;
|
||||
|
||||
if (attribValue == null)
|
||||
// memberValue may be null when a mutable annotation is being added to a
|
||||
// collection
|
||||
// and before it has actually been visited (and populated) by
|
||||
// MutableAnnotationVisitor
|
||||
attribValueHashCode = 0;
|
||||
else if (attribValue.getClass().isArray())
|
||||
attribValueHashCode = Arrays.hashCode((Object[]) attribValue);
|
||||
else
|
||||
attribValueHashCode = attribValue.hashCode();
|
||||
|
||||
sum += (127 * attribNameHashCode) ^ attribValueHashCode;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares <var>proxy</var> object and <var>other</var> object by comparing the return
|
||||
* values of the methods specified by their common {@link Annotation} ancestry.
|
||||
* <p/>
|
||||
* <var>other</var> must be the same type as or a subtype of <var>proxy</var>. Will
|
||||
* return false otherwise.
|
||||
* <p/>
|
||||
* Eagerly returns true if {@code proxy} == <var>other</var>
|
||||
* </p>
|
||||
* <p/>
|
||||
* Conforms strictly to the equals() specification for Annotation
|
||||
* </p>
|
||||
*
|
||||
* @see Annotation#equals(Object)
|
||||
*/
|
||||
private Object isEqualTo(Object proxy, Object other) {
|
||||
if (proxy == other)
|
||||
return true;
|
||||
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
if (!annoType.isAssignableFrom(other.getClass()))
|
||||
return false;
|
||||
|
||||
for (String attribName : attributes.keySet()) {
|
||||
Object thisVal;
|
||||
Object thatVal;
|
||||
|
||||
try {
|
||||
thisVal = attributes.get(attribName);
|
||||
thatVal = other.getClass().getDeclaredMethod(attribName).invoke(other);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
|
||||
if ((thisVal == null) && (thatVal != null))
|
||||
return false;
|
||||
|
||||
if ((thatVal == null) && (thisVal != null))
|
||||
return false;
|
||||
|
||||
if (thatVal.getClass().isArray()) {
|
||||
if (!Arrays.equals((Object[]) thatVal, (Object[]) thisVal)) {
|
||||
return false;
|
||||
}
|
||||
} else if (thisVal instanceof Double) {
|
||||
if (!Double.valueOf((Double) thisVal).equals(Double.valueOf((Double) thatVal)))
|
||||
return false;
|
||||
} else if (thisVal instanceof Float) {
|
||||
if (!Float.valueOf((Float) thisVal).equals(Float.valueOf((Float) thatVal)))
|
||||
return false;
|
||||
} else if (!thisVal.equals(thatVal)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getAttribs() {
|
||||
List<String> attribs = new ArrayList<String>();
|
||||
for (String attribName : attributes.keySet()) {
|
||||
attribs.add(format("%s=%s", attribName, attributes.get(attribName)));
|
||||
}
|
||||
return StringUtils.collectionToDelimitedString(attribs, ", ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of the given annotation attribute.
|
||||
*/
|
||||
private static Class<?> getAttributeType(Class<? extends Annotation> annotationType, String attributeName) {
|
||||
try {
|
||||
return annotationType.getDeclaredMethod(attributeName).getReturnType();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not introspect return type", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,445 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.springframework.asm.AnnotationVisitor;
|
||||
import org.springframework.asm.Attribute;
|
||||
import org.springframework.asm.ClassAdapter;
|
||||
import org.springframework.asm.ClassReader;
|
||||
import org.springframework.asm.ClassVisitor;
|
||||
import org.springframework.asm.FieldVisitor;
|
||||
import org.springframework.asm.Label;
|
||||
import org.springframework.asm.MethodAdapter;
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.asm.Opcodes;
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.asm.commons.EmptyVisitor;
|
||||
import org.springframework.beans.factory.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;
|
||||
|
||||
/**
|
||||
* ASM {@link ClassVisitor} that visits a {@link Configuration} class, populating a
|
||||
* {@link ConfigurationClass} instance with information gleaned along the way.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see ConfigurationClassParser
|
||||
* @see ConfigurationClass
|
||||
*/
|
||||
class ConfigurationClassVisitor implements ClassVisitor {
|
||||
|
||||
private static final String OBJECT_DESC = ClassUtils.convertClassNameToResourcePath(Object.class.getName());
|
||||
|
||||
private final ConfigurationClass configClass;
|
||||
|
||||
private final Set<ConfigurationClass> model;
|
||||
|
||||
private final ProblemReporter problemReporter;
|
||||
|
||||
private final SimpleMetadataReaderFactory metadataReaderFactory;
|
||||
|
||||
private final Stack<ConfigurationClass> importStack;
|
||||
|
||||
|
||||
public ConfigurationClassVisitor(ConfigurationClass configClass, Set<ConfigurationClass> model,
|
||||
ProblemReporter problemReporter, SimpleMetadataReaderFactory metadataReaderFactory) {
|
||||
|
||||
this.configClass = configClass;
|
||||
this.model = model;
|
||||
this.problemReporter = problemReporter;
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
this.importStack = new ImportStack();
|
||||
}
|
||||
|
||||
private ConfigurationClassVisitor(ConfigurationClass configClass, Set<ConfigurationClass> model,
|
||||
ProblemReporter problemReporter, SimpleMetadataReaderFactory metadataReaderFactory,
|
||||
Stack<ConfigurationClass> importStack) {
|
||||
this.configClass = configClass;
|
||||
this.model = model;
|
||||
this.problemReporter = problemReporter;
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
this.importStack = importStack;
|
||||
}
|
||||
|
||||
|
||||
public void visit(int classVersion, int modifiers, String classTypeDesc, String arg3, String superTypeDesc, String[] arg5) {
|
||||
visitSuperType(superTypeDesc);
|
||||
|
||||
configClass.setName(ClassUtils.convertResourcePathToClassName(classTypeDesc));
|
||||
|
||||
// ASM always adds ACC_SUPER to the opcodes/modifiers for class definitions.
|
||||
// Unknown as to why (JavaDoc is silent on the matter), but it should be
|
||||
// eliminated in order to comply with java.lang.reflect.Modifier values.
|
||||
configClass.setModifiers(modifiers - Opcodes.ACC_SUPER);
|
||||
}
|
||||
|
||||
private void visitSuperType(String superTypeDesc) {
|
||||
// traverse up the type hierarchy unless the next ancestor is java.lang.Object
|
||||
if (OBJECT_DESC.equals(superTypeDesc)) {
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
String resourcePath =
|
||||
ClassUtils.convertClassNameToResourcePath(configClass.getName())
|
||||
.substring(0, configClass.getName().lastIndexOf('.') + 1).concat(sourceFile);
|
||||
|
||||
configClass.setSource(resourcePath);
|
||||
}
|
||||
|
||||
public void visitOuterClass(String s, String s1, String s2) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits a class level annotation on a {@link Configuration @Configuration} class.
|
||||
* <p>Upon encountering such an annotation, updates the {@link #configClass} model
|
||||
* object appropriately, and then returns an {@link AnnotationVisitor} implementation
|
||||
* that can populate the annotation appropriately with its attribute data as parsed
|
||||
* by ASM.
|
||||
* @see ConfigurationClassAnnotation
|
||||
* @see Configuration
|
||||
* @see Lazy
|
||||
* @see Import
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
|
||||
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);
|
||||
return new ImportAnnotationVisitor(model, problemReporter, classLoader);
|
||||
}
|
||||
problemReporter.error(new CircularImportProblem(configClass, importStack));
|
||||
}
|
||||
|
||||
ConfigurationClassAnnotation mutableAnnotation = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader);
|
||||
configClass.addAnnotation(mutableAnnotation);
|
||||
return new ConfigurationClassAnnotationVisitor(mutableAnnotation, classLoader);
|
||||
}
|
||||
|
||||
public void visitAttribute(Attribute attribute) {
|
||||
}
|
||||
|
||||
public void visitInnerClass(String s, String s1, String s2, int i) {
|
||||
}
|
||||
|
||||
public FieldVisitor visitField(int i, String s, String s1, String s2, Object o) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates all {@link Configuration @Configuration} class method parsing to
|
||||
* {@link ConfigurationClassMethodVisitor}.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ASM {@link MethodVisitor} that visits a single method declared in a given
|
||||
* {@link Configuration} class. Determines whether the method is a {@link Bean}
|
||||
* method and if so, adds it to the {@link ConfigurationClass}.
|
||||
*/
|
||||
private class ConfigurationClassMethodVisitor extends MethodAdapter {
|
||||
|
||||
private final ConfigurationClass configClass;
|
||||
private final String methodName;
|
||||
private final int modifiers;
|
||||
private final ConfigurationClassMethod.ReturnType returnType;
|
||||
private final List<Annotation> annotations = new ArrayList<Annotation>();
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private int lineNumber;
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigurationClassMethodVisitor} instance.
|
||||
* @param configClass model object to which this method will be added
|
||||
* @param methodName name of the method declared in the {@link Configuration} class
|
||||
* @param methodDescriptor ASM representation of the method signature
|
||||
* @param modifiers modifiers for this method
|
||||
*/
|
||||
public ConfigurationClassMethodVisitor(ConfigurationClass configClass, String methodName,
|
||||
String methodDescriptor, int modifiers, ClassLoader classLoader) {
|
||||
super(new EmptyVisitor());
|
||||
this.configClass = configClass;
|
||||
this.methodName = methodName;
|
||||
this.classLoader = classLoader;
|
||||
this.modifiers = modifiers;
|
||||
this.returnType = initReturnTypeFromMethodDescriptor(methodDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits a single annotation on this method. Will be called once for each annotation
|
||||
* present (regardless of its RetentionPolicy).
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public AnnotationVisitor visitAnnotation(String annoTypeDesc, boolean visible) {
|
||||
Class<? extends Annotation> annoClass = ConfigurationClassReaderUtils.loadAnnotationType(annoTypeDesc, classLoader);
|
||||
if (annoClass == null) {
|
||||
return super.visitAnnotation(annoTypeDesc, visible);
|
||||
}
|
||||
ConfigurationClassAnnotation annotation = ConfigurationClassReaderUtils.createMutableAnnotation(annoClass, classLoader);
|
||||
annotations.add(annotation);
|
||||
return new ConfigurationClassAnnotationVisitor(annotation, classLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the line number of this method within its declaring class. In reality, this
|
||||
* number is always inaccurate - <var>lineNo</var> represents the line number of the
|
||||
* first instruction in this method. Method declaration line numbers are not in any way
|
||||
* tracked in the bytecode. Any tooling or output that reads this value will have to
|
||||
* compensate and estimate where the actual method declaration is.
|
||||
*/
|
||||
@Override
|
||||
public void visitLineNumber(int lineNo, Label start) {
|
||||
this.lineNumber = lineNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses through all {@link #annotations} on this method in order to determine whether
|
||||
* it is a {@link Bean} method and if so adds it to the enclosing {@link #configClass}.
|
||||
*/
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
for (Annotation anno : annotations) {
|
||||
if (Bean.class.equals(anno.annotationType())) {
|
||||
// this method is annotated with @Bean -> add it to the ConfigurationClass model
|
||||
Annotation[] annoArray = annotations.toArray(new Annotation[annotations.size()]);
|
||||
ConfigurationClassMethod method = new ConfigurationClassMethod(methodName, modifiers, returnType, annoArray);
|
||||
method.setSource(lineNumber);
|
||||
configClass.addMethod(method);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines return type from ASM <var>methodDescriptor</var> and determines whether
|
||||
* that type is an interface.
|
||||
*/
|
||||
private ConfigurationClassMethod.ReturnType initReturnTypeFromMethodDescriptor(String methodDescriptor) {
|
||||
final ConfigurationClassMethod.ReturnType returnType = new ConfigurationClassMethod.ReturnType(ConfigurationClassReaderUtils.getReturnTypeFromAsmMethodDescriptor(methodDescriptor));
|
||||
// detect whether the return type is an interface
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ASM {@link AnnotationVisitor} implementation that reads an {@link Import} annotation
|
||||
* for all its specified classes and then one by one processes each class with a new
|
||||
* {@link ConfigurationClassVisitor}.
|
||||
*/
|
||||
private class ImportAnnotationVisitor implements AnnotationVisitor{
|
||||
|
||||
private final Set<ConfigurationClass> model;
|
||||
|
||||
private final ProblemReporter problemReporter;
|
||||
|
||||
private final List<String> classesToImport = new ArrayList<String>();
|
||||
|
||||
public ImportAnnotationVisitor(
|
||||
Set<ConfigurationClass> model, ProblemReporter problemReporter, ClassLoader classLoader) {
|
||||
|
||||
this.model = model;
|
||||
this.problemReporter = problemReporter;
|
||||
}
|
||||
|
||||
public void visit(String s, Object o) {
|
||||
}
|
||||
|
||||
public void visitEnum(String s, String s1, String s2) {
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String s, String s1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitArray(String attribName) {
|
||||
Assert.isTrue("value".equals(attribName));
|
||||
return new AnnotationVisitor() {
|
||||
public void visit(String na, Object type) {
|
||||
Assert.isInstanceOf(Type.class, type);
|
||||
classesToImport.add(((Type) type).getClassName());
|
||||
}
|
||||
public void visitEnum(String s, String s1, String s2) {
|
||||
}
|
||||
public AnnotationVisitor visitAnnotation(String s, String s1) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
public AnnotationVisitor visitArray(String s) {
|
||||
return new EmptyVisitor();
|
||||
}
|
||||
public void visitEnd() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
for (String classToImport : classesToImport) {
|
||||
processClassToImport(classToImport);
|
||||
}
|
||||
importStack.pop();
|
||||
}
|
||||
|
||||
private void processClassToImport(String classToImport) {
|
||||
ConfigurationClass configClass = new ConfigurationClass();
|
||||
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);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new BeanDefinitionStoreException("Failed to load imported configuration class [" + classToImport + "]", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class ImportStack extends Stack<ConfigurationClass> {
|
||||
|
||||
/**
|
||||
* Simplified contains() implementation that tests to see if any {@link ConfigurationClass}
|
||||
* exists within this stack that has the same name as <var>elem</var>. Elem must be of
|
||||
* type ConfigurationClass.
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(Object elem) {
|
||||
if (!(elem instanceof ConfigurationClass)) {
|
||||
return false;
|
||||
}
|
||||
ConfigurationClass configClass = (ConfigurationClass) elem;
|
||||
Comparator<ConfigurationClass> comparator = new Comparator<ConfigurationClass>() {
|
||||
public int compare(ConfigurationClass first, ConfigurationClass second) {
|
||||
return first.getName().equals(second.getName()) ? 0 : 1;
|
||||
}
|
||||
};
|
||||
return (Collections.binarySearch(this, configClass, comparator) != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a stack containing (in order)
|
||||
* <ol>
|
||||
* <li>com.acme.Foo</li>
|
||||
* <li>com.acme.Bar</li>
|
||||
* <li>com.acme.Baz</li>
|
||||
* </ol>
|
||||
* Returns "Foo->Bar->Baz". In the case of an empty stack, returns empty string.
|
||||
*/
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Iterator<ConfigurationClass> iterator = iterator();
|
||||
while (iterator.hasNext()) {
|
||||
builder.append(iterator.next().getSimpleName());
|
||||
if (iterator.hasNext()) {
|
||||
builder.append("->");
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Configuration classes must be annotated with {@link Configuration @Configuration}. */
|
||||
private static class NonAnnotatedConfigurationProblem extends Problem {
|
||||
|
||||
public NonAnnotatedConfigurationProblem(String className, Location location) {
|
||||
super(String.format("%s was imported as a @Configuration class but was not actually annotated " +
|
||||
"with @Configuration. Annotate the class or do not attempt to process it.", className), location);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link Problem} registered upon detection of a circular {@link Import}.
|
||||
* @see Import
|
||||
*/
|
||||
private static class CircularImportProblem extends Problem {
|
||||
|
||||
public CircularImportProblem(ConfigurationClass attemptedImport, Stack<ConfigurationClass> importStack) {
|
||||
super(String.format("A circular @Import has been detected: " +
|
||||
"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " +
|
||||
"already present in the current import stack [%s]",
|
||||
importStack.peek().getSimpleName(), attemptedImport.getSimpleName(),
|
||||
attemptedImport.getSimpleName(), importStack),
|
||||
new Location(new ClassPathResource(
|
||||
ClassUtils.convertClassNameToResourcePath(importStack.peek().getName())),
|
||||
importStack.peek().getSource())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user