diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java index b660b0355b..a19488d93c 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java @@ -92,7 +92,7 @@ public class AnnotatedBeanDefinitionReader { /** * Set the Environment to use when evaluating whether - * {@link Profile @Profile}-annotated component classes should be registered. + * {@link Conditional @Conditional}-annotated component classes should be registered. *

The default is a {@link StandardEnvironment}. * @see #registerBean(Class, String, Class...) */ @@ -133,8 +133,7 @@ public class AnnotatedBeanDefinitionReader { public void registerBean(Class annotatedClass, String name, Class... qualifiers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); - if (ConditionalAnnotationHelper.shouldSkip(abd, this.registry, - this.environment, this.beanNameGenerator)) { + if (ConditionEvaluator.get(abd.getMetadata(), true).shouldSkip(this.registry, this.environment)) { return; } ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index a016b88554..b673b40fc4 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -31,6 +31,7 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.classreading.MetadataReader; import org.springframework.util.Assert; import org.springframework.util.PatternMatchUtils; @@ -52,7 +53,6 @@ import org.springframework.util.PatternMatchUtils; * @author Mark Fisher * @author Juergen Hoeller * @author Chris Beams - * @author Phillip Webb * @since 2.5 * @see AnnotationConfigApplicationContext#scan * @see org.springframework.stereotype.Component @@ -262,6 +262,12 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo return beanDefinitions; } + @Override + protected boolean isConditionMatch(MetadataReader metadataReader) { + return !ConditionEvaluator.get(metadataReader.getAnnotationMetadata(), true).shouldSkip( + getRegistry(), getEnvironment()); + } + /** * Apply further settings to the given bean definition, * beyond the contents retrieved from scanning the component class. @@ -299,10 +305,6 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo * bean definition has been found for the specified name */ protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException { - if (ConditionalAnnotationHelper.shouldSkip(beanDefinition, getRegistry(), - getEnvironment(), this.beanNameGenerator)) { - return false; - } if (!this.registry.containsBeanDefinition(beanName)) { return true; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index a12e9ab190..5c66d8f211 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -25,12 +25,10 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.ResourceLoaderAware; -import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; @@ -39,7 +37,6 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; -import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -159,7 +156,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC /** * Set the Environment to use when resolving placeholders and evaluating - * {@link Profile @Profile}-annotated component classes. + * {@link Conditional @Conditional}-annotated component classes. *

The default is a {@link StandardEnvironment} * @param environment the Environment to use */ @@ -333,17 +330,23 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { - AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); - if (!metadata.isAnnotated(Profile.class.getName())) { - return true; - } - AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class); - return this.environment.acceptsProfiles(profile.getStringArray("value")); + return isConditionMatch(metadataReader); } } return false; } + /** + * Determine whether the given class is a candidate component based on any + * {@code @Conditional} annotations. + * @param metadataReader the ASM ClassReader for the class + * @return whether the class qualifies as a candidate component + */ + protected boolean isConditionMatch(MetadataReader metadataReader) { + return !ConditionEvaluator.get(metadataReader.getAnnotationMetadata(), true).shouldSkip( + null, getEnvironment()); + } + /** * Determine whether the given bean definition qualifies as candidate. *

The default implementation checks whether the class is concrete diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java b/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java index 564586501c..bca2fc8f1b 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java @@ -31,8 +31,8 @@ public interface ConditionContext { /** * Returns the {@link BeanDefinitionRegistry} that will hold the bean definition - * should the condition match. - * @return the registry (never {@code null}) + * should the condition match or {@code null} if the registry is not available. + * @return the registry or {@code null} */ BeanDefinitionRegistry getRegistry(); @@ -45,13 +45,11 @@ public interface ConditionContext { /** * Returns the {@link ConfigurableListableBeanFactory} that will hold the bean - * definition should the condition match. If a - * {@link ConfigurableListableBeanFactory} is unavailable an - * {@link IllegalStateException} will be thrown. - * @return the bean factory - * @throws IllegalStateException if the bean factory could not be obtained + * definition should the condition match or {@code null} if the bean factory is + * not available. + * @return the bean factory or {@code null} */ - ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException; + ConfigurableListableBeanFactory getBeanFactory(); /** * Returns the {@link ResourceLoader} currently being used or {@code null} if the diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConditionEvaluator.java b/spring-context/src/main/java/org/springframework/context/annotation/ConditionEvaluator.java new file mode 100644 index 0000000000..2f7342f8dd --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConditionEvaluator.java @@ -0,0 +1,213 @@ +/* + * Copyright 2002-2013 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.util.Collections; +import java.util.List; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.Environment; +import org.springframework.core.env.EnvironmentCapable; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.MultiValueMap; + +/** + * Utility class used to evaluate {@link Conditional} annotations. + * + * @author Phillip Webb + * @since 4.0 + */ +abstract class ConditionEvaluator { + + private static final String CONDITIONAL_ANNOTATION = Conditional.class.getName(); + + private static final ConditionEvaluator NONE = new ConditionEvaluator() { + + @Override + public boolean shouldSkip(BeanDefinitionRegistry registry, Environment environment) { + return false; + } + + }; + + + /** + * Evaluate if any condition does not match and hence registration should be skipped. + * @param registry the registry or {@code null} + * @param environment the environment or {@code null} + * @return if the registration should be skipped + */ + public abstract boolean shouldSkip(BeanDefinitionRegistry registry, + Environment environment); + + + /** + * Returns a {@link ConditionEvaluator} instance of the specified metadata. + * @param metadata the metadata to test + * @param deferIfConfigurationCandidate if the evaluator should be deferred when the + * metadata is from a {@code @Configuration} candidate. + * @return the evaluator instance + */ + public static ConditionEvaluator get(AnnotatedTypeMetadata metadata, + boolean deferIfConfigurationCandidate) { + if (metadata == null || !metadata.isAnnotated(CONDITIONAL_ANNOTATION)) { + // Shortcut to save always creating a ConditionEvaluator + return NONE; + } + + // Defer @Conditional @Configuration classes until later when the + // ConfigurationClassPostProcessor will evaluate them. Allows @Conditional + // implementations that inspect beans created by @Configuration to work + if (deferIfConfigurationCandidate && metadata instanceof AnnotationMetadata + && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { + return NONE; + } + + return new ConditionEvaluatorImpl(metadata); + } + + + /** + * Implementation of {@link ConditionEvaluator}. + */ + private static class ConditionEvaluatorImpl extends ConditionEvaluator { + + private AnnotatedTypeMetadata metadata; + + + public ConditionEvaluatorImpl(AnnotatedTypeMetadata metadata) { + this.metadata = metadata; + } + + + @Override + public boolean shouldSkip(BeanDefinitionRegistry registry, Environment environment) { + ConditionContext context = new ConditionContextImpl(registry, environment); + if (this.metadata != null) { + for (String[] conditionClasses : getConditionClasses(metadata)) { + for (String conditionClass : conditionClasses) { + if (!getCondition(conditionClass, context.getClassLoader()).matches( + context, metadata)) { + return true; + } + } + } + } + return false; + } + + @SuppressWarnings("unchecked") + private static List getConditionClasses(AnnotatedTypeMetadata metadata) { + MultiValueMap attributes = metadata.getAllAnnotationAttributes( + CONDITIONAL_ANNOTATION, true); + Object values = attributes == null ? null : attributes.get("value"); + return (List) (values == null ? Collections.emptyList() : values); + } + + private static Condition getCondition(String conditionClassName, + ClassLoader classloader) { + Class conditionClass = ClassUtils.resolveClassName(conditionClassName, + classloader); + return (Condition) BeanUtils.instantiateClass(conditionClass); + } + } + + /** + * Implementation of a {@link ConditionContext}. + */ + private static class ConditionContextImpl implements ConditionContext { + + private BeanDefinitionRegistry registry; + + private ConfigurableListableBeanFactory beanFactory; + + private Environment environment; + + + public ConditionContextImpl(BeanDefinitionRegistry registry, + Environment environment) { + this.registry = registry; + this.beanFactory = deduceBeanFactory(registry); + this.environment = environment; + if (this.environment == null) { + this.environment = deduceEnvironment(registry); + } + } + + + private ConfigurableListableBeanFactory deduceBeanFactory(Object source) { + if (source == null) { + return null; + } + if (source instanceof ConfigurableListableBeanFactory) { + return (ConfigurableListableBeanFactory) source; + } + else if (source instanceof ConfigurableApplicationContext) { + return deduceBeanFactory(((ConfigurableApplicationContext) source).getBeanFactory()); + } + return null; + } + + private Environment deduceEnvironment(BeanDefinitionRegistry registry) { + if (registry == null) { + return null; + } + if (registry instanceof EnvironmentCapable) { + return ((EnvironmentCapable) registry).getEnvironment(); + } + return null; + } + + @Override + public BeanDefinitionRegistry getRegistry() { + return this.registry; + } + + @Override + public Environment getEnvironment() { + return this.environment; + } + + @Override + public ConfigurableListableBeanFactory getBeanFactory() { + Assert.state(this.beanFactory != null, "Unable to locate the BeanFactory"); + return this.beanFactory; + } + + @Override + public ResourceLoader getResourceLoader() { + if (registry instanceof ResourceLoader) { + return (ResourceLoader) registry; + } + return null; + } + + @Override + public ClassLoader getClassLoader() { + ResourceLoader resourceLoader = getResourceLoader(); + return (resourceLoader == null ? null : resourceLoader.getClassLoader()); + } + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConditionalAnnotationHelper.java b/spring-context/src/main/java/org/springframework/context/annotation/ConditionalAnnotationHelper.java deleted file mode 100644 index 0eba197672..0000000000 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConditionalAnnotationHelper.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2002-2013 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.util.Collections; -import java.util.List; - -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanNameGenerator; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.env.Environment; -import org.springframework.core.env.EnvironmentCapable; -import org.springframework.core.io.ResourceLoader; -import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.MultiValueMap; - -/** - * Helper class used to determine if registration should be skipped based due to a - * {@code @Conditional} annotation. - * - * @author Phillip Webb - * @since 4.0 - * @see Conditional - */ -abstract class ConditionalAnnotationHelper { - - private static final String CONDITIONAL_ANNOTATION = Conditional.class.getName(); - - - public static boolean shouldSkip(BeanDefinition beanDefinition, - BeanDefinitionRegistry registry, Environment environment, - BeanNameGenerator beanNameGenerator) { - if (hasCondition(getMetadata(beanDefinition))) { - ConditionContextImpl context = new ConditionContextImpl(registry, - environment, beanNameGenerator); - return shouldSkip(getMetadata(beanDefinition), context); - } - return false; - } - - public static boolean shouldSkip(BeanMethod beanMethod, - BeanDefinitionRegistry registry, Environment environment, - BeanNameGenerator beanNameGenerator) { - if (hasCondition(getMetadata(beanMethod))) { - ConditionContextImpl context = new ConditionContextImpl(registry, - environment, beanNameGenerator); - return shouldSkip(getMetadata(beanMethod), context); - } - return false; - } - - public static boolean shouldSkip(ConfigurationClass configurationClass, - BeanDefinitionRegistry registry, Environment environment, - BeanNameGenerator beanNameGenerator) { - if (hasCondition(configurationClass)) { - ConditionContextImpl context = new ConditionContextImpl(registry, - environment, beanNameGenerator); - return shouldSkip(configurationClass, context); - } - return false; - } - - public static boolean shouldSkip(ConfigurationClass configClass, - ConditionContextImpl context) { - if (configClass == null) { - return false; - } - return shouldSkip(configClass.getMetadata(), context); - } - - private static boolean shouldSkip(AnnotatedTypeMetadata metadata, - ConditionContextImpl context) { - if (metadata != null) { - for (String[] conditionClasses : getConditionClasses(metadata)) { - for (String conditionClass : conditionClasses) { - if (!getCondition(conditionClass, context.getClassLoader()).matches( - context, metadata)) { - return true; - } - } - } - } - return false; - } - - private static AnnotatedTypeMetadata getMetadata(BeanMethod beanMethod) { - return (beanMethod == null ? null : beanMethod.getMetadata()); - } - - private static AnnotatedTypeMetadata getMetadata(BeanDefinition beanDefinition) { - if (beanDefinition != null && beanDefinition instanceof AnnotatedBeanDefinition) { - return ((AnnotatedBeanDefinition) beanDefinition).getMetadata(); - } - return null; - } - - private static boolean hasCondition(ConfigurationClass configurationClass) { - if (configurationClass == null) { - return false; - } - return hasCondition(configurationClass.getMetadata()) - || hasCondition(configurationClass.getImportedBy()); - } - - private static boolean hasCondition(AnnotatedTypeMetadata metadata) { - return (metadata != null) && metadata.isAnnotated(CONDITIONAL_ANNOTATION); - } - - @SuppressWarnings("unchecked") - private static List getConditionClasses(AnnotatedTypeMetadata metadata) { - MultiValueMap attributes = metadata.getAllAnnotationAttributes( - CONDITIONAL_ANNOTATION, true); - Object values = attributes == null ? null : attributes.get("value"); - return (List) (values == null ? Collections.emptyList() : values); - } - - private static Condition getCondition(String conditionClassName, - ClassLoader classloader) { - Class conditionClass = ClassUtils.resolveClassName(conditionClassName, - classloader); - return (Condition) BeanUtils.instantiateClass(conditionClass); - } - - - /** - * Implementation of a {@link ConditionContext}. - */ - private static class ConditionContextImpl implements ConditionContext { - - private BeanDefinitionRegistry registry; - - private ConfigurableListableBeanFactory beanFactory; - - private Environment environment; - - - public ConditionContextImpl(BeanDefinitionRegistry registry, - Environment environment, BeanNameGenerator beanNameGenerator) { - Assert.notNull(registry, "Registry must not be null"); - this.registry = registry; - this.beanFactory = deduceBeanFactory(registry); - this.environment = environment; - if (this.environment == null) { - this.environment = deduceEnvironment(registry); - } - } - - - private ConfigurableListableBeanFactory deduceBeanFactory(Object source) { - if (source instanceof ConfigurableListableBeanFactory) { - return (ConfigurableListableBeanFactory) source; - } - else if (source instanceof ConfigurableApplicationContext) { - return deduceBeanFactory(((ConfigurableApplicationContext) source).getBeanFactory()); - } - return null; - } - - private Environment deduceEnvironment(BeanDefinitionRegistry registry) { - if (registry instanceof EnvironmentCapable) { - return ((EnvironmentCapable) registry).getEnvironment(); - } - return null; - } - - @Override - public BeanDefinitionRegistry getRegistry() { - return this.registry; - } - - @Override - public Environment getEnvironment() { - return this.environment; - } - - @Override - public ConfigurableListableBeanFactory getBeanFactory() { - Assert.state(this.beanFactory != null, "Unable to locate the BeanFactory"); - return this.beanFactory; - } - - @Override - public ResourceLoader getResourceLoader() { - if (registry instanceof ResourceLoader) { - return (ResourceLoader) registry; - } - return null; - } - - @Override - public ClassLoader getClassLoader() { - ResourceLoader resourceLoader = getResourceLoader(); - return (resourceLoader == null ? null : resourceLoader.getClassLoader()); - } - } - -} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java index 2e91b45d5f..3af2311e2d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java @@ -16,6 +16,7 @@ package org.springframework.context.annotation; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -61,6 +62,9 @@ final class ConfigurationClass { private final Map> importedResources = new LinkedHashMap>(); + private final Set importBeanDefinitionRegistrars = + new LinkedHashSet(); + /** * Create a new {@link ConfigurationClass} with the given name. @@ -173,11 +177,18 @@ final class ConfigurationClass { this.importedResources.put(importedResource, readerClass); } + public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar) { + this.importBeanDefinitionRegistrars.add(registrar); + } + + public Set getImportBeanDefinitionRegistrars() { + return Collections.unmodifiableSet(importBeanDefinitionRegistrars); + } + public Map> getImportedResources() { return this.importedResources; } - public void validate(ProblemReporter problemReporter) { // A configuration class may not be final (CGLIB limitation) if (getMetadata().isAnnotated(Configuration.class.getName())) { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 27f417f655..1ca7538b70 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -16,8 +16,6 @@ package org.springframework.context.annotation; -import static org.springframework.context.annotation.MetadataUtils.attributesFor; - import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; @@ -28,6 +26,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.annotation.Autowire; @@ -52,6 +51,8 @@ import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.StringUtils; +import static org.springframework.context.annotation.MetadataUtils.*; + /** * Reads a given fully-populated set of ConfigurationClass instances, registering bean * definitions with the given {@link BeanDefinitionRegistry} based on its contents. @@ -84,7 +85,6 @@ class ConfigurationClassBeanDefinitionReader { private final BeanNameGenerator importBeanNameGenerator; - /** * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used * to populate the given {@link BeanDefinitionRegistry}. @@ -109,8 +109,9 @@ class ConfigurationClassBeanDefinitionReader { * based on its contents. */ public void loadBeanDefinitions(Set configurationModel) { + TrackedConditionEvaluator conditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { - loadBeanDefinitionsForConfigurationClass(configClass); + loadBeanDefinitionsForConfigurationClass(configClass, conditionEvaluator); } } @@ -118,7 +119,14 @@ class ConfigurationClassBeanDefinitionReader { * Read a particular {@link ConfigurationClass}, registering bean definitions for the * class itself, all its {@link Bean} methods */ - public void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { + private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, + TrackedConditionEvaluator conditionEvaluator) { + + if(conditionEvaluator.shouldSkip(configClass)) { + removeBeanDefinition(configClass); + return; + } + if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } @@ -126,6 +134,17 @@ class ConfigurationClassBeanDefinitionReader { loadBeanDefinitionsForBeanMethod(beanMethod); } loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); + loadBeanDefinitionsFromRegistrars(configClass.getMetadata(), configClass.getImportBeanDefinitionRegistrars()); + } + + private void removeBeanDefinition(ConfigurationClass configClass) { + if (StringUtils.hasLength(configClass.getBeanName())) { + try { + this.registry.removeBeanDefinition(configClass.getBeanName()); + } + catch (NoSuchBeanDefinitionException ex) { + } + } } /** @@ -153,8 +172,8 @@ class ConfigurationClassBeanDefinitionReader { * with the BeanDefinitionRegistry based on its contents. */ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { - if (ConditionalAnnotationHelper.shouldSkip(beanMethod, this.registry, - this.environment, this.importBeanNameGenerator)) { + if (ConditionEvaluator.get(beanMethod.getMetadata(), false).shouldSkip( + this.registry, this.environment)) { return; } ConfigurationClass configClass = beanMethod.getConfigurationClass(); @@ -300,6 +319,14 @@ class ConfigurationClassBeanDefinitionReader { } } + private void loadBeanDefinitionsFromRegistrars( + AnnotationMetadata importingClassMetadata, + Set importBeanDefinitionRegistrars) { + for (ImportBeanDefinitionRegistrar registrar : importBeanDefinitionRegistrars) { + registrar.registerBeanDefinitions(importingClassMetadata, this.registry); + } + } + /** * {@link RootBeanDefinition} marker subclass used to signify that a bean definition @@ -357,4 +384,32 @@ class ConfigurationClassBeanDefinitionReader { } } + /** + * Evaluate {@Code @Conditional} annotations, tracking results and taking into + * account 'imported by'. + */ + private class TrackedConditionEvaluator { + + private final Map skipped = new HashMap(); + + public boolean shouldSkip(ConfigurationClass configClass) { + Boolean skip = this.skipped.get(configClass); + if (skip == null) { + if (configClass.isImported()) { + if (shouldSkip(configClass.getImportedBy())) { + // The config that imported this one was skipped, therefore we are skipped + skip = true; + } + } + if (skip == null) { + skip = ConditionEvaluator.get(configClass.getMetadata(), false).shouldSkip( + registry, environment); + } + this.skipped.put(configClass, skip); + } + return skip; + } + + } + } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 0ac8c31e82..b7622fc2bb 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -76,7 +76,8 @@ import static org.springframework.context.annotation.MetadataUtils.*; * *

This class helps separate the concern of parsing the structure of a Configuration * class from the concern of registering BeanDefinition objects based on the - * content of that model. + * content of that model (with the exception of {@code @ComponentScan} annotations which + * need to be registered immediately). * *

This ASM-based implementation avoids reflection and eager class loading in order to * interoperate effectively with lazy class loading in a Spring ApplicationContext. @@ -108,8 +109,6 @@ class ConfigurationClassParser { private final BeanDefinitionRegistry registry; - private final BeanNameGenerator beanNameGenerator; - private final ComponentScanAnnotationParser componentScanParser; private final Set configurationClasses = new LinkedHashSet(); @@ -136,7 +135,6 @@ class ConfigurationClassParser { this.environment = environment; this.resourceLoader = resourceLoader; this.registry = registry; - this.beanNameGenerator = componentScanBeanNameGenerator; this.componentScanParser = new ComponentScanAnnotationParser( resourceLoader, environment, componentScanBeanNameGenerator, registry); } @@ -182,9 +180,6 @@ class ConfigurationClassParser { protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { - if (ConditionalAnnotationHelper.shouldSkip(configClass, this.registry, this.environment, this.beanNameGenerator)) { - return; - } if (this.configurationClasses.contains(configClass) && configClass.getBeanName() != null) { // Explicit bean definition found, probably replacing an import. @@ -226,13 +221,16 @@ class ConfigurationClassParser { AnnotationAttributes componentScan = attributesFor(metadata, ComponentScan.class); if (componentScan != null) { // the config class is annotated with @ComponentScan -> perform the scan immediately - Set scannedBeanDefinitions = - this.componentScanParser.parse(componentScan, metadata.getClassName()); + if (!ConditionEvaluator.get(configClass.getMetadata(), false).shouldSkip( + this.registry, this.environment)) { + Set scannedBeanDefinitions = + this.componentScanParser.parse(componentScan, metadata.getClassName()); - // check the set of scanned definitions for any further config classes and parse recursively if necessary - for (BeanDefinitionHolder holder : scannedBeanDefinitions) { - if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) { - this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); + // check the set of scanned definitions for any further config classes and parse recursively if necessary + for (BeanDefinitionHolder holder : scannedBeanDefinitions) { + if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) { + this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); + } } } } @@ -451,7 +449,7 @@ class ConfigurationClassParser { this.resourceLoader.getClassLoader().loadClass((String) candidate)); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); invokeAwareMethods(registrar); - registrar.registerBeanDefinitions(importingClassMetadata, this.registry); + configClass.addImportBeanDefinitionRegistrar(registrar); } else { // candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> process it as a @Configuration class diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 944fd051f2..42f16bbd7e 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -26,7 +26,6 @@ import java.util.Stack; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanDefinitionStoreException; @@ -305,12 +304,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory, this.resourceLoader, this.environment, this.importBeanNameGenerator); } - for (ConfigurationClass configurationClass : parser.getConfigurationClasses()) { - if (!ConditionalAnnotationHelper.shouldSkip(configurationClass, registry, - this.environment, this.importBeanNameGenerator)) { - reader.loadBeanDefinitionsForConfigurationClass(configurationClass); - } - } + + reader.loadBeanDefinitions(parser.getConfigurationClasses()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (singletonRegistry != null) { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index 9a4a682771..46a58b7eb0 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java index 49dfa4a966..6e261a0e5e 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java @@ -46,20 +46,41 @@ public class ConfigurationClassWithConditionTests { public ExpectedException thrown = ExpectedException.none(); @Test - public void conditionalOnBeanMatch() throws Exception { + public void conditionalOnMissingBeanMatch() throws Exception { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(BeanOneConfiguration.class, BeanTwoConfiguration.class); ctx.refresh(); assertTrue(ctx.containsBean("bean1")); assertFalse(ctx.containsBean("bean2")); + assertFalse(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration")); + } + + @Test + public void conditionalOnMissingBeanNoMatch() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(BeanTwoConfiguration.class); + ctx.refresh(); + assertFalse(ctx.containsBean("bean1")); + assertTrue(ctx.containsBean("bean2")); + assertTrue(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration")); + } + + @Test + public void conditionalOnBeanMatch() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(BeanOneConfiguration.class, BeanThreeConfiguration.class); + ctx.refresh(); + assertTrue(ctx.containsBean("bean1")); + assertTrue(ctx.containsBean("bean3")); } @Test public void conditionalOnBeanNoMatch() throws Exception { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); - ctx.register(BeanTwoConfiguration.class); + ctx.register(BeanThreeConfiguration.class); ctx.refresh(); - assertTrue(ctx.containsBean("bean2")); + assertFalse(ctx.containsBean("bean1")); + assertFalse(ctx.containsBean("bean3")); } @Test @@ -105,6 +126,15 @@ public class ConfigurationClassWithConditionTests { } } + @Configuration + @Conditional(HasBeanOneCondition.class) + static class BeanThreeConfiguration { + @Bean + public ExampleBean bean3() { + return new ExampleBean(); + } + } + @Configuration @MetaConditional("test") static class ConfigurationWithMetaCondition { @@ -135,6 +165,14 @@ public class ConfigurationClassWithConditionTests { } } + static class HasBeanOneCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return context.getBeanFactory().containsBeanDefinition("bean1"); + } + } + static class MetaConditionalFilter implements Condition { @Override diff --git a/spring-context/src/test/java/org/springframework/context/annotation/componentscan/simple/SimpleComponent.java b/spring-context/src/test/java/org/springframework/context/annotation/componentscan/simple/SimpleComponent.java index 43e394b560..9d63ad60a2 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/componentscan/simple/SimpleComponent.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/componentscan/simple/SimpleComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 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.