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