All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.context.annotation.ConfigurationClassParser Maven / Gradle / Ivy

/*
 * Copyright 2002-2016 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.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
import org.springframework.core.NestedIOException;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePropertySource;
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;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

/**
 * Parses a {@link Configuration} class definition, populating a collection of
 * {@link ConfigurationClass} objects (parsing a single Configuration class may result in
 * any number of ConfigurationClass objects because one Configuration class may import
 * another using the {@link Import} annotation).
 *
 * 

This class helps separate the concern of parsing the structure of a Configuration * class from the concern of registering BeanDefinition objects based on the * content of that model (with the exception of {@code @ComponentScan} annotations which * need to be registered immediately). * *

This ASM-based implementation avoids reflection and eager class loading in order to * interoperate effectively with lazy class loading in a Spring ApplicationContext. * * @author Chris Beams * @author Juergen Hoeller * @author Phillip Webb * @author Sam Brannen * @since 3.0 * @see ConfigurationClassBeanDefinitionReader */ class ConfigurationClassParser { private static final Comparator DEFERRED_IMPORT_COMPARATOR = new Comparator() { @Override public int compare(DeferredImportSelectorHolder o1, DeferredImportSelectorHolder o2) { return AnnotationAwareOrderComparator.INSTANCE.compare(o1.getImportSelector(), o2.getImportSelector()); } }; private final Log logger = LogFactory.getLog(getClass()); private final MetadataReaderFactory metadataReaderFactory; private final ProblemReporter problemReporter; private final Environment environment; private final ResourceLoader resourceLoader; private final BeanDefinitionRegistry registry; private final ComponentScanAnnotationParser componentScanParser; private final ConditionEvaluator conditionEvaluator; private final Map configurationClasses = new LinkedHashMap(); private final Map knownSuperclasses = new HashMap(); private final List propertySourceNames = new ArrayList(); private final ImportStack importStack = new ImportStack(); private List deferredImportSelectors; /** * Create a new {@link ConfigurationClassParser} instance that will be used * to populate the set of configuration classes. */ public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader, BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) { this.metadataReaderFactory = metadataReaderFactory; this.problemReporter = problemReporter; this.environment = environment; this.resourceLoader = resourceLoader; this.registry = registry; this.componentScanParser = new ComponentScanAnnotationParser( resourceLoader, environment, componentScanBeanNameGenerator, registry); this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader); } public void parse(Set configCandidates) { this.deferredImportSelectors = new LinkedList(); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Exception ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } processDeferredImportSelectors(); } protected final void parse(String className, String beanName) throws IOException { MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className); processConfigurationClass(new ConfigurationClass(reader, beanName)); } protected final void parse(Class clazz, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(clazz, beanName)); } protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(metadata, beanName)); } protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } // Otherwise ignore new imported config class; existing non-imported class overrides it. return; } else { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); for (Iterator it = this.knownSuperclasses.values().iterator(); it.hasNext(); ) { if (configClass.equals(it.next())) { it.remove(); } } } } // Recursively process the configuration class and its superclass hierarchy. SourceClass sourceClass = asSourceClass(configClass); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null); this.configurationClasses.put(configClass, configClass); } /** * 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, or {@code null} if none found or previously processed */ protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass); // Process any @PropertySource annotations for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // Process any @ComponentScan annotations AnnotationAttributes componentScan = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ComponentScan.class); if (componentScan != null && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set scannedBeanDefinitions = 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) { if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) { parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); } } } // Process any @Import annotations processImports(configClass, sourceClass, getImports(sourceClass), true); // Process any @ImportResource annotations if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) { AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); String[] resources = importResource.getAliasedStringArray("locations", ImportResource.class, sourceClass); Class readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods Set beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName()); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces for (SourceClass ifc : sourceClass.getInterfaces()) { beanMethods = ifc.getMetadata().getAnnotatedMethods(Bean.class.getName()); for (MethodMetadata methodMetadata : beanMethods) { if (!methodMetadata.isAbstract()) { // A default method or other concrete method on a Java 8+ interface... configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } } } // Process superclass, if any if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null; } /** * Register member (nested) classes that happen to be configuration classes themselves. * @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, SourceClass sourceClass) throws IOException { for (SourceClass memberClass : sourceClass.getMemberClasses()) { if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) && !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) { if (this.importStack.contains(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { processConfigurationClass(memberClass.asConfigClass(configClass)); } finally { this.importStack.pop(); } } } } } /** * Process the given @PropertySource annotation metadata. * @param propertySource metadata for the @PropertySource annotation found * @throws IOException if loading a property source failed */ private void processPropertySource(AnnotationAttributes propertySource) throws IOException { String name = propertySource.getString("name"); String[] locations = propertySource.getStringArray("value"); boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound"); Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required"); for (String location : locations) { try { String resolvedLocation = this.environment.resolveRequiredPlaceholders(location); Resource resource = this.resourceLoader.getResource(resolvedLocation); ResourcePropertySource rps = (StringUtils.hasText(name) ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource)); addPropertySource(rps); } catch (IllegalArgumentException ex) { // from resolveRequiredPlaceholders if (!ignoreResourceNotFound) { throw ex; } } catch (FileNotFoundException ex) { // from ResourcePropertySource constructor if (!ignoreResourceNotFound) { throw ex; } } } } private void addPropertySource(ResourcePropertySource propertySource) { String name = propertySource.getName(); MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources(); if (propertySources.contains(name) && this.propertySourceNames.contains(name)) { // We've already added a version, we need to extend it PropertySource existing = propertySources.get(name); if (existing instanceof CompositePropertySource) { ((CompositePropertySource) existing).addFirstPropertySource(propertySource.withResourceName()); } else { if (existing instanceof ResourcePropertySource) { existing = ((ResourcePropertySource) existing).withResourceName(); } CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource(propertySource.withResourceName()); composite.addPropertySource(existing); propertySources.replace(name, composite); } } else { if (this.propertySourceNames.isEmpty()) { propertySources.addLast(propertySource); } else { String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1); propertySources.addBefore(firstProcessed, propertySource); } } this.propertySourceNames.add(name); } /** * 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 of 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 * {@code @Import}s in addition to meta-imports originating from an {@code @Enable} * annotation. * @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(SourceClass sourceClass, Set imports, Set visited) throws IOException { if (visited.add(sourceClass)) { for (SourceClass annotation : sourceClass.getAnnotations()) { String annName = annotation.getMetadata().getClassName(); if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) { collectImports(annotation, imports, visited); } } imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); } } private void processDeferredImportSelectors() { List deferredImports = this.deferredImportSelectors; this.deferredImportSelectors = null; Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR); for (DeferredImportSelectorHolder deferredImport : deferredImports) { ConfigurationClass configClass = deferredImport.getConfigurationClass(); try { String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata()); processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Exception ex) { throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } } } private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection importCandidates, boolean checkForCircularImports) throws IOException { if (importCandidates.isEmpty()) { return; } if (checkForCircularImports && this.importStack.contains(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { for (SourceClass candidate : importCandidates) { if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); invokeAwareMethods(selector); if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) { this.deferredImportSelectors.add( new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector)); } else { String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection importSourceClasses = asSourceClasses(importClassNames); processImports(configClass, currentSourceClass, importSourceClasses, false); } } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); invokeAwareMethods(registrar); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass)); } } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Exception ex) { throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } finally { this.importStack.pop(); } } } /** * Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and * {@link BeanFactoryAware} contracts if implemented by the given {@code bean}. */ private void invokeAwareMethods(Object importStrategyBean) { if (importStrategyBean instanceof Aware) { if (importStrategyBean instanceof EnvironmentAware) { ((EnvironmentAware) importStrategyBean).setEnvironment(this.environment); } if (importStrategyBean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) importStrategyBean).setResourceLoader(this.resourceLoader); } if (importStrategyBean instanceof BeanClassLoaderAware) { ClassLoader classLoader = (this.registry instanceof ConfigurableBeanFactory ? ((ConfigurableBeanFactory) this.registry).getBeanClassLoader() : this.resourceLoader.getClassLoader()); ((BeanClassLoaderAware) importStrategyBean).setBeanClassLoader(classLoader); } if (importStrategyBean instanceof BeanFactoryAware && this.registry instanceof BeanFactory) { ((BeanFactoryAware) importStrategyBean).setBeanFactory((BeanFactory) this.registry); } } } /** * Validate each {@link ConfigurationClass} object. * @see ConfigurationClass#validate */ public void validate() { for (ConfigurationClass configClass : this.configurationClasses.keySet()) { configClass.validate(this.problemReporter); } } public Set getConfigurationClasses() { return this.configurationClasses.keySet(); } ImportRegistry getImportRegistry() { return this.importStack; } /** * Factory method to obtain a {@link SourceClass} from a {@link ConfigurationClass}. */ public SourceClass asSourceClass(ConfigurationClass configurationClass) throws IOException { AnnotationMetadata metadata = configurationClass.getMetadata(); if (metadata instanceof StandardAnnotationMetadata) { return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass()); } return asSourceClass(configurationClass.getMetadata().getClassName()); } /** * Factory method to obtain a {@link SourceClass} from a {@link Class}. */ public SourceClass asSourceClass(Class classType) throws IOException { try { // Sanity test that we can read annotations, if not fall back to ASM classType.getAnnotations(); return new SourceClass(classType); } catch (Throwable ex) { // Enforce ASM via class name resolution return asSourceClass(classType.getName()); } } /** * Factory method to obtain {@link SourceClass}s from class names. */ public Collection asSourceClasses(String[] classNames) throws IOException { List annotatedClasses = new ArrayList(); for (String className : classNames) { annotatedClasses.add(asSourceClass(className)); } return annotatedClasses; } /** * Factory method to obtain a {@link SourceClass} from a class name. */ public SourceClass asSourceClass(String className) throws IOException { if (className.startsWith("java")) { // Never use ASM for core java types try { return new SourceClass(this.resourceLoader.getClassLoader().loadClass(className)); } catch (ClassNotFoundException ex) { throw new NestedIOException("Failed to load class [" + className + "]", ex); } } return new SourceClass(this.metadataReaderFactory.getMetadataReader(className)); } @SuppressWarnings("serial") private static class ImportStack extends ArrayDeque implements ImportRegistry { private final MultiValueMap imports = new LinkedMultiValueMap(); public void registerImport(AnnotationMetadata importingClass, String importedClass) { this.imports.add(importedClass, importingClass); } @Override public void removeImportingClassFor(String importedClass) { for (List list : this.imports.values()) { for (Iterator iterator = list.iterator(); iterator.hasNext();) { if (iterator.next().getClassName().equals(importedClass)) { iterator.remove(); } } } } @Override public AnnotationMetadata getImportingClassFor(String importedClass) { List list = this.imports.get(importedClass); return (!CollectionUtils.isEmpty(list) ? list.get(list.size() - 1) : null); } /** * Given a stack containing (in order) *

    *
  • com.acme.Foo
  • *
  • com.acme.Bar
  • *
  • com.acme.Baz
  • *
* return "[Foo->Bar->Baz]". */ @Override public String toString() { StringBuilder builder = new StringBuilder("["); Iterator iterator = iterator(); while (iterator.hasNext()) { builder.append(iterator.next().getSimpleName()); if (iterator.hasNext()) { builder.append("->"); } } return builder.append(']').toString(); } } private static class DeferredImportSelectorHolder { private final ConfigurationClass configurationClass; private final DeferredImportSelector importSelector; public DeferredImportSelectorHolder(ConfigurationClass configurationClass, DeferredImportSelector importSelector) { this.configurationClass = configurationClass; this.importSelector = importSelector; } public ConfigurationClass getConfigurationClass() { return this.configurationClass; } public DeferredImportSelector getImportSelector() { return this.importSelector; } } /** * Simple wrapper that allows annotated source classes to be dealt with * in a uniform manner, regardless of how they are loaded. */ private class SourceClass { private final Object source; // Class or MetadataReader private final AnnotationMetadata metadata; public SourceClass(Object source) { this.source = source; if (source instanceof Class) { this.metadata = new StandardAnnotationMetadata((Class) source, true); } else { this.metadata = ((MetadataReader) source).getAnnotationMetadata(); } } public final AnnotationMetadata getMetadata() { return this.metadata; } public Class loadClass() throws ClassNotFoundException { if (this.source instanceof Class) { return (Class) this.source; } String className = ((MetadataReader) this.source).getClassMetadata().getClassName(); return resourceLoader.getClassLoader().loadClass(className); } public boolean isAssignable(Class clazz) throws IOException { if (this.source instanceof Class) { return clazz.isAssignableFrom((Class) this.source); } return new AssignableTypeFilter(clazz).match((MetadataReader) this.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) this.source, importedBy); } public Collection getMemberClasses() throws IOException { Object sourceToProcess = this.source; if (sourceToProcess instanceof Class) { Class sourceClass = (Class) sourceToProcess; try { Class[] declaredClasses = sourceClass.getDeclaredClasses(); List members = new ArrayList(declaredClasses.length); for (Class declaredClass : declaredClasses) { members.add(asSourceClass(declaredClass)); } return members; } catch (NoClassDefFoundError err) { // getDeclaredClasses() failed because of non-resolvable dependencies // -> fall back to ASM below sourceToProcess = metadataReaderFactory.getMetadataReader(sourceClass.getName()); } } // ASM-based resolution - safe for non-resolvable classes as well MetadataReader sourceReader = (MetadataReader) sourceToProcess; String[] memberClassNames = sourceReader.getClassMetadata().getMemberClassNames(); List members = new ArrayList(memberClassNames.length); for (String memberClassName : memberClassNames) { try { members.add(asSourceClass(memberClassName)); } catch (IOException ex) { // Let's skip it if it's not resolvable - we're just looking for candidates if (logger.isDebugEnabled()) { logger.debug("Failed to resolve member class [" + memberClassName + "] - not considering it as a configuration class candidate"); } } } return members; } public SourceClass getSuperClass() throws IOException { if (this.source instanceof Class) { return asSourceClass(((Class) this.source).getSuperclass()); } return asSourceClass(((MetadataReader) this.source).getClassMetadata().getSuperClassName()); } public Set getInterfaces() throws IOException { Set result = new LinkedHashSet(); if (this.source instanceof Class) { Class sourceClass = (Class) this.source; for (Class ifcClass : sourceClass.getInterfaces()) { result.add(asSourceClass(ifcClass)); } } else { for (String className : this.metadata.getInterfaceNames()) { result.add(asSourceClass(className)); } } return result; } public Set getAnnotations() throws IOException { Set result = new LinkedHashSet(); for (String className : this.metadata.getAnnotationTypes()) { try { result.add(getRelated(className)); } catch (Throwable ex) { // An annotation not present on the classpath is being ignored // by the JVM's class loading -> ignore here as well. } } return result; } public Collection getAnnotationAttributes(String annotationType, String attribute) throws IOException { Map annotationAttributes = this.metadata.getAnnotationAttributes(annotationType, true); if (annotationAttributes == null || !annotationAttributes.containsKey(attribute)) { return Collections.emptySet(); } String[] classNames = (String[]) annotationAttributes.get(attribute); Set result = new LinkedHashSet(); for (String className : classNames) { result.add(getRelated(className)); } return result; } private SourceClass getRelated(String className) throws IOException { if (this.source instanceof Class) { try { Class clazz = resourceLoader.getClassLoader().loadClass(className); return asSourceClass(clazz); } catch (ClassNotFoundException ex) { // Ignore -> fall back to ASM next, except for core java types. if (className.startsWith("java")) { throw new NestedIOException("Failed to load class [" + className + "]", ex); } return new SourceClass(metadataReaderFactory.getMetadataReader(className)); } } return asSourceClass(className); } @Override public boolean equals(Object other) { return (this == other || (other instanceof SourceClass && this.metadata.getClassName().equals(((SourceClass) other).metadata.getClassName()))); } @Override public int hashCode() { return this.metadata.getClassName().hashCode(); } @Override public String toString() { return this.metadata.getClassName(); } } /** * {@link Problem} registered upon detection of a circular {@link Import}. */ private static class CircularImportProblem extends Problem { public CircularImportProblem(ConfigurationClass attemptedImport, Deque 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(), attemptedImport.getMetadata())); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy