diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index b7622fc2bb..489b79c59d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -17,8 +17,7 @@ package org.springframework.context.annotation; import java.io.IOException; -import java.lang.annotation.Annotation; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -63,7 +62,6 @@ import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AssignableTypeFilter; -import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import static org.springframework.context.annotation.MetadataUtils.*; @@ -193,38 +191,43 @@ class ConfigurationClassParser { } // Recursively process the configuration class and its superclass hierarchy. - AnnotationMetadata metadata = configClass.getMetadata(); + SourceClass sourceClass = asSourceClass(configClass); do { - metadata = doProcessConfigurationClass(configClass, metadata); + sourceClass = doProcessConfigurationClass(configClass, sourceClass); } - while (metadata != null); + while (sourceClass != null); this.configurationClasses.add(configClass); } /** - * @return annotation metadata of superclass, {@code null} if none found or previously processed + * Apply processing and build a complete {@link ConfigurationClass} by reading the + * annotations, members and methods from the source class. This method can be called + * multiple times as relevant sources are discovered. + * @param configClass the configuration class being build + * @param sourceClass a source class + * @return the superclass, {@code null} if none found or previously processed */ - protected AnnotationMetadata doProcessConfigurationClass( - ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { + protected final SourceClass doProcessConfigurationClass( + ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // recursively process any member (nested) classes first - processMemberClasses(configClass, metadata); + processMemberClasses(configClass, sourceClass); // process any @PropertySource annotations - AnnotationAttributes propertySource = attributesFor(metadata, org.springframework.context.annotation.PropertySource.class); + AnnotationAttributes propertySource = attributesFor(sourceClass.getMetadata(), org.springframework.context.annotation.PropertySource.class); if (propertySource != null) { processPropertySource(propertySource); } // process any @ComponentScan annotations - AnnotationAttributes componentScan = attributesFor(metadata, ComponentScan.class); + AnnotationAttributes componentScan = attributesFor(sourceClass.getMetadata(), ComponentScan.class); if (componentScan != null) { // the config class is annotated with @ComponentScan -> perform the scan immediately if (!ConditionEvaluator.get(configClass.getMetadata(), false).shouldSkip( this.registry, this.environment)) { Set scannedBeanDefinitions = - this.componentScanParser.parse(componentScan, metadata.getClassName()); + this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // check the set of scanned definitions for any further config classes and parse recursively if necessary for (BeanDefinitionHolder holder : scannedBeanDefinitions) { @@ -236,16 +239,11 @@ class ConfigurationClassParser { } // process any @Import annotations - Set imports = new LinkedHashSet(); - Set visited = new LinkedHashSet(); - collectImports(metadata, imports, visited); - if (!imports.isEmpty()) { - processImport(configClass, imports, true); - } + processImports(configClass, getImports(sourceClass), true); // process any @ImportResource annotations - if (metadata.isAnnotated(ImportResource.class.getName())) { - AnnotationAttributes importResource = attributesFor(metadata, ImportResource.class); + if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) { + AnnotationAttributes importResource = attributesFor(sourceClass.getMetadata(), ImportResource.class); String[] resources = importResource.getStringArray("value"); Class readerClass = importResource.getClass("reader"); for (String resource : resources) { @@ -254,34 +252,22 @@ class ConfigurationClassParser { } // process individual @Bean methods - Set beanMethods = metadata.getAnnotatedMethods(Bean.class.getName()); + Set beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName()); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // process superclass, if any - if (metadata.hasSuperClass()) { - String superclass = metadata.getSuperClassName(); + if (sourceClass.getMetadata().hasSuperClass()) { + String superclass = sourceClass.getMetadata().getSuperClassName(); if (!this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // superclass found, return its annotation metadata and recurse - if (metadata instanceof StandardAnnotationMetadata) { - Class clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass(); - return new StandardAnnotationMetadata(clazz.getSuperclass(), true); + try { + return sourceClass.getSuperClass(); } - else if (superclass.startsWith("java")) { - // never load core JDK classes via ASM, in particular not java.lang.Object! - try { - return new StandardAnnotationMetadata( - this.resourceLoader.getClassLoader().loadClass(superclass), true); - } - catch (ClassNotFoundException ex) { - throw new IllegalStateException(ex); - } - } - else { - MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superclass); - return reader.getAnnotationMetadata(); + catch (ClassNotFoundException ex) { + throw new IllegalStateException(ex); } } } @@ -292,24 +278,13 @@ class ConfigurationClassParser { /** * Register member (nested) classes that happen to be configuration classes themselves. - * @param metadata the metadata representation of the containing class + * @param sourceClass the source class to process * @throws IOException if there is any problem reading metadata from a member class */ - private void processMemberClasses(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { - if (metadata instanceof StandardAnnotationMetadata) { - for (Class memberClass : ((StandardAnnotationMetadata) metadata).getIntrospectedClass().getDeclaredClasses()) { - if (ConfigurationClassUtils.isConfigurationCandidate(new StandardAnnotationMetadata(memberClass))) { - processConfigurationClass(new ConfigurationClass(memberClass, configClass)); - } - } - } - else { - for (String memberClassName : metadata.getMemberClassNames()) { - MetadataReader reader = this.metadataReaderFactory.getMetadataReader(memberClassName); - AnnotationMetadata memberClassMetadata = reader.getAnnotationMetadata(); - if (ConfigurationClassUtils.isConfigurationCandidate(memberClassMetadata)) { - processConfigurationClass(new ConfigurationClass(reader, configClass)); - } + private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { + for (SourceClass memberClass : sourceClass.getMemberClasses()) { + if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata())) { + processConfigurationClass(memberClass.asConfigClass(configClass)); } } } @@ -349,59 +324,46 @@ class ConfigurationClassParser { } } + /** + * Returns {@code @Import} class, considering all meta-annotations. + */ + private Set getImports(SourceClass sourceClass) throws IOException { + Set imports = new LinkedHashSet(); + Set visited = new LinkedHashSet(); + collectImports(sourceClass, imports, visited); + return imports; + } + /** * Recursively collect all declared {@code @Import} values. Unlike most * meta-annotations it is valid to have several {@code @Import}s declared with * different values, the usual process or returning values from the first * meta-annotation on a class is not sufficient. - *

For example, it is common for a {@code @Configuration} class to declare direct + *

+ * For example, it is common for a {@code @Configuration} class to declare direct * {@code @Import}s in addition to meta-imports originating from an {@code @Enable} * annotation. - * @param metadata the metadata representation of the class to search + * + * @param sourceClass the class to search * @param imports the imports collected so far * @param visited used to track visited classes to prevent infinite recursion * @throws IOException if there is any problem reading metadata from the named class */ - private void collectImports(AnnotationMetadata metadata, Set imports, Set visited) throws IOException { - String className = metadata.getClassName(); - if (visited.add(className)) { - if (metadata instanceof StandardAnnotationMetadata) { - StandardAnnotationMetadata stdMetadata = (StandardAnnotationMetadata) metadata; - for (Annotation ann : stdMetadata.getIntrospectedClass().getAnnotations()) { - if (!ann.annotationType().getName().startsWith("java") && !(ann instanceof Import)) { - collectImports(new StandardAnnotationMetadata(ann.annotationType()), imports, visited); - } - } - Map attributes = stdMetadata.getAnnotationAttributes(Import.class.getName(), false); - if (attributes != null) { - Class[] value = (Class[]) attributes.get("value"); - if (!ObjectUtils.isEmpty(value)) { - imports.addAll(Arrays.asList(value)); - } - } - } - else { - for (String annotationType : metadata.getAnnotationTypes()) { - if (!className.startsWith("java") && !className.equals(Import.class.getName())) { - try { - collectImports( - new StandardAnnotationMetadata(this.resourceLoader.getClassLoader().loadClass(annotationType)), - imports, visited); - } - catch (ClassNotFoundException ex) { - // - } - } - } - Map attributes = metadata.getAnnotationAttributes(Import.class.getName(), true); - if (attributes != null) { - String[] value = (String[]) attributes.get("value"); - if (!ObjectUtils.isEmpty(value)) { - imports.addAll(Arrays.asList(value)); + private void collectImports(SourceClass sourceClass, Set imports, + Set visited) throws IOException { + try { + if (visited.add(sourceClass)) { + for (SourceClass annotation : sourceClass.getAnnotations()) { + if(!annotation.getMetadata().getClassName().startsWith("java") && !annotation.isAssignable(Import.class)) { + collectImports(annotation, imports, visited); } } + imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); } } + catch (ClassNotFoundException ex) { + throw new NestedIOException("Unable to collect imports", ex); + } } private void processDeferredImportSelectors() { @@ -410,16 +372,21 @@ class ConfigurationClassParser { try { ConfigurationClass configClass = deferredImport.getConfigurationClass(); String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata()); - processImport(configClass, Arrays.asList(imports), false); + processImports(configClass, asSourceClasses(imports), false); } - catch (IOException ex) { + catch (Exception ex) { throw new BeanDefinitionStoreException("Failed to load bean class: ", ex); } } this.deferredImportSelectors.clear(); } - private void processImport(ConfigurationClass configClass, Collection classesToImport, boolean checkForCircularImports) throws IOException { + private void processImports(ConfigurationClass configClass, + Collection sourceClasses, boolean checkForCircularImports) + throws IOException { + if(sourceClasses.isEmpty()) { + return; + } if (checkForCircularImports && this.importStack.contains(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata())); } @@ -427,26 +394,24 @@ class ConfigurationClassParser { this.importStack.push(configClass); AnnotationMetadata importingClassMetadata = configClass.getMetadata(); try { - for (Object candidate : classesToImport) { - Object candidateToCheck = (candidate instanceof Class ? (Class) candidate : - this.metadataReaderFactory.getMetadataReader((String) candidate)); - if (checkAssignability(ImportSelector.class, candidateToCheck)) { + for (SourceClass candidate : sourceClasses) { + if (candidate.isAssignable(ImportSelector.class)) { // the candidate class is an ImportSelector -> delegate to it to determine imports - Class candidateClass = (candidate instanceof Class ? (Class) candidate : - this.resourceLoader.getClassLoader().loadClass((String) candidate)); + Class candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); invokeAwareMethods(selector); if(selector instanceof DeferredImportSelector) { this.deferredImportSelectors.add(new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector)); } else { - processImport(configClass, Arrays.asList(selector.selectImports(importingClassMetadata)), false); + String[] importClassNames = selector.selectImports(importingClassMetadata); + Collection importSourceClasses = asSourceClasses(importClassNames); + processImports(configClass, importSourceClasses, false); } } - else if (checkAssignability(ImportBeanDefinitionRegistrar.class, candidateToCheck)) { + else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // the candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions - Class candidateClass = (candidate instanceof Class ? (Class) candidate : - this.resourceLoader.getClassLoader().loadClass((String) candidate)); + Class candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); invokeAwareMethods(registrar); configClass.addImportBeanDefinitionRegistrar(registrar); @@ -454,10 +419,8 @@ class ConfigurationClassParser { else { // candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> process it as a @Configuration class this.importStack.registerImport(importingClassMetadata.getClassName(), - (candidate instanceof Class ? ((Class) candidate).getName() : (String) candidate)); - processConfigurationClass((candidateToCheck instanceof Class ? - new ConfigurationClass((Class) candidateToCheck, configClass) : - new ConfigurationClass((MetadataReader) candidateToCheck, configClass))); + candidate.getMetadata().getClassName()); + processConfigurationClass(candidate.asConfigClass(configClass)); } } } @@ -470,15 +433,6 @@ class ConfigurationClassParser { } } - private boolean checkAssignability(Class clazz, Object candidate) throws IOException { - if (candidate instanceof Class) { - return clazz.isAssignableFrom((Class) candidate); - } - else { - return new AssignableTypeFilter(clazz).match((MetadataReader) candidate, this.metadataReaderFactory); - } - } - /** * Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and * {@link BeanFactoryAware} contracts if implemented by the given {@code bean}. @@ -526,10 +480,68 @@ class ConfigurationClassParser { return this.importStack; } + /** + * Factory method to obtain a {@link SourceClass} from a {@link ConfigurationClass}. + */ + public SourceClass asSourceClass(ConfigurationClass configurationClass) + throws IOException { + try { + AnnotationMetadata metadata = configurationClass.getMetadata(); + if (metadata instanceof StandardAnnotationMetadata) { + return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass()); + } + return asSourceClass(configurationClass.getMetadata().getClassName()); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException(ex); + } + } + + /** + * Factory method to obtain a {@link SourceClass} from a {@link Class}. + */ + public SourceClass asSourceClass(Class classType) + throws ClassNotFoundException, IOException { + try { + // Sanity test that we can read annotations, if not fall back to ASM + classType.getAnnotations(); + } + catch (Throwable ex) { + return asSourceClass(classType.getName()); + } + return new SourceClass(classType); + } + + /** + * Factory method to obtain {@link SourceClass}s from class names. + */ + public Collection asSourceClasses(String[] classNamess) + throws ClassNotFoundException, IOException { + List annotatedClasses = new ArrayList(); + for (String className : classNamess) { + annotatedClasses.add(asSourceClass(className)); + } + return annotatedClasses; + } + + /** + * Factory method to obtain a {@link SourceClass} from a class name. + */ + public SourceClass asSourceClass(String className) + throws ClassNotFoundException, IOException { + if (className.startsWith("java")) { + // Never use ASM for core java types + return new SourceClass(this.resourceLoader.getClassLoader().loadClass( + className)); + } + return new SourceClass(this.metadataReaderFactory.getMetadataReader(className)); + } + interface ImportRegistry { String getImportingClassFor(String importedClass); + } @@ -609,6 +621,151 @@ class ConfigurationClassParser { } + /** + * Simple wrapper that allows annotated source classes to be dealt with in a uniform + * manor, regardless of how they are loaded. + */ + private class SourceClass { + + private final Object source; // Class or MetaDataReader + + private final AnnotationMetadata metadata; + + + private SourceClass(Object source) { + this.source = source; + if (source instanceof Class) { + this.metadata = new StandardAnnotationMetadata((Class) source, true); + } + else { + this.metadata = ((MetadataReader) source).getAnnotationMetadata(); + } + } + + + public Class loadClass() throws ClassNotFoundException { + if(source instanceof Class) { + return (Class) source; + } + String className = ((MetadataReader) source).getClassMetadata().getClassName(); + return resourceLoader.getClassLoader().loadClass(className); + } + + public boolean isAssignable(Class clazz) throws IOException { + if (source instanceof Class) { + return clazz.isAssignableFrom((Class) source); + } + return new AssignableTypeFilter(clazz).match((MetadataReader) source, + metadataReaderFactory); + } + + public ConfigurationClass asConfigClass(ConfigurationClass importedBy) + throws IOException { + if (this.source instanceof Class) { + return new ConfigurationClass((Class) this.source, importedBy); + } + return new ConfigurationClass((MetadataReader) source, importedBy); + } + + public Collection getMemberClasses() throws IOException { + List members = new ArrayList(); + if (source instanceof Class) { + Class sourceClass = (Class) source; + for (Class declaredClass : sourceClass.getDeclaredClasses()) { + try { + members.add(asSourceClass(declaredClass)); + } + catch (ClassNotFoundException e) { + } + } + } + else { + MetadataReader sourceReader = (MetadataReader) source; + for (String memberClassName : sourceReader.getClassMetadata().getMemberClassNames()) { + try { + members.add(asSourceClass(memberClassName)); + } + catch (ClassNotFoundException e) { + } + } + } + return members; + } + + public SourceClass getSuperClass() throws ClassNotFoundException, IOException { + if (source instanceof Class) { + return asSourceClass(((Class) source).getSuperclass()); + } + return asSourceClass(((MetadataReader) source).getClassMetadata().getSuperClassName()); + } + + public Set getAnnotations() throws ClassNotFoundException, IOException { + Set annotations = new LinkedHashSet(); + for(String annotation : getMetadata().getAnnotationTypes()) { + annotations.add(getRelated(annotation)); + } + return annotations; + } + + public Collection getAnnotationAttributes(String annotationType, + String attribute) throws ClassNotFoundException, IOException { + Map annotationAttributes = getMetadata().getAnnotationAttributes( + annotationType, true); + if (annotationAttributes == null + || !annotationAttributes.containsKey(attribute)) { + return Collections.emptySet(); + } + String[] classNames = (String[]) annotationAttributes.get(attribute); + Set rtn = new LinkedHashSet(); + for (String className : classNames) { + rtn.add(getRelated(className)); + } + return rtn; + } + + private SourceClass getRelated(String className) throws IOException, + ClassNotFoundException { + if (source instanceof Class) { + try { + Class clazz = resourceLoader.getClassLoader().loadClass(className); + return asSourceClass(clazz); + } + catch (ClassNotFoundException ex) { + } + } + return asSourceClass(className); + } + + public AnnotationMetadata getMetadata() { + return this.metadata; + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null) { + return false; + } + if (obj instanceof SourceClass) { + return toString().equals(obj.toString()); + } + return false; + } + + @Override + public String toString() { + return getMetadata().getClassName(); + } + } + + /** * {@link Problem} registered upon detection of a circular {@link Import}. */