From 2e2e9b8dd0ecb113c86ec3a5d0cf87d4f6f72fd6 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 12 Jun 2013 11:41:12 -0700 Subject: [PATCH] Improve support for @Conditional on @Configuration Introduce new ConfigurationCondition interface allowing more fine-grained control for @Conditional when used with @Configuration beans. Primarily added so that the evaluation of conditions that inspect bean definitions can be deferred until all @Configuration classes have been parsed. Issue: SPR-10534 --- .../AnnotatedBeanDefinitionReader.java | 12 +- .../ClassPathBeanDefinitionScanner.java | 6 - ...athScanningCandidateComponentProvider.java | 20 +- .../context/annotation/Condition.java | 11 +- .../context/annotation/ConditionContext.java | 3 + .../annotation/ConditionEvaluator.java | 198 ++++++++++-------- .../context/annotation/Conditional.java | 2 +- .../annotation/ConfigurationClass.java | 1 - ...onfigurationClassBeanDefinitionReader.java | 32 +-- .../annotation/ConfigurationClassParser.java | 22 +- .../ConfigurationClassPostProcessor.java | 19 +- .../annotation/ConfigurationCondition.java | 60 ++++++ .../AsmCircularImportDetectionTests.java | 5 +- .../ConfigurationClassWithConditionTests.java | 154 +++++++++++++- 14 files changed, 402 insertions(+), 143 deletions(-) create mode 100644 spring-context/src/main/java/org/springframework/context/annotation/ConfigurationCondition.java 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 a19488d93c..be17b59d6d 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 @@ -45,12 +45,12 @@ public class AnnotatedBeanDefinitionReader { private final BeanDefinitionRegistry registry; - private Environment environment; - private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver(); + private ConditionEvaluator conditionEvaluator; + /** * Create a new {@code AnnotatedBeanDefinitionReader} for the given registry. @@ -79,7 +79,8 @@ public class AnnotatedBeanDefinitionReader { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); Assert.notNull(environment, "Environment must not be null"); this.registry = registry; - this.environment = environment; + this.conditionEvaluator = new ConditionEvaluator(registry, environment, + null, null, null); AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } @@ -97,7 +98,8 @@ public class AnnotatedBeanDefinitionReader { * @see #registerBean(Class, String, Class...) */ public void setEnvironment(Environment environment) { - this.environment = environment; + this.conditionEvaluator = new ConditionEvaluator(this.registry, environment, + null, null, null); } /** @@ -133,7 +135,7 @@ public class AnnotatedBeanDefinitionReader { public void registerBean(Class annotatedClass, String name, Class... qualifiers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); - if (ConditionEvaluator.get(abd.getMetadata(), true).shouldSkip(this.registry, this.environment)) { + if (conditionEvaluator.shouldSkip(abd.getMetadata())) { 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 b673b40fc4..42f5b0ab9a 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 @@ -262,12 +262,6 @@ 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. 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 5c66d8f211..08336b34b0 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 @@ -28,6 +28,7 @@ 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.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; @@ -85,6 +86,8 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC private final List excludeFilters = new LinkedList(); + private ConditionEvaluator conditionEvaluator; + /** * Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}. @@ -162,6 +165,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC */ public void setEnvironment(Environment environment) { this.environment = environment; + this.conditionEvaluator = null; } @Override @@ -169,6 +173,13 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC return this.environment; } + /** + * Returns the {@link BeanDefinitionRegistry} used by this scanner or {@code null}. + */ + protected BeanDefinitionRegistry getRegistry() { + return null; + } + /** * Set the resource pattern to use when scanning the classpath. * This value will be appended to each base package name. @@ -342,9 +353,12 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC * @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()); + private boolean isConditionMatch(MetadataReader metadataReader) { + if (this.conditionEvaluator == null) { + this.conditionEvaluator = new ConditionEvaluator(getRegistry(), + getEnvironment(), null, null, getResourceLoader()); + } + return !conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata()); } /** diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Condition.java b/spring-context/src/main/java/org/springframework/context/annotation/Condition.java index 69543830aa..a3cfe9fc80 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Condition.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Condition.java @@ -25,15 +25,18 @@ import org.springframework.core.type.AnnotationMetadata; * A single {@code condition} that must be {@linkplain #matches matched} in order * for a component to be registered. * - *

Conditions are checked immediately before a component bean-definition is due to be - * registered and are free to veto registration based on any criteria that can be - * determined at that point. + *

Conditions are checked immediately before the bean-definition is due to be + * registered and are free to veto registration based on any criteria that can + * be determined at that point. * *

Conditions must follow the same restrictions as {@link BeanFactoryPostProcessor} - * and take care to never interact with bean instances. + * and take care to never interact with bean instances. For more fine-grained control + * of conditions that interact with {@code @Configuration} beans consider the + * {@link ConfigurationCondition} interface. * * @author Phillip Webb * @since 4.0 + * @see ConfigurationCondition * @see Conditional * @see ConditionContext */ 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 bca2fc8f1b..541f749c25 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 @@ -18,6 +18,7 @@ package org.springframework.context.annotation; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.ApplicationContext; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; @@ -65,4 +66,6 @@ public interface ConditionContext { */ ClassLoader getClassLoader(); + ApplicationContext getApplicationContext(); + } 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 index 2f7342f8dd..32142bc59e 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConditionEvaluator.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConditionEvaluator.java @@ -22,7 +22,9 @@ 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.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.io.ResourceLoader; @@ -33,106 +35,93 @@ import org.springframework.util.ClassUtils; import org.springframework.util.MultiValueMap; /** - * Utility class used to evaluate {@link Conditional} annotations. + * Internal class used to evaluate {@link Conditional} annotations. * * @author Phillip Webb * @since 4.0 */ -abstract class ConditionEvaluator { +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; - } - - }; + private final ConditionContextImpl context; /** - * 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 + * Create a new {@link ConditionEvaluator} instance. */ - 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); + public ConditionEvaluator(BeanDefinitionRegistry registry, Environment environment, + ApplicationContext applicationContext, ClassLoader classLoader, + ResourceLoader resourceLoader) { + this.context = new ConditionContextImpl(registry, environment, + applicationContext, classLoader, resourceLoader); } /** - * Implementation of {@link ConditionEvaluator}. + * Determine if an item should be skipped based on {@code @Conditional} annotations. + * The {@link ConfigurationPhase} will be deduced from the type of item (i.e. a + * {@code @Configuration} class will be {@link ConfigurationPhase#PARSE_CONFIGURATION}) + * @param metadata the meta data + * @return if the item should be skipped */ - private static class ConditionEvaluatorImpl extends ConditionEvaluator { + public boolean shouldSkip(AnnotatedTypeMetadata metadata) { + return shouldSkip(metadata, null); + } - private AnnotatedTypeMetadata metadata; - - - public ConditionEvaluatorImpl(AnnotatedTypeMetadata metadata) { - this.metadata = metadata; + /** + * Determine if an item should be skipped based on {@code @Conditional} annotations. + * @param metadata the meta data + * @param phase the phase of the call + * @return if the item should be skipped + */ + public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) { + if (metadata == null || !metadata.isAnnotated(CONDITIONAL_ANNOTATION)) { + return false; } + if (phase == null) { + if (metadata instanceof AnnotationMetadata && + ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { + return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); + } + return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); + } - @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; - } + for (String[] conditionClasses : getConditionClasses(metadata)) { + for (String conditionClass : conditionClasses) { + Condition condition = getCondition(conditionClass, context.getClassLoader()); + ConfigurationPhase requiredPhase = null; + if (condition instanceof ConfigurationCondition) { + requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); + } + if (requiredPhase == null || requiredPhase == phase) { + if (!condition.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); } + return false; } + @SuppressWarnings("unchecked") + private 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 Condition getCondition(String conditionClassName, + ClassLoader classloader) { + Class conditionClass = ClassUtils.resolveClassName(conditionClassName, + classloader); + return (Condition) BeanUtils.instantiateClass(conditionClass); + } + + /** * Implementation of a {@link ConditionContext}. */ @@ -144,18 +133,24 @@ abstract class ConditionEvaluator { private Environment environment; + private ApplicationContext applicationContext; + + private ClassLoader classLoader; + + private ResourceLoader resourceLoader; + public ConditionContextImpl(BeanDefinitionRegistry registry, - Environment environment) { + Environment environment, ApplicationContext applicationContext, + ClassLoader classLoader, ResourceLoader resourceLoader) { this.registry = registry; this.beanFactory = deduceBeanFactory(registry); this.environment = environment; - if (this.environment == null) { - this.environment = deduceEnvironment(registry); - } + this.applicationContext = applicationContext; + this.classLoader = classLoader; + this.resourceLoader = resourceLoader; } - private ConfigurableListableBeanFactory deduceBeanFactory(Object source) { if (source == null) { return null; @@ -169,24 +164,26 @@ abstract class ConditionEvaluator { return null; } - private Environment deduceEnvironment(BeanDefinitionRegistry registry) { - if (registry == null) { - return null; + @Override + public BeanDefinitionRegistry getRegistry() { + if (this.registry != null) { + return this.registry; } - if (registry instanceof EnvironmentCapable) { - return ((EnvironmentCapable) registry).getEnvironment(); + if(getBeanFactory() != null && getBeanFactory() instanceof BeanDefinitionRegistry) { + return (BeanDefinitionRegistry) getBeanFactory(); } return null; } - @Override - public BeanDefinitionRegistry getRegistry() { - return this.registry; - } - @Override public Environment getEnvironment() { - return this.environment; + if (this.environment != null) { + return this.environment; + } + if (getRegistry() != null && getRegistry() instanceof EnvironmentCapable) { + return ((EnvironmentCapable) getRegistry()).getEnvironment(); + } + return null; } @Override @@ -197,6 +194,9 @@ abstract class ConditionEvaluator { @Override public ResourceLoader getResourceLoader() { + if (this.resourceLoader != null) { + return this.resourceLoader; + } if (registry instanceof ResourceLoader) { return (ResourceLoader) registry; } @@ -205,8 +205,24 @@ abstract class ConditionEvaluator { @Override public ClassLoader getClassLoader() { - ResourceLoader resourceLoader = getResourceLoader(); - return (resourceLoader == null ? null : resourceLoader.getClassLoader()); + if (this.classLoader != null) { + return this.classLoader; + } + if (getResourceLoader() != null) { + return getResourceLoader().getClassLoader(); + } + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + if (this.applicationContext != null) { + return this.applicationContext; + } + if (getRegistry() != null && getRegistry() instanceof ApplicationContext) { + return (ApplicationContext) getRegistry(); + } + return null; } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java b/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java index 795ebfcc69..898e3ebcd3 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java @@ -26,7 +26,7 @@ import java.lang.annotation.Target; * {@linkplain #value() specified conditions} match. * *

A condition is any state that can be determined programmatically - * immediately before the bean is due to be created (see {@link Condition} for details). + * before the bean definition is due to be registered (see {@link Condition} for details). * *

The {@code @Conditional} annotation may be used in any of the following ways: *

    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 3af2311e2d..9138e2357f 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 @@ -219,7 +219,6 @@ final class ConfigurationClass { } } - @Override public boolean equals(Object other) { return (this == other || (other instanceof ConfigurationClass && 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 1ca7538b70..4aa586dd2c 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 @@ -42,6 +42,8 @@ import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; @@ -85,14 +87,17 @@ class ConfigurationClassBeanDefinitionReader { private final BeanNameGenerator importBeanNameGenerator; + private final ConditionEvaluator conditionEvaluator; + /** * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used * to populate the given {@link BeanDefinitionRegistry}. */ public ConfigurationClassBeanDefinitionReader( - BeanDefinitionRegistry registry, SourceExtractor sourceExtractor, - ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory, - ResourceLoader resourceLoader, Environment environment, BeanNameGenerator importBeanNameGenerator) { + BeanDefinitionRegistry registry, ApplicationContext applicationContext, + SourceExtractor sourceExtractor, ProblemReporter problemReporter, + MetadataReaderFactory metadataReaderFactory, ResourceLoader resourceLoader, + Environment environment, BeanNameGenerator importBeanNameGenerator) { this.registry = registry; this.sourceExtractor = sourceExtractor; @@ -101,6 +106,8 @@ class ConfigurationClassBeanDefinitionReader { this.resourceLoader = resourceLoader; this.environment = environment; this.importBeanNameGenerator = importBeanNameGenerator; + this.conditionEvaluator = new ConditionEvaluator(registry, environment, + applicationContext, null, resourceLoader); } @@ -109,9 +116,9 @@ class ConfigurationClassBeanDefinitionReader { * based on its contents. */ public void loadBeanDefinitions(Set configurationModel) { - TrackedConditionEvaluator conditionEvaluator = new TrackedConditionEvaluator(); + TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { - loadBeanDefinitionsForConfigurationClass(configClass, conditionEvaluator); + loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } } @@ -120,9 +127,8 @@ class ConfigurationClassBeanDefinitionReader { * class itself, all its {@link Bean} methods */ private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, - TrackedConditionEvaluator conditionEvaluator) { - - if(conditionEvaluator.shouldSkip(configClass)) { + TrackedConditionEvaluator trackedConditionEvaluator) { + if (trackedConditionEvaluator.shouldSkip(configClass)) { removeBeanDefinition(configClass); return; } @@ -172,8 +178,8 @@ class ConfigurationClassBeanDefinitionReader { * with the BeanDefinitionRegistry based on its contents. */ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { - if (ConditionEvaluator.get(beanMethod.getMetadata(), false).shouldSkip( - this.registry, this.environment)) { + if (conditionEvaluator.shouldSkip(beanMethod.getMetadata(), + ConfigurationPhase.REGISTER_BEAN)) { return; } ConfigurationClass configClass = beanMethod.getConfigurationClass(); @@ -384,6 +390,7 @@ class ConfigurationClassBeanDefinitionReader { } } + /** * Evaluate {@Code @Conditional} annotations, tracking results and taking into * account 'imported by'. @@ -402,14 +409,13 @@ class ConfigurationClassBeanDefinitionReader { } } if (skip == null) { - skip = ConditionEvaluator.get(configClass.getMetadata(), false).shouldSkip( - registry, environment); + skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), + ConfigurationPhase.REGISTER_BEAN); } 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 489b79c59d..ba0052358a 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 @@ -46,8 +46,10 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.context.ApplicationContext; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; +import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase; import org.springframework.core.NestedIOException; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAwareOrderComparator; @@ -119,6 +121,8 @@ class ConfigurationClassParser { private final List deferredImportSelectors = new LinkedList(); + private final ConditionEvaluator conditionEvaluator; + /** * Create a new {@link ConfigurationClassParser} instance that will be used @@ -126,7 +130,8 @@ class ConfigurationClassParser { */ public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader, - BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) { + BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry, + ApplicationContext applicationContext) { this.metadataReaderFactory = metadataReaderFactory; this.problemReporter = problemReporter; @@ -135,6 +140,8 @@ class ConfigurationClassParser { this.registry = registry; this.componentScanParser = new ComponentScanAnnotationParser( resourceLoader, environment, componentScanBeanNameGenerator, registry); + this.conditionEvaluator = new ConditionEvaluator(registry, environment, + applicationContext, null, resourceLoader); } @@ -162,7 +169,7 @@ class ConfigurationClassParser { * @param beanName may be null, but if populated represents the bean id * (assumes that this configuration class was configured via XML) */ - public void parse(String className, String beanName) throws IOException { + protected final void parse(String className, String beanName) throws IOException { MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className); processConfigurationClass(new ConfigurationClass(reader, beanName)); } @@ -172,13 +179,17 @@ class ConfigurationClassParser { * @param clazz the Class to parse * @param beanName must not be null (as of Spring 3.1.1) */ - public void parse(Class clazz, String beanName) throws IOException { + protected final void parse(Class clazz, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(clazz, beanName)); } protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { + if (conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { + return; + } + if (this.configurationClasses.contains(configClass) && configClass.getBeanName() != null) { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. @@ -224,15 +235,14 @@ class ConfigurationClassParser { AnnotationAttributes componentScan = attributesFor(sourceClass.getMetadata(), ComponentScan.class); if (componentScan != null) { // the config class is annotated with @ComponentScan -> perform the scan immediately - if (!ConditionEvaluator.get(configClass.getMetadata(), false).shouldSkip( - this.registry, this.environment)) { + if (!conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { Set scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().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()); + parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); } } } 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 42f16bbd7e..5610baeaca 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 @@ -46,6 +46,8 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.ConfigurationClassParser.ImportRegistry; @@ -85,7 +87,7 @@ import static org.springframework.context.annotation.AnnotationConfigUtils.*; * @since 3.0 */ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, - ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { + ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware, ApplicationContextAware { private static final String IMPORT_AWARE_PROCESSOR_BEAN_NAME = ConfigurationClassPostProcessor.class.getName() + ".importAwareProcessor"; @@ -102,6 +104,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo private Environment environment; + private ApplicationContext applicationContext; + private ResourceLoader resourceLoader = new DefaultResourceLoader(); private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); @@ -130,6 +134,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo }; + /** * Set the {@link SourceExtractor} to use for generated bean definitions * that correspond to {@link Bean} factory methods. @@ -189,6 +194,12 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo this.environment = environment; } + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } + @Override public void setResourceLoader(ResourceLoader resourceLoader) { Assert.notNull(resourceLoader, "ResourceLoader must not be null"); @@ -279,7 +290,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, - this.resourceLoader, this.componentScanBeanNameGenerator, registry); + this.resourceLoader, this.componentScanBeanNameGenerator, registry, + this.applicationContext); parser.parse(configCandidates); parser.validate(); @@ -301,7 +313,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( - registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory, + registry, this.applicationContext, this.sourceExtractor, + this.problemReporter, this.metadataReaderFactory, this.resourceLoader, this.environment, this.importBeanNameGenerator); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationCondition.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationCondition.java new file mode 100644 index 0000000000..bcdd56a5f6 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationCondition.java @@ -0,0 +1,60 @@ +/* + * 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; + +/** + * A {@link Condition} that offers more fine-grained control when used with + * {@code @Configuration}. Allows certain {@link Condition}s to adapt when they match + * based on the configuration phase. For example, a condition that checks if a bean has + * already been registered might choose to only be evaluated on the + * {@link ConfigurationPhase#REGISTER_BEAN REGISTER_BEAN} {@link ConfigurationPhase}. + * + * @author Phillip Webb + */ +public interface ConfigurationCondition extends Condition { + + /** + * Returns the {@link ConfigurationPhase} in which the condition should be evaluated. + */ + ConfigurationPhase getConfigurationPhase(); + + /** + * The various configuration phases where the condition could be evaluated. + */ + public static enum ConfigurationPhase { + + /** + * The {@link Condition} should be evaluated as a {@code @Configuration} class is + * being parsed. + * + *

    If the condition does not match at this point the {@code @Configuration} + * class will not be added. + */ + PARSE_CONFIGURATION, + + /** + * The {@link Condition} should be evaluated when adding a regular (non + * {@code @Configuration}) bean. The condition will not prevent + * {@code @Configuration} classes from being added. + * + *

    At the time that the condition is evaluated all {@code @Configuration}s + * will have been parsed. + */ + REGISTER_BEAN + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java index 61b2612d29..e7795333e5 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.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. @@ -41,7 +41,8 @@ public class AsmCircularImportDetectionTests extends AbstractCircularImportDetec new StandardEnvironment(), new DefaultResourceLoader(), new AnnotationBeanNameGenerator(), - new DefaultListableBeanFactory()); + new DefaultListableBeanFactory(), + null); } @Override 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 0c321345e6..d26e6b5f78 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 @@ -16,12 +16,6 @@ package org.springframework.context.annotation; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -32,15 +26,22 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.stereotype.Component; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + /** * Tests for {@link Conditional} beans. - * + * * @author Phillip Webb */ +@SuppressWarnings("resource") public class ConfigurationClassWithConditionTests { private final AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); @@ -110,6 +111,32 @@ public class ConfigurationClassWithConditionTests { assertNull(ctx.getBean(ExampleBean.class)); } + @Test + public void importsNotCreated() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ImportsNotCreated.class); + ctx.refresh(); + } + + @Test + public void importsNotLoaded() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ImportsNotLoaded.class); + ctx.refresh(); + assertThat(ctx.containsBeanDefinition("a"), equalTo(false)); + assertThat(ctx.containsBeanDefinition("b"), equalTo(false)); + } + + @Test + public void sensibleConditionContext() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.setResourceLoader(new DefaultResourceLoader()); + ctx.setClassLoader(getClass().getClassLoader()); + ctx.register(SensibleConditionContext.class); + ctx.refresh(); + assertThat(ctx.getBean(ExampleBean.class), instanceOf(ExampleBean.class)); + } + @Configuration static class BeanOneConfiguration { @Bean @@ -166,7 +193,12 @@ public class ConfigurationClassWithConditionTests { } } - static class HasBeanOneCondition implements Condition { + static class HasBeanOneCondition implements ConfigurationCondition { + + @Override + public ConfigurationPhase getConfigurationPhase() { + return ConfigurationPhase.REGISTER_BEAN; + } @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { @@ -206,6 +238,112 @@ public class ConfigurationClassWithConditionTests { } } + @Configuration + @Never + @Import({ ConfigurationNotCreated.class, RegistrarNotCreated.class, ImportSelectorNotCreated.class }) + static class ImportsNotCreated { + static { + if (true) throw new RuntimeException(); + } + } + + @Configuration + static class ConfigurationNotCreated { + static { + if (true) throw new RuntimeException(); + } + } + + static class RegistrarNotCreated implements ImportBeanDefinitionRegistrar { + static { + if (true) throw new RuntimeException(); + } + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + } + } + + static class ImportSelectorNotCreated implements ImportSelector { + + static { + if (true) throw new RuntimeException(); + } + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[] {}; + } + + } + + @Configuration + @Never + @Import({ ConfigurationNotLoaded.class, RegistrarNotLoaded.class, ImportSelectorNotLoaded.class }) + static class ImportsNotLoaded { + static { + if (true) throw new RuntimeException(); + } + } + + @Configuration + static class ConfigurationNotLoaded { + @Bean + public String a() { + return "a"; + } + } + + static class RegistrarNotLoaded implements ImportBeanDefinitionRegistrar { + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + throw new RuntimeException(); + } + } + + static class ImportSelectorNotLoaded implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[] { SelectedConfigurationNotLoaded.class.getName() }; + } + + } + + @Configuration + static class SelectedConfigurationNotLoaded { + @Bean + public String b() { + return "b"; + } + } + + @Configuration + @Conditional(SensibleConditionContextCondition.class) + static class SensibleConditionContext { + @Bean + ExampleBean exampleBean() { + return new ExampleBean(); + } + } + + static class SensibleConditionContextCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + assertThat(context.getApplicationContext(), notNullValue()); + assertThat(context.getBeanFactory(), notNullValue()); + assertThat(context.getClassLoader(), notNullValue()); + assertThat(context.getEnvironment(), notNullValue()); + assertThat(context.getRegistry(), notNullValue()); + assertThat(context.getResourceLoader(), notNullValue()); + return true; + } + + } + static class ExampleBean { }