Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up
Find file
Copy path
spring-framework/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java /
Find file
Copy path
Fetching contributors…
| /* | |
| * Copyright 2002-2017 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.BeanDefinitionStoreException; | |
| 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.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.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.DefaultPropertySourceFactory; | |
| import org.springframework.core.io.support.EncodedResource; | |
| import org.springframework.core.io.support.PropertySourceFactory; | |
| 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). | |
| * | |
| * <p>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). | |
| * | |
| * <p>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 PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory(); | |
| private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR = | |
| new Comparator<ConfigurationClassParser.DeferredImportSelectorHolder>() { | |
| @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<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>(); | |
| private final Map<String, ConfigurationClass> knownSuperclasses = new HashMap<>(); | |
| private final List<String> propertySourceNames = new ArrayList<>(); | |
| private final ImportStack importStack = new ImportStack(); | |
| private List<DeferredImportSelectorHolder> 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( | |
| environment, resourceLoader, componentScanBeanNameGenerator, registry); | |
| this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader); | |
| } | |
| public void parse(Set<BeanDefinitionHolder> 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 (Throwable 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<ConfigurationClass> 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 | |
| Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( | |
| sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); | |
| if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { | |
| for (AnnotationAttributes componentScan : componentScans) { | |
| // The config class is annotated with @ComponentScan -> perform the scan immediately | |
| Set<BeanDefinitionHolder> 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.getStringArray("locations"); | |
| Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); | |
| for (String resource : resources) { | |
| String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); | |
| configClass.addImportedResource(resolvedResource, readerClass); | |
| } | |
| } | |
| // Process individual @Bean methods | |
| Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); | |
| for (MethodMetadata methodMetadata : beanMethods) { | |
| configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); | |
| } | |
| // Process default methods on interfaces | |
| processInterfaces(configClass, sourceClass); | |
| // 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. | |
| */ | |
| 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(); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Register default methods on interfaces implemented by the configuration class. | |
| */ | |
| private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { | |
| for (SourceClass ifc : sourceClass.getInterfaces()) { | |
| Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc); | |
| 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)); | |
| } | |
| } | |
| processInterfaces(configClass, ifc); | |
| } | |
| } | |
| /** | |
| * Retrieve the metadata for all <code>@Bean</code> methods. | |
| */ | |
| private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) { | |
| AnnotationMetadata original = sourceClass.getMetadata(); | |
| Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName()); | |
| if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) { | |
| // Try reading the class file via ASM for deterministic declaration order... | |
| // Unfortunately, the JVM's standard reflection returns methods in arbitrary | |
| // order, even between different runs of the same application on the same JVM. | |
| try { | |
| AnnotationMetadata asm = | |
| this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata(); | |
| Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName()); | |
| if (asmMethods.size() >= beanMethods.size()) { | |
| Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size()); | |
| for (MethodMetadata asmMethod : asmMethods) { | |
| for (MethodMetadata beanMethod : beanMethods) { | |
| if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) { | |
| selectedMethods.add(beanMethod); | |
| break; | |
| } | |
| } | |
| } | |
| if (selectedMethods.size() == beanMethods.size()) { | |
| // All reflection-detected methods found in ASM method set -> proceed | |
| beanMethods = selectedMethods; | |
| } | |
| } | |
| } | |
| catch (IOException ex) { | |
| logger.debug("Failed to read class file via ASM for determining @Bean method order", ex); | |
| // No worries, let's continue with the reflection metadata we started with... | |
| } | |
| } | |
| return beanMethods; | |
| } | |
| /** | |
| * Process the given <code>@PropertySource</code> annotation metadata. | |
| * @param propertySource metadata for the <code>@PropertySource</code> annotation found | |
| * @throws IOException if loading a property source failed | |
| */ | |
| private void processPropertySource(AnnotationAttributes propertySource) throws IOException { | |
| String name = propertySource.getString("name"); | |
| if (!StringUtils.hasLength(name)) { | |
| name = null; | |
| } | |
| String encoding = propertySource.getString("encoding"); | |
| if (!StringUtils.hasLength(encoding)) { | |
| encoding = null; | |
| } | |
| String[] locations = propertySource.getStringArray("value"); | |
| Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required"); | |
| boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound"); | |
| Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory"); | |
| PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ? | |
| DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass)); | |
| for (String location : locations) { | |
| try { | |
| String resolvedLocation = this.environment.resolveRequiredPlaceholders(location); | |
| Resource resource = this.resourceLoader.getResource(resolvedLocation); | |
| addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding))); | |
| } | |
| catch (IllegalArgumentException | FileNotFoundException ex) { | |
| // Placeholders not resolvable or resource not found when trying to open it | |
| if (ignoreResourceNotFound) { | |
| if (logger.isInfoEnabled()) { | |
| logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage()); | |
| } | |
| } | |
| else { | |
| throw ex; | |
| } | |
| } | |
| } | |
| } | |
| private void addPropertySource(PropertySource<?> 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); | |
| PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ? | |
| ((ResourcePropertySource) propertySource).withResourceName() : propertySource); | |
| if (existing instanceof CompositePropertySource) { | |
| ((CompositePropertySource) existing).addFirstPropertySource(newSource); | |
| } | |
| else { | |
| if (existing instanceof ResourcePropertySource) { | |
| existing = ((ResourcePropertySource) existing).withResourceName(); | |
| } | |
| CompositePropertySource composite = new CompositePropertySource(name); | |
| composite.addPropertySource(newSource); | |
| 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<SourceClass> getImports(SourceClass sourceClass) throws IOException { | |
| Set<SourceClass> imports = new LinkedHashSet<>(); | |
| Set<SourceClass> 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. | |
| * <p>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<SourceClass> imports, Set<SourceClass> 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<DeferredImportSelectorHolder> 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 (Throwable ex) { | |
| throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + | |
| configClass.getMetadata().getClassName() + "]", ex); | |
| } | |
| } | |
| } | |
| private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, | |
| Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException { | |
| if (importCandidates.isEmpty()) { | |
| return; | |
| } | |
| if (checkForCircularImports && isChainedImportOnStack(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); | |
| ParserStrategyUtils.invokeAwareMethods( | |
| selector, this.environment, this.resourceLoader, this.registry); | |
| if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) { | |
| this.deferredImportSelectors.add( | |
| new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector)); | |
| } | |
| else { | |
| String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); | |
| Collection<SourceClass> 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); | |
| ParserStrategyUtils.invokeAwareMethods( | |
| registrar, this.environment, this.resourceLoader, this.registry); | |
| 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 (Throwable ex) { | |
| throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + | |
| configClass.getMetadata().getClassName() + "]", ex); | |
| } | |
| finally { | |
| this.importStack.pop(); | |
| } | |
| } | |
| } | |
| private boolean isChainedImportOnStack(ConfigurationClass configClass) { | |
| if (this.importStack.contains(configClass)) { | |
| String configClassName = configClass.getMetadata().getClassName(); | |
| AnnotationMetadata importingClass = this.importStack.getImportingClassFor(configClassName); | |
| while (importingClass != null) { | |
| if (configClassName.equals(importingClass.getClassName())) { | |
| return true; | |
| } | |
| importingClass = this.importStack.getImportingClassFor(importingClass.getClassName()); | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Validate each {@link ConfigurationClass} object. | |
| * @see ConfigurationClass#validate | |
| */ | |
| public void validate() { | |
| for (ConfigurationClass configClass : this.configurationClasses.keySet()) { | |
| configClass.validate(this.problemReporter); | |
| } | |
| } | |
| public Set<ConfigurationClass> 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(metadata.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<SourceClass> asSourceClasses(String[] classNames) throws IOException { | |
| List<SourceClass> 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<ConfigurationClass> implements ImportRegistry { | |
| private final MultiValueMap<String, AnnotationMetadata> imports = new LinkedMultiValueMap<>(); | |
| public void registerImport(AnnotationMetadata importingClass, String importedClass) { | |
| this.imports.add(importedClass, importingClass); | |
| } | |
| @Override | |
| public AnnotationMetadata getImportingClassFor(String importedClass) { | |
| List<AnnotationMetadata> list = this.imports.get(importedClass); | |
| return (!CollectionUtils.isEmpty(list) ? list.get(list.size() - 1) : null); | |
| } | |
| @Override | |
| public void removeImportingClass(String importingClass) { | |
| for (List<AnnotationMetadata> list : this.imports.values()) { | |
| for (Iterator<AnnotationMetadata> iterator = list.iterator(); iterator.hasNext();) { | |
| if (iterator.next().getClassName().equals(importingClass)) { | |
| iterator.remove(); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Given a stack containing (in order) | |
| * <ul> | |
| * <li>com.acme.Foo</li> | |
| * <li>com.acme.Bar</li> | |
| * <li>com.acme.Baz</li> | |
| * </ul> | |
| * return "[Foo->Bar->Baz]". | |
| */ | |
| @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.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<SourceClass> getMemberClasses() throws IOException { | |
| Object sourceToProcess = this.source; | |
| if (sourceToProcess instanceof Class) { | |
| Class<?> sourceClass = (Class<?>) sourceToProcess; | |
| try { | |
| Class<?>[] declaredClasses = sourceClass.getDeclaredClasses(); | |
| List<SourceClass> 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<SourceClass> 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<SourceClass> getInterfaces() throws IOException { | |
| Set<SourceClass> 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<SourceClass> getAnnotations() throws IOException { | |
| Set<SourceClass> 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<SourceClass> getAnnotationAttributes(String annotationType, String attribute) throws IOException { | |
| Map<String, Object> annotationAttributes = this.metadata.getAnnotationAttributes(annotationType, true); | |
| if (annotationAttributes == null || !annotationAttributes.containsKey(attribute)) { | |
| return Collections.emptySet(); | |
| } | |
| String[] classNames = (String[]) annotationAttributes.get(attribute); | |
| Set<SourceClass> 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 = ((Class<?>) this.source).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<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(), attemptedImport.getMetadata())); | |
| } | |
| } | |
| } |