/* * Copyright 2002-2011 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.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; 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.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; 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.parsing.SourceExtractor; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.config.FeatureSpecification; import org.springframework.context.config.SpecificationContext; import org.springframework.core.Conventions; import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; 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.classreading.SimpleMetadataReaderFactory; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; /** * Reads a given fully-populated set of ConfigurationClass instances, registering bean * definitions with the given {@link BeanDefinitionRegistry} based on its contents. * *

This class was modeled after the {@link BeanDefinitionReader} hierarchy, but does * not implement/extend any of its artifacts as a set of configuration classes is not a * {@link Resource}. * * @author Chris Beams * @author Juergen Hoeller * @since 3.0 * @see ConfigurationClassParser */ public class ConfigurationClassBeanDefinitionReader { private static final String CONFIGURATION_CLASS_FULL = "full"; private static final String CONFIGURATION_CLASS_LITE = "lite"; private static final String CONFIGURATION_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass"); private static final Log logger = LogFactory.getLog(ConfigurationClassBeanDefinitionReader.class); private final BeanDefinitionRegistry registry; private final SourceExtractor sourceExtractor; private final ProblemReporter problemReporter; private final MetadataReaderFactory metadataReaderFactory; private ResourceLoader resourceLoader; private SpecificationContext specificationContext; /** * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used * to populate the given {@link BeanDefinitionRegistry}. * @param problemReporter * @param metadataReaderFactory */ public ConfigurationClassBeanDefinitionReader(final BeanDefinitionRegistry registry, SourceExtractor sourceExtractor, ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory, ResourceLoader resourceLoader, Environment environment) { this.registry = registry; this.sourceExtractor = sourceExtractor; this.problemReporter = problemReporter; this.metadataReaderFactory = metadataReaderFactory; this.resourceLoader = resourceLoader; // TODO SPR-7420: see about passing in the SpecificationContext created in ConfigurationClassPostProcessor this.specificationContext = new SpecificationContext(); this.specificationContext.setRegistry(this.registry); this.specificationContext.setRegistrar(new SimpleComponentRegistrar(this.registry)); this.specificationContext.setResourceLoader(this.resourceLoader); this.specificationContext.setEnvironment(environment); this.specificationContext.setProblemReporter(problemReporter); } /** * Read {@code configurationModel}, registering bean definitions with {@link #registry} * based on its contents. */ public void loadBeanDefinitions(Set configurationModel) { for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass); } } /** * Read a particular {@link ConfigurationClass}, registering bean definitions for the * class itself, all its {@link Bean} methods */ private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { AnnotationMetadata metadata = configClass.getMetadata(); processFeatureAnnotations(metadata); doLoadBeanDefinitionForConfigurationClassIfNecessary(configClass); for (ConfigurationClassMethod beanMethod : configClass.getMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); } private void processFeatureAnnotations(AnnotationMetadata metadata) { try { for (String annotationType : metadata.getAnnotationTypes()) { MetadataReader metadataReader = new SimpleMetadataReaderFactory().getMetadataReader(annotationType); if (metadataReader.getAnnotationMetadata().isAnnotated(FeatureAnnotation.class.getName())) { Map annotationAttributes = metadataReader.getAnnotationMetadata().getAnnotationAttributes(FeatureAnnotation.class.getName(), true); // TODO SPR-7420: this is where we can catch user-defined types and avoid instantiating them for STS purposes FeatureAnnotationParser processor = (FeatureAnnotationParser) BeanUtils.instantiateClass(Class.forName((String)annotationAttributes.get("parser"))); FeatureSpecification spec = processor.process(metadata); spec.execute(this.specificationContext); } } } catch (BeanDefinitionParsingException ex) { throw ex; } catch (Exception ex) { // TODO SPR-7420: what exception to throw? throw new RuntimeException(ex); } } /** * Register the {@link Configuration} class itself as a bean definition. */ private void doLoadBeanDefinitionForConfigurationClassIfNecessary(ConfigurationClass configClass) { if (configClass.getBeanName() != null) { // a bean definition already exists for this configuration class -> nothing to do return; } // no bean definition exists yet -> this must be an imported configuration class (@Import). BeanDefinition configBeanDef = new GenericBeanDefinition(); String className = configClass.getMetadata().getClassName(); configBeanDef.setBeanClassName(className); if (checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) { String configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(configBeanDef, this.registry); configClass.setBeanName(configBeanName); if (logger.isDebugEnabled()) { logger.debug(String.format("Registered bean definition for imported @Configuration class %s", configBeanName)); } } else { try { MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className); AnnotationMetadata metadata = reader.getAnnotationMetadata(); this.problemReporter.error( new InvalidConfigurationImportProblem(className, reader.getResource(), metadata)); } catch (IOException ex) { throw new IllegalStateException("Could not create MetadataReader for class " + className); } } } /** * Read a particular {@link ConfigurationClassMethod}, registering bean definitions * with the BeanDefinitionRegistry based on its contents. */ private void loadBeanDefinitionsForBeanMethod(ConfigurationClassMethod beanMethod) { ConfigurationClass configClass = beanMethod.getConfigurationClass(); MethodMetadata metadata = beanMethod.getMetadata(); RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass); beanDef.setResource(configClass.getResource()); beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource())); beanDef.setFactoryBeanName(configClass.getBeanName()); beanDef.setUniqueFactoryMethodName(metadata.getMethodName()); beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE); // consider name and any aliases Map beanAttributes = metadata.getAnnotationAttributes(Bean.class.getName()); List names = new ArrayList(Arrays.asList((String[]) beanAttributes.get("name"))); String beanName = (names.size() > 0 ? names.remove(0) : beanMethod.getMetadata().getMethodName()); for (String alias : names) { this.registry.registerAlias(beanName, alias); } // has this already been overridden (e.g. via XML)? if (this.registry.containsBeanDefinition(beanName)) { BeanDefinition existingBeanDef = registry.getBeanDefinition(beanName); // is the existing bean definition one that was created from a configuration class? if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) { // no -> then it's an external override, probably XML // overriding is legal, return immediately if (logger.isDebugEnabled()) { logger.debug(String.format("Skipping loading bean definition for %s: a definition for bean " + "'%s' already exists. This is likely due to an override in XML.", beanMethod, beanName)); } return; } } if (metadata.isAnnotated(Primary.class.getName())) { beanDef.setPrimary(true); } // is this bean to be instantiated lazily? if (metadata.isAnnotated(Lazy.class.getName())) { beanDef.setLazyInit((Boolean) metadata.getAnnotationAttributes(Lazy.class.getName()).get("value")); } else if (configClass.getMetadata().isAnnotated(Lazy.class.getName())){ beanDef.setLazyInit((Boolean) configClass.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value")); } if (metadata.isAnnotated(DependsOn.class.getName())) { String[] dependsOn = (String[]) metadata.getAnnotationAttributes(DependsOn.class.getName()).get("value"); if (dependsOn.length > 0) { beanDef.setDependsOn(dependsOn); } } Autowire autowire = (Autowire) beanAttributes.get("autowire"); if (autowire.isAutowire()) { beanDef.setAutowireMode(autowire.value()); } String initMethodName = (String) beanAttributes.get("initMethod"); if (StringUtils.hasText(initMethodName)) { beanDef.setInitMethodName(initMethodName); } String destroyMethodName = (String) beanAttributes.get("destroyMethod"); if (StringUtils.hasText(destroyMethodName)) { beanDef.setDestroyMethodName(destroyMethodName); } // consider scoping ScopedProxyMode proxyMode = ScopedProxyMode.NO; Map scopeAttributes = metadata.getAnnotationAttributes(Scope.class.getName()); if (scopeAttributes != null) { beanDef.setScope((String) scopeAttributes.get("value")); proxyMode = (ScopedProxyMode) scopeAttributes.get("proxyMode"); if (proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = ScopedProxyMode.NO; } } // replace the original bean definition with the target one, if necessary BeanDefinition beanDefToRegister = beanDef; if (proxyMode != ScopedProxyMode.NO) { BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy( new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS); beanDefToRegister = proxyDef.getBeanDefinition(); } if (logger.isDebugEnabled()) { logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getMetadata().getClassName(), beanName)); } registry.registerBeanDefinition(beanName, beanDefToRegister); } private void loadBeanDefinitionsFromImportedResources(Map> importedResources) { Map, BeanDefinitionReader> readerInstanceCache = new HashMap, BeanDefinitionReader>(); for (Map.Entry> entry : importedResources.entrySet()) { String resource = entry.getKey(); Class readerClass = entry.getValue(); if (!readerInstanceCache.containsKey(readerClass)) { try { // Instantiate the specified BeanDefinitionReader BeanDefinitionReader readerInstance = (BeanDefinitionReader) readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry); // Delegate the current ResourceLoader to it if possible if (readerInstance instanceof AbstractBeanDefinitionReader) { ((AbstractBeanDefinitionReader)readerInstance).setResourceLoader(this.resourceLoader); } readerInstanceCache.put(readerClass, readerInstance); } catch (Exception ex) { throw new IllegalStateException("Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]"); } } BeanDefinitionReader reader = readerInstanceCache.get(readerClass); // TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations reader.loadBeanDefinitions(resource); } } /** * Check whether the given bean definition is a candidate for a configuration class, * and mark it accordingly. * @param beanDef the bean definition to check * @param metadataReaderFactory the current factory in use by the caller * @return whether the candidate qualifies as (any kind of) configuration class */ public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { AnnotationMetadata metadata = null; // Check already loaded Class if present... // since we possibly can't even load the class file for this Class. if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { metadata = new StandardAnnotationMetadata(((AbstractBeanDefinition) beanDef).getBeanClass()); } else { String className = beanDef.getBeanClassName(); if (className != null) { try { MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className); metadata = metadataReader.getAnnotationMetadata(); } catch (IOException ex) { if (logger.isDebugEnabled()) { logger.debug("Could not find class file for introspecting factory methods: " + className, ex); } return false; } } } if (metadata != null) { if (metadata.isAnnotated(Configuration.class.getName())) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); return true; } else if (metadata.isAnnotated(Component.class.getName()) || metadata.hasAnnotatedMethods(Bean.class.getName())) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); return true; } } return false; } /** * Determine whether the given bean definition indicates a full @Configuration class. */ public static boolean isFullConfigurationClass(BeanDefinition beanDef) { return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE)); } /** * {@link RootBeanDefinition} marker subclass used to signify that a bean definition * was created from a configuration class as opposed to any other configuration source. * Used in bean overriding cases where it's necessary to determine whether the bean * definition was created externally. */ @SuppressWarnings("serial") private static class ConfigurationClassBeanDefinition extends RootBeanDefinition implements AnnotatedBeanDefinition { private AnnotationMetadata annotationMetadata; public ConfigurationClassBeanDefinition(ConfigurationClass configClass) { this.annotationMetadata = configClass.getMetadata(); } private ConfigurationClassBeanDefinition(ConfigurationClassBeanDefinition original) { super(original); this.annotationMetadata = original.annotationMetadata; } public AnnotationMetadata getMetadata() { return this.annotationMetadata; } @Override public boolean isFactoryMethod(Method candidate) { return (super.isFactoryMethod(candidate) && BeanAnnotationHelper.isBeanAnnotated(candidate)); } @Override public ConfigurationClassBeanDefinition cloneBeanDefinition() { return new ConfigurationClassBeanDefinition(this); } } /** * Configuration classes must be annotated with {@link Configuration @Configuration} or * declare at least one {@link Bean @Bean} method. */ private static class InvalidConfigurationImportProblem extends Problem { public InvalidConfigurationImportProblem(String className, Resource resource, AnnotationMetadata metadata) { super(String.format("%s was @Import'ed but is not annotated with @FeatureConfiguration or " + "@Configuration nor does it declare any @Bean methods. Update the class to " + "meet one of these requirements or do not attempt to @Import it.", className), new Location(resource, metadata)); } } }