Open Source Repository

Home /spring/spring-context-3.0.5 | Repository Home



org/springframework/context/annotation/ConfigurationClassPostProcessor.java
/*
 * Copyright 2002-2010 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.IOException;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

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

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.parsing.PassThroughSourceExtractor;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.parsing.SourceExtractor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 {@link BeanFactoryPostProcessor} used for bootstrapping processing of
 {@link Configuration @Configuration} classes.
 *
 <p>Registered by default when using {@literal <context:annotation-config/>} or
 * {@literal <context:component-scan/>}. Otherwise, may be declared manually as
 * with any other BeanFactoryPostProcessor.
 *
 <p>This post processor is {@link Ordered#HIGHEST_PRECEDENCE} as it is important
 * that any {@link Bean} methods declared in Configuration classes have their
 * respective bean definitions registered before any other BeanFactoryPostProcessor
 * executes.
 
 @author Chris Beams
 @author Juergen Hoeller
 @since 3.0
 */
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, BeanClassLoaderAware {

  /** Whether the CGLIB2 library is present on the classpath */
  private static final boolean cglibAvailable = ClassUtils.isPresent(
      "net.sf.cglib.proxy.Enhancer", ConfigurationClassPostProcessor.class.getClassLoader());


  private final Log logger = LogFactory.getLog(getClass());

  private SourceExtractor sourceExtractor = new PassThroughSourceExtractor();

  private ProblemReporter problemReporter = new FailFastProblemReporter();

  private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

  private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();

  private boolean setMetadataReaderFactoryCalled = false;

  private boolean postProcessBeanDefinitionRegistryCalled = false;

  private boolean postProcessBeanFactoryCalled = false;


  /**
   * Set the {@link SourceExtractor} to use for generated bean definitions
   * that correspond to {@link Bean} factory methods.
   */
  public void setSourceExtractor(SourceExtractor sourceExtractor) {
    this.sourceExtractor = (sourceExtractor != null ? sourceExtractor : new PassThroughSourceExtractor());
  }

  /**
   * Set the {@link ProblemReporter} to use.
   <p>Used to register any problems detected with {@link Configuration} or {@link Bean}
   * declarations. For instance, an @Bean method marked as {@literal final} is illegal
   * and would be reported as a problem. Defaults to {@link FailFastProblemReporter}.
   */
  public void setProblemReporter(ProblemReporter problemReporter) {
    this.problemReporter = (problemReporter != null ? problemReporter : new FailFastProblemReporter());
  }

  /**
   * Set the {@link MetadataReaderFactory} to use.
   <p>Default is a {@link CachingMetadataReaderFactory} for the specified
   {@link #setBeanClassLoader bean class loader}.
   */
  public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) {
    Assert.notNull(metadataReaderFactory, "MetadataReaderFactory must not be null");
    this.metadataReaderFactory = metadataReaderFactory;
    this.setMetadataReaderFactoryCalled = true;
  }

  public void setBeanClassLoader(ClassLoader beanClassLoader) {
    this.beanClassLoader = beanClassLoader;
    if (!this.setMetadataReaderFactoryCalled) {
      this.metadataReaderFactory = new CachingMetadataReaderFactory(beanClassLoader);
    }
  }

  public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE;
  }


  /**
   * Derive further bean definitions from the configuration classes in the registry.
   */
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.postProcessBeanDefinitionRegistryCalled) {
      throw new IllegalStateException(
          "postProcessBeanDefinitionRegistry already called for this post-processor");
    }
    if (this.postProcessBeanFactoryCalled) {
      throw new IllegalStateException(
          "postProcessBeanFactory already called for this post-processor");
    }
    this.postProcessBeanDefinitionRegistryCalled = true;
    processConfigBeanDefinitions(registry);
  }

  /**
   * Prepare the Configuration classes for servicing bean requests at runtime
   * by replacing them with CGLIB-enhanced subclasses.
   */
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    if (this.postProcessBeanFactoryCalled) {
      throw new IllegalStateException(
          "postProcessBeanFactory already called for this post-processor");
    }
    this.postProcessBeanFactoryCalled = true;
    if (!this.postProcessBeanDefinitionRegistryCalled) {
      // BeanDefinitionRegistryPostProcessor hook apparently not supported...
      // Simply call processConfigBeanDefinitions lazily at this point then.
      processConfigBeanDefinitions((BeanDefinitionRegistrybeanFactory);
    }
    enhanceConfigurationClasses(beanFactory);
  }


  /**
   * Build and validate a configuration model based on the registry of
   {@link Configuration} classes.
   */
  public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();
    for (String beanName : registry.getBeanDefinitionNames()) {
      BeanDefinition beanDef = registry.getBeanDefinition(beanName);
      if (ConfigurationClassBeanDefinitionReader.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
        configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
      }
    }

    // Return immediately if no @Configuration classes were found
    if (configCandidates.isEmpty()) {
      return;
    }

    // Populate a new configuration model by parsing each @Configuration classes
    ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter);
    for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      try {
        if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinitionbd).hasBeanClass()) {
          parser.parse(((AbstractBeanDefinitionbd).getBeanClass(), holder.getBeanName());
        }
        else {
          parser.parse(bd.getBeanClassName(), holder.getBeanName());
        }
      }
      catch (IOException ex) {
        throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);
      }
    }
    parser.validate();

    // Read the model and create bean definitions based on its content
    ConfigurationClassBeanDefinitionReader reader = new ConfigurationClassBeanDefinitionReader(
        registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory);
    reader.loadBeanDefinitions(parser.getConfigurationClasses());
  }

  /**
   * Post-processes a BeanFactory in search of Configuration class BeanDefinitions;
   * any candidates are then enhanced by a {@link ConfigurationClassEnhancer}.
   * Candidate status is determined by BeanDefinition attribute metadata.
   @see ConfigurationClassEnhancer
   */
  public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
      BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
      if (ConfigurationClassBeanDefinitionReader.isFullConfigurationClass(beanDef)) {
        if (!(beanDef instanceof AbstractBeanDefinition)) {
          throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
              beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
        }
        configBeanDefs.put(beanName, (AbstractBeanDefinitionbeanDef);
      }
    }
    if (configBeanDefs.isEmpty()) {
      // nothing to enhance -> return immediately
      return;
    }
    if (!cglibAvailable) {
      throw new IllegalStateException("CGLIB is required to process @Configuration classes. " +
          "Either add CGLIB to the classpath or remove the following @Configuration bean definitions: " +
          configBeanDefs.keySet());
    }
    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer(beanFactory);
    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
      AbstractBeanDefinition beanDef = entry.getValue();
      try {
        Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
        Class<?> enhancedClass = enhancer.enhance(configClass);
        if (logger.isDebugEnabled()) {
          logger.debug(String.format("Replacing bean definition '%s' existing class name '%s' " +
              "with enhanced class name '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
        }
        beanDef.setBeanClass(enhancedClass);
      }
      catch (Throwable ex) {
        throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
      }
    }
  }

}