001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.context.annotation;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.HashSet;
022import java.util.LinkedHashMap;
023import java.util.LinkedHashSet;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
032import org.springframework.beans.PropertyValues;
033import org.springframework.beans.factory.BeanClassLoaderAware;
034import org.springframework.beans.factory.BeanDefinitionStoreException;
035import org.springframework.beans.factory.BeanFactory;
036import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
037import org.springframework.beans.factory.config.BeanDefinition;
038import org.springframework.beans.factory.config.BeanDefinitionHolder;
039import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
040import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
041import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
042import org.springframework.beans.factory.config.SingletonBeanRegistry;
043import org.springframework.beans.factory.parsing.FailFastProblemReporter;
044import org.springframework.beans.factory.parsing.PassThroughSourceExtractor;
045import org.springframework.beans.factory.parsing.ProblemReporter;
046import org.springframework.beans.factory.parsing.SourceExtractor;
047import org.springframework.beans.factory.support.AbstractBeanDefinition;
048import org.springframework.beans.factory.support.BeanDefinitionRegistry;
049import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
050import org.springframework.beans.factory.support.BeanNameGenerator;
051import org.springframework.context.EnvironmentAware;
052import org.springframework.context.ResourceLoaderAware;
053import org.springframework.context.annotation.ConfigurationClassEnhancer.EnhancedConfiguration;
054import org.springframework.core.Ordered;
055import org.springframework.core.PriorityOrdered;
056import org.springframework.core.env.Environment;
057import org.springframework.core.env.StandardEnvironment;
058import org.springframework.core.io.DefaultResourceLoader;
059import org.springframework.core.io.ResourceLoader;
060import org.springframework.core.type.AnnotationMetadata;
061import org.springframework.core.type.MethodMetadata;
062import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
063import org.springframework.core.type.classreading.MetadataReaderFactory;
064import org.springframework.lang.Nullable;
065import org.springframework.util.Assert;
066import org.springframework.util.ClassUtils;
067
068/**
069 * {@link BeanFactoryPostProcessor} used for bootstrapping processing of
070 * {@link Configuration @Configuration} classes.
071 *
072 * <p>Registered by default when using {@code <context:annotation-config/>} or
073 * {@code <context:component-scan/>}. Otherwise, may be declared manually as
074 * with any other BeanFactoryPostProcessor.
075 *
076 * <p>This post processor is priority-ordered as it is important that any
077 * {@link Bean} methods declared in {@code @Configuration} classes have
078 * their corresponding bean definitions registered before any other
079 * {@link BeanFactoryPostProcessor} executes.
080 *
081 * @author Chris Beams
082 * @author Juergen Hoeller
083 * @author Phillip Webb
084 * @since 3.0
085 */
086public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
087                PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
088
089        /**
090         * A {@code BeanNameGenerator} using fully qualified class names as default bean names.
091         * <p>This default for configuration-level import purposes may be overridden through
092         * {@link #setBeanNameGenerator}. Note that the default for component scanning purposes
093         * is a plain {@link AnnotationBeanNameGenerator#INSTANCE}, unless overridden through
094         * {@link #setBeanNameGenerator} with a unified user-level bean name generator.
095         * @since 5.2
096         * @see #setBeanNameGenerator
097         */
098        public static final AnnotationBeanNameGenerator IMPORT_BEAN_NAME_GENERATOR =
099                        new FullyQualifiedAnnotationBeanNameGenerator();
100
101        private static final String IMPORT_REGISTRY_BEAN_NAME =
102                        ConfigurationClassPostProcessor.class.getName() + ".importRegistry";
103
104
105        private final Log logger = LogFactory.getLog(getClass());
106
107        private SourceExtractor sourceExtractor = new PassThroughSourceExtractor();
108
109        private ProblemReporter problemReporter = new FailFastProblemReporter();
110
111        @Nullable
112        private Environment environment;
113
114        private ResourceLoader resourceLoader = new DefaultResourceLoader();
115
116        @Nullable
117        private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
118
119        private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
120
121        private boolean setMetadataReaderFactoryCalled = false;
122
123        private final Set<Integer> registriesPostProcessed = new HashSet<>();
124
125        private final Set<Integer> factoriesPostProcessed = new HashSet<>();
126
127        @Nullable
128        private ConfigurationClassBeanDefinitionReader reader;
129
130        private boolean localBeanNameGeneratorSet = false;
131
132        /* Using short class names as default bean names by default. */
133        private BeanNameGenerator componentScanBeanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;
134
135        /* Using fully qualified class names as default bean names by default. */
136        private BeanNameGenerator importBeanNameGenerator = IMPORT_BEAN_NAME_GENERATOR;
137
138
139        @Override
140        public int getOrder() {
141                return Ordered.LOWEST_PRECEDENCE;  // within PriorityOrdered
142        }
143
144        /**
145         * Set the {@link SourceExtractor} to use for generated bean definitions
146         * that correspond to {@link Bean} factory methods.
147         */
148        public void setSourceExtractor(@Nullable SourceExtractor sourceExtractor) {
149                this.sourceExtractor = (sourceExtractor != null ? sourceExtractor : new PassThroughSourceExtractor());
150        }
151
152        /**
153         * Set the {@link ProblemReporter} to use.
154         * <p>Used to register any problems detected with {@link Configuration} or {@link Bean}
155         * declarations. For instance, an @Bean method marked as {@code final} is illegal
156         * and would be reported as a problem. Defaults to {@link FailFastProblemReporter}.
157         */
158        public void setProblemReporter(@Nullable ProblemReporter problemReporter) {
159                this.problemReporter = (problemReporter != null ? problemReporter : new FailFastProblemReporter());
160        }
161
162        /**
163         * Set the {@link MetadataReaderFactory} to use.
164         * <p>Default is a {@link CachingMetadataReaderFactory} for the specified
165         * {@linkplain #setBeanClassLoader bean class loader}.
166         */
167        public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) {
168                Assert.notNull(metadataReaderFactory, "MetadataReaderFactory must not be null");
169                this.metadataReaderFactory = metadataReaderFactory;
170                this.setMetadataReaderFactoryCalled = true;
171        }
172
173        /**
174         * Set the {@link BeanNameGenerator} to be used when triggering component scanning
175         * from {@link Configuration} classes and when registering {@link Import}'ed
176         * configuration classes. The default is a standard {@link AnnotationBeanNameGenerator}
177         * for scanned components (compatible with the default in {@link ClassPathBeanDefinitionScanner})
178         * and a variant thereof for imported configuration classes (using unique fully-qualified
179         * class names instead of standard component overriding).
180         * <p>Note that this strategy does <em>not</em> apply to {@link Bean} methods.
181         * <p>This setter is typically only appropriate when configuring the post-processor as a
182         * standalone bean definition in XML, e.g. not using the dedicated {@code AnnotationConfig*}
183         * application contexts or the {@code <context:annotation-config>} element. Any bean name
184         * generator specified against the application context will take precedence over any set here.
185         * @since 3.1.1
186         * @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
187         * @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
188         */
189        public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
190                Assert.notNull(beanNameGenerator, "BeanNameGenerator must not be null");
191                this.localBeanNameGeneratorSet = true;
192                this.componentScanBeanNameGenerator = beanNameGenerator;
193                this.importBeanNameGenerator = beanNameGenerator;
194        }
195
196        @Override
197        public void setEnvironment(Environment environment) {
198                Assert.notNull(environment, "Environment must not be null");
199                this.environment = environment;
200        }
201
202        @Override
203        public void setResourceLoader(ResourceLoader resourceLoader) {
204                Assert.notNull(resourceLoader, "ResourceLoader must not be null");
205                this.resourceLoader = resourceLoader;
206                if (!this.setMetadataReaderFactoryCalled) {
207                        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
208                }
209        }
210
211        @Override
212        public void setBeanClassLoader(ClassLoader beanClassLoader) {
213                this.beanClassLoader = beanClassLoader;
214                if (!this.setMetadataReaderFactoryCalled) {
215                        this.metadataReaderFactory = new CachingMetadataReaderFactory(beanClassLoader);
216                }
217        }
218
219
220        /**
221         * Derive further bean definitions from the configuration classes in the registry.
222         */
223        @Override
224        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
225                int registryId = System.identityHashCode(registry);
226                if (this.registriesPostProcessed.contains(registryId)) {
227                        throw new IllegalStateException(
228                                        "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
229                }
230                if (this.factoriesPostProcessed.contains(registryId)) {
231                        throw new IllegalStateException(
232                                        "postProcessBeanFactory already called on this post-processor against " + registry);
233                }
234                this.registriesPostProcessed.add(registryId);
235
236                processConfigBeanDefinitions(registry);
237        }
238
239        /**
240         * Prepare the Configuration classes for servicing bean requests at runtime
241         * by replacing them with CGLIB-enhanced subclasses.
242         */
243        @Override
244        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
245                int factoryId = System.identityHashCode(beanFactory);
246                if (this.factoriesPostProcessed.contains(factoryId)) {
247                        throw new IllegalStateException(
248                                        "postProcessBeanFactory already called on this post-processor against " + beanFactory);
249                }
250                this.factoriesPostProcessed.add(factoryId);
251                if (!this.registriesPostProcessed.contains(factoryId)) {
252                        // BeanDefinitionRegistryPostProcessor hook apparently not supported...
253                        // Simply call processConfigurationClasses lazily at this point then.
254                        processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
255                }
256
257                enhanceConfigurationClasses(beanFactory);
258                beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
259        }
260
261        /**
262         * Build and validate a configuration model based on the registry of
263         * {@link Configuration} classes.
264         */
265        public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
266                List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
267                String[] candidateNames = registry.getBeanDefinitionNames();
268
269                for (String beanName : candidateNames) {
270                        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
271                        if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
272                                if (logger.isDebugEnabled()) {
273                                        logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
274                                }
275                        }
276                        else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
277                                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
278                        }
279                }
280
281                // Return immediately if no @Configuration classes were found
282                if (configCandidates.isEmpty()) {
283                        return;
284                }
285
286                // Sort by previously determined @Order value, if applicable
287                configCandidates.sort((bd1, bd2) -> {
288                        int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
289                        int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
290                        return Integer.compare(i1, i2);
291                });
292
293                // Detect any custom bean name generation strategy supplied through the enclosing application context
294                SingletonBeanRegistry sbr = null;
295                if (registry instanceof SingletonBeanRegistry) {
296                        sbr = (SingletonBeanRegistry) registry;
297                        if (!this.localBeanNameGeneratorSet) {
298                                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
299                                                AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
300                                if (generator != null) {
301                                        this.componentScanBeanNameGenerator = generator;
302                                        this.importBeanNameGenerator = generator;
303                                }
304                        }
305                }
306
307                if (this.environment == null) {
308                        this.environment = new StandardEnvironment();
309                }
310
311                // Parse each @Configuration class
312                ConfigurationClassParser parser = new ConfigurationClassParser(
313                                this.metadataReaderFactory, this.problemReporter, this.environment,
314                                this.resourceLoader, this.componentScanBeanNameGenerator, registry);
315
316                Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
317                Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
318                do {
319                        parser.parse(candidates);
320                        parser.validate();
321
322                        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
323                        configClasses.removeAll(alreadyParsed);
324
325                        // Read the model and create bean definitions based on its content
326                        if (this.reader == null) {
327                                this.reader = new ConfigurationClassBeanDefinitionReader(
328                                                registry, this.sourceExtractor, this.resourceLoader, this.environment,
329                                                this.importBeanNameGenerator, parser.getImportRegistry());
330                        }
331                        this.reader.loadBeanDefinitions(configClasses);
332                        alreadyParsed.addAll(configClasses);
333
334                        candidates.clear();
335                        if (registry.getBeanDefinitionCount() > candidateNames.length) {
336                                String[] newCandidateNames = registry.getBeanDefinitionNames();
337                                Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
338                                Set<String> alreadyParsedClasses = new HashSet<>();
339                                for (ConfigurationClass configurationClass : alreadyParsed) {
340                                        alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
341                                }
342                                for (String candidateName : newCandidateNames) {
343                                        if (!oldCandidateNames.contains(candidateName)) {
344                                                BeanDefinition bd = registry.getBeanDefinition(candidateName);
345                                                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
346                                                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
347                                                        candidates.add(new BeanDefinitionHolder(bd, candidateName));
348                                                }
349                                        }
350                                }
351                                candidateNames = newCandidateNames;
352                        }
353                }
354                while (!candidates.isEmpty());
355
356                // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
357                if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
358                        sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
359                }
360
361                if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
362                        // Clear cache in externally provided MetadataReaderFactory; this is a no-op
363                        // for a shared cache since it'll be cleared by the ApplicationContext.
364                        ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
365                }
366        }
367
368        /**
369         * Post-processes a BeanFactory in search of Configuration class BeanDefinitions;
370         * any candidates are then enhanced by a {@link ConfigurationClassEnhancer}.
371         * Candidate status is determined by BeanDefinition attribute metadata.
372         * @see ConfigurationClassEnhancer
373         */
374        public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
375                Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
376                for (String beanName : beanFactory.getBeanDefinitionNames()) {
377                        BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
378                        Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
379                        MethodMetadata methodMetadata = null;
380                        if (beanDef instanceof AnnotatedBeanDefinition) {
381                                methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();
382                        }
383                        if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
384                                // Configuration class (full or lite) or a configuration-derived @Bean method
385                                // -> resolve bean class at this point...
386                                AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
387                                if (!abd.hasBeanClass()) {
388                                        try {
389                                                abd.resolveBeanClass(this.beanClassLoader);
390                                        }
391                                        catch (Throwable ex) {
392                                                throw new IllegalStateException(
393                                                                "Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
394                                        }
395                                }
396                        }
397                        if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
398                                if (!(beanDef instanceof AbstractBeanDefinition)) {
399                                        throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
400                                                        beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
401                                }
402                                else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
403                                        logger.info("Cannot enhance @Configuration bean definition '" + beanName +
404                                                        "' since its singleton instance has been created too early. The typical cause " +
405                                                        "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
406                                                        "return type: Consider declaring such methods as 'static'.");
407                                }
408                                configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
409                        }
410                }
411                if (configBeanDefs.isEmpty()) {
412                        // nothing to enhance -> return immediately
413                        return;
414                }
415
416                ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
417                for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
418                        AbstractBeanDefinition beanDef = entry.getValue();
419                        // If a @Configuration class gets proxied, always proxy the target class
420                        beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
421                        // Set enhanced subclass of the user-specified bean class
422                        Class<?> configClass = beanDef.getBeanClass();
423                        Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
424                        if (configClass != enhancedClass) {
425                                if (logger.isTraceEnabled()) {
426                                        logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
427                                                        "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
428                                }
429                                beanDef.setBeanClass(enhancedClass);
430                        }
431                }
432        }
433
434
435        private static class ImportAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
436
437                private final BeanFactory beanFactory;
438
439                public ImportAwareBeanPostProcessor(BeanFactory beanFactory) {
440                        this.beanFactory = beanFactory;
441                }
442
443                @Override
444                public PropertyValues postProcessProperties(@Nullable PropertyValues pvs, Object bean, String beanName) {
445                        // Inject the BeanFactory before AutowiredAnnotationBeanPostProcessor's
446                        // postProcessProperties method attempts to autowire other configuration beans.
447                        if (bean instanceof EnhancedConfiguration) {
448                                ((EnhancedConfiguration) bean).setBeanFactory(this.beanFactory);
449                        }
450                        return pvs;
451                }
452
453                @Override
454                public Object postProcessBeforeInitialization(Object bean, String beanName) {
455                        if (bean instanceof ImportAware) {
456                                ImportRegistry ir = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
457                                AnnotationMetadata importingClass = ir.getImportingClassFor(ClassUtils.getUserClass(bean).getName());
458                                if (importingClass != null) {
459                                        ((ImportAware) bean).setImportMetadata(importingClass);
460                                }
461                        }
462                        return bean;
463                }
464        }
465
466}