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}