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 cfbc423714..b660b0355b 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 @@ -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. @@ -24,11 +24,9 @@ import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; -import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; -import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.Assert; /** @@ -39,6 +37,7 @@ import org.springframework.util.Assert; * @author Juergen Hoeller * @author Chris Beams * @author Sam Brannen + * @author Phillip Webb * @since 3.0 * @see AnnotationConfigApplicationContext#register */ @@ -134,12 +133,9 @@ public class AnnotatedBeanDefinitionReader { public void registerBean(Class annotatedClass, String name, Class... qualifiers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); - AnnotationMetadata metadata = abd.getMetadata(); - if (metadata.isAnnotated(Profile.class.getName())) { - AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class); - if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) { - return; - } + if (ConditionalAnnotationHelper.shouldSkip(abd, this.registry, + this.environment, this.beanNameGenerator)) { + return; } ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); 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 b3f9450f98..a016b88554 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 @@ -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. @@ -52,6 +52,7 @@ 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 @@ -298,6 +299,10 @@ 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/Condition.java b/spring-context/src/main/java/org/springframework/context/annotation/Condition.java new file mode 100644 index 0000000000..69543830aa --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/Condition.java @@ -0,0 +1,52 @@ +/* + * 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 org.jruby.internal.runtime.methods.MethodMethod; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.core.type.AnnotatedTypeMetadata; +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 must follow the same restrictions as {@link BeanFactoryPostProcessor} + * and take care to never interact with bean instances. + * + * @author Phillip Webb + * @since 4.0 + * @see Conditional + * @see ConditionContext + */ +public interface Condition { + + /** + * Determine if the condition matches. + * @param context the condition context + * @param metadata meta-data of the {@link AnnotationMetadata class} or + * {@link MethodMethod method} being checked. + * @return {@code true} if the condition matches and the component can be registered + * or {@code false} to veto registration. + */ + boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); + +} 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 new file mode 100644 index 0000000000..564586501c --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java @@ -0,0 +1,70 @@ +/* + * 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 org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; + +/** + * Context information for use by {@link Condition}s. + * + * @author Phillip Webb + * @since 4.0 + */ +public interface ConditionContext { + + /** + * Returns the {@link BeanDefinitionRegistry} that will hold the bean definition + * should the condition match. + * @return the registry (never {@code null}) + */ + BeanDefinitionRegistry getRegistry(); + + /** + * Return the {@link Environment} for which the current application is running or + * {@code null} if no environment is available. + * @return the environment or {@code null} + */ + Environment getEnvironment(); + + /** + * 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 + */ + ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException; + + /** + * Returns the {@link ResourceLoader} currently being used or {@code null} if the + * resource loader cannot be obtained. + * @return a resource loader or {@code null} + */ + ResourceLoader getResourceLoader(); + + /** + * Returns the {@link ClassLoader} that should be used to load additional classes + * or {@code null} if the default classloader should be used. + * @return the classloader or {@code null} + */ + ClassLoader getClassLoader(); + +} 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 new file mode 100644 index 0000000000..9a98714254 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java @@ -0,0 +1,58 @@ +/* + * 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.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a component is is only eligible for registration when all + * {@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). + * + *

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

+ * + *

If a {@code @Configuration} class is marked with {@code @Conditional}, all of the + * {@code @Bean} methods and {@link Import @Import} annotations associated with that class + * will be subject to the conditions. + * + * @author Phillip Webb + * @since 4.0 + * @see Condition + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface Conditional { + + /** + * All {@link Condition}s that must {@linkplain Condition#matches match} in order for + * the component to be registered. + */ + Class[] value(); + +} 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 new file mode 100644 index 0000000000..acd766ea48 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConditionalAnnotationHelper.java @@ -0,0 +1,212 @@ +/* + * 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; + } + + public BeanDefinitionRegistry getRegistry() { + return this.registry; + } + + public Environment getEnvironment() { + return this.environment; + } + + public ConfigurableListableBeanFactory getBeanFactory() { + Assert.state(this.beanFactory != null, "Unable to locate the BeanFactory"); + return this.beanFactory; + } + + public ResourceLoader getResourceLoader() { + if (registry instanceof ResourceLoader) { + return (ResourceLoader) registry; + } + return null; + } + + 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 0a5a646092..6ed1f64309 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 @@ -41,6 +41,7 @@ import org.springframework.util.ClassUtils; * * @author Chris Beams * @author Juergen Hoeller + * @author Phillip Webb * @since 3.0 * @see BeanMethod * @see ConfigurationClassParser @@ -58,7 +59,7 @@ final class ConfigurationClass { private String beanName; - private final boolean imported; + private final ConfigurationClass importedBy; /** @@ -66,28 +67,28 @@ final class ConfigurationClass { * @param metadataReader reader used to parse the underlying {@link Class} * @param beanName must not be {@code null} * @throws IllegalArgumentException if beanName is null (as of Spring 3.1.1) - * @see ConfigurationClass#ConfigurationClass(Class, boolean) + * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass) */ public ConfigurationClass(MetadataReader metadataReader, String beanName) { Assert.hasText(beanName, "bean name must not be null"); this.metadata = metadataReader.getAnnotationMetadata(); this.resource = metadataReader.getResource(); this.beanName = beanName; - this.imported = false; + this.importedBy = null; } /** * Create a new {@link ConfigurationClass} representing a class that was imported * using the {@link Import} annotation or automatically processed as a nested - * configuration class (if imported is {@code true}). + * configuration class (if importedBy is not {@code null}). * @param metadataReader reader used to parse the underlying {@link Class} - * @param imported whether the given configuration class is being imported + * @param importedBy the configuration class importing this one or {@code null} * @since 3.1.1 */ - public ConfigurationClass(MetadataReader metadataReader, boolean imported) { + public ConfigurationClass(MetadataReader metadataReader, ConfigurationClass importedBy) { this.metadata = metadataReader.getAnnotationMetadata(); this.resource = metadataReader.getResource(); - this.imported = imported; + this.importedBy = importedBy; } /** @@ -95,14 +96,14 @@ final class ConfigurationClass { * @param clazz the underlying {@link Class} to represent * @param beanName name of the {@code @Configuration} class bean * @throws IllegalArgumentException if beanName is null (as of Spring 3.1.1) - * @see ConfigurationClass#ConfigurationClass(Class, boolean) + * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass) */ public ConfigurationClass(Class clazz, String beanName) { Assert.hasText(beanName, "bean name must not be null"); this.metadata = new StandardAnnotationMetadata(clazz, true); this.resource = new DescriptiveResource(clazz.toString()); this.beanName = beanName; - this.imported = false; + this.importedBy = null; } /** @@ -110,13 +111,13 @@ final class ConfigurationClass { * using the {@link Import} annotation or automatically processed as a nested * configuration class (if imported is {@code true}). * @param clazz the underlying {@link Class} to represent - * @param imported whether the given configuration class is being imported + * @param importedBy the configuration class importing this one or {@code null} * @since 3.1.1 */ - public ConfigurationClass(Class clazz, boolean imported) { + public ConfigurationClass(Class clazz, ConfigurationClass importedBy) { this.metadata = new StandardAnnotationMetadata(clazz, true); this.resource = new DescriptiveResource(clazz.toString()); - this.imported = imported; + this.importedBy = importedBy; } @@ -136,9 +137,20 @@ final class ConfigurationClass { * Return whether this configuration class was registered via @{@link Import} or * automatically registered due to being nested within another configuration class. * @since 3.1.1 + * @see #getImportedBy() */ public boolean isImported() { - return this.imported; + return this.importedBy != null; + } + + /** + * Returns the configuration class that imported this class or {@code null} if + * this configuration was not imported. + * @since 4.0 + * @see #isImported() + */ + public ConfigurationClass getImportedBy() { + return importedBy; } public void setBeanName(String beanName) { 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 27ae171955..eab2b0bd26 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,6 +16,8 @@ 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; @@ -26,7 +28,6 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.annotation.Autowire; @@ -51,8 +52,6 @@ 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. @@ -63,6 +62,7 @@ import static org.springframework.context.annotation.MetadataUtils.*; * * @author Chris Beams * @author Juergen Hoeller + * @author Phillip Webb * @since 3.0 * @see ConfigurationClassParser */ @@ -118,7 +118,7 @@ class ConfigurationClassBeanDefinitionReader { * Read a particular {@link ConfigurationClass}, registering bean definitions for the * class itself, all its {@link Bean} methods */ - private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { + public void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } @@ -153,6 +153,10 @@ class ConfigurationClassBeanDefinitionReader { * with the BeanDefinitionRegistry based on its contents. */ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { + if (ConditionalAnnotationHelper.shouldSkip(beanMethod, this.registry, + this.environment, this.importBeanNameGenerator)) { + return; + } ConfigurationClass configClass = beanMethod.getConfigurationClass(); MethodMetadata metadata = beanMethod.getMetadata(); 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 82fbbeba52..93731a0025 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,6 +76,7 @@ import static org.springframework.context.annotation.MetadataUtils.*; * * @author Chris Beams * @author Juergen Hoeller + * @author Phillip Webb * @since 3.0 * @see ConfigurationClassBeanDefinitionReader */ @@ -103,6 +104,8 @@ class ConfigurationClassParser { private final ComponentScanAnnotationParser componentScanParser; + private final BeanNameGenerator beanNameGenerator; + /** * Create a new {@link ConfigurationClassParser} instance that will be used @@ -117,6 +120,7 @@ class ConfigurationClassParser { this.environment = environment; this.resourceLoader = resourceLoader; this.registry = registry; + this.beanNameGenerator = componentScanBeanNameGenerator; this.componentScanParser = new ComponentScanAnnotationParser( resourceLoader, environment, componentScanBeanNameGenerator, registry); } @@ -144,11 +148,10 @@ class ConfigurationClassParser { protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { AnnotationMetadata metadata = configClass.getMetadata(); - if (this.environment != null && metadata.isAnnotated(Profile.class.getName())) { - AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class); - if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) { - return; - } + + if (ConditionalAnnotationHelper.shouldSkip(configClass, this.registry, + this.environment, this.beanNameGenerator)) { + return; } // recursively process the configuration class and its superclass hierarchy @@ -173,7 +176,7 @@ class ConfigurationClassParser { ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { // recursively process any member (nested) classes first - processMemberClasses(metadata); + processMemberClasses(configClass, metadata); // process any @PropertySource annotations AnnotationAttributes propertySource = attributesFor(metadata, org.springframework.context.annotation.PropertySource.class); @@ -256,11 +259,12 @@ class ConfigurationClassParser { * @param metadata the metadata representation of the containing class * @throws IOException if there is any problem reading metadata from a member class */ - private void processMemberClasses(AnnotationMetadata metadata) throws IOException { + private void processMemberClasses(ConfigurationClass configClass, + AnnotationMetadata metadata) throws IOException { if (metadata instanceof StandardAnnotationMetadata) { for (Class memberClass : ((StandardAnnotationMetadata) metadata).getIntrospectedClass().getDeclaredClasses()) { if (ConfigurationClassUtils.isConfigurationCandidate(new StandardAnnotationMetadata(memberClass))) { - processConfigurationClass(new ConfigurationClass(memberClass, true)); + processConfigurationClass(new ConfigurationClass(memberClass, configClass)); } } } @@ -269,7 +273,7 @@ class ConfigurationClassParser { MetadataReader reader = this.metadataReaderFactory.getMetadataReader(memberClassName); AnnotationMetadata memberClassMetadata = reader.getAnnotationMetadata(); if (ConfigurationClassUtils.isConfigurationCandidate(memberClassMetadata)) { - processConfigurationClass(new ConfigurationClass(reader, true)); + processConfigurationClass(new ConfigurationClass(reader, configClass)); } } } @@ -391,9 +395,11 @@ class ConfigurationClassParser { } else { // candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> process it as a @Configuration class - this.importStack.registerImport(importingClassMetadata.getClassName(), (candidate instanceof Class ? ((Class) candidate).getName() : (String) candidate)); - processConfigurationClass(candidateToCheck instanceof Class ? new ConfigurationClass((Class) candidateToCheck, true) : - new ConfigurationClass((MetadataReader) candidateToCheck, true)); + this.importStack.registerImport(importingClassMetadata.getClassName(), + (candidate instanceof Class ? ((Class) candidate).getName() : (String) candidate)); + processConfigurationClass((candidateToCheck instanceof Class ? + new ConfigurationClass((Class) candidateToCheck, configClass) : + new ConfigurationClass((MetadataReader) candidateToCheck, configClass))); } } } 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 7afbc02733..933192970a 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 @@ -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. @@ -82,6 +82,7 @@ import static org.springframework.context.annotation.AnnotationConfigUtils.*; * * @author Chris Beams * @author Juergen Hoeller + * @author Phillip Webb * @since 3.0 */ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, @@ -312,7 +313,12 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory, this.resourceLoader, this.environment, this.importBeanNameGenerator); } - this.reader.loadBeanDefinitions(parser.getConfigurationClasses()); + for (ConfigurationClass configurationClass : parser.getConfigurationClasses()) { + if (!ConditionalAnnotationHelper.shouldSkip(configurationClass, registry, + this.environment, this.importBeanNameGenerator)) { + reader.loadBeanDefinitionsForConfigurationClass(configurationClass); + } + } // 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/Profile.java b/spring-context/src/main/java/org/springframework/context/annotation/Profile.java index ab793599c0..27900952cd 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Profile.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Profile.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. @@ -38,8 +38,9 @@ import org.springframework.core.env.ConfigurableEnvironment; *

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

* *

If a {@code @Configuration} class is marked with {@code @Profile}, all of the @@ -65,6 +66,7 @@ import org.springframework.core.env.ConfigurableEnvironment; * {@code spring-beans} XSD (version 3.1 or greater) for details. * * @author Chris Beams + * @author Phillip Webb * @since 3.1 * @see ConfigurableEnvironment#setActiveProfiles * @see ConfigurableEnvironment#setDefaultProfiles @@ -72,7 +74,8 @@ import org.springframework.core.env.ConfigurableEnvironment; * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Conditional(ProfileCondition.class) public @interface Profile { /** diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ProfileCondition.java b/spring-context/src/main/java/org/springframework/context/annotation/ProfileCondition.java new file mode 100644 index 0000000000..424e20ae09 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ProfileCondition.java @@ -0,0 +1,42 @@ +/* + * 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 org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link Condition} that matches based on the value of a {@link Profile @Profile} + * annotation. + * + * @author Chris Beams + * @author Phillip Webb + * @since 4.0 + */ +class ProfileCondition implements Condition { + + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + if (context.getEnvironment() != null && metadata.isAnnotated(Profile.class.getName())) { + AnnotationAttributes profile = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(Profile.class.getName())); + if (!context.getEnvironment().acceptsProfiles(profile.getStringArray("value"))) { + return false; + } + } + return true; + } + +} 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 new file mode 100644 index 0000000000..9506b817c9 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java @@ -0,0 +1,170 @@ +/* + * 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 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; +import java.lang.annotation.Target; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.stereotype.Component; + +/** + * Test for {@link Conditional} beans. + * + * @author Phillip Webb + */ +public class ConfigurationClassWithConditionTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void conditionalOnBeanMatch() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(BeanOneConfiguration.class, BeanTwoConfiguration.class); + ctx.refresh(); + assertTrue(ctx.containsBean("bean1")); + assertFalse(ctx.containsBean("bean2")); + } + + @Test + public void conditionalOnBeanNoMatch() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(BeanTwoConfiguration.class); + ctx.refresh(); + assertTrue(ctx.containsBean("bean2")); + } + + @Test + public void metaConditional() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ConfigurationWithMetaCondition.class); + ctx.refresh(); + assertTrue(ctx.containsBean("bean")); + } + + @Test + public void nonConfigurationClass() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(NonConfigurationClass.class); + ctx.refresh(); + thrown.expect(NoSuchBeanDefinitionException.class); + assertNull(ctx.getBean(NonConfigurationClass.class)); + } + + @Test + public void methodConditional() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ConditionOnMethodConfiguration.class); + ctx.refresh(); + thrown.expect(NoSuchBeanDefinitionException.class); + assertNull(ctx.getBean(ExampleBean.class)); + } + + @Configuration + static class BeanOneConfiguration { + @Bean + public ExampleBean bean1() { + return new ExampleBean(); + } + } + + @Configuration + @Conditional(NoBeanOneCondition.class) + static class BeanTwoConfiguration { + @Bean + public ExampleBean bean2() { + return new ExampleBean(); + } + } + + @Configuration + @MetaConditional("test") + static class ConfigurationWithMetaCondition { + @Bean + public ExampleBean bean() { + return new ExampleBean(); + } + } + + @Conditional(MetaConditionalFilter.class) + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @interface MetaConditional { + String value(); + } + + @Conditional(NeverCondition.class) + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE, ElementType.METHOD}) + public static @interface Never { + } + + static class NoBeanOneCondition implements Condition { + + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return !context.getBeanFactory().containsBeanDefinition("bean1"); + } + } + + static class MetaConditionalFilter implements Condition { + + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(MetaConditional.class.getName())); + assertThat(attributes.getString("value"), equalTo("test")); + return true; + } + } + + static class NeverCondition implements Condition { + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return false; + } + } + + @Component + @Never + static class NonConfigurationClass { + } + + @Configuration + static class ConditionOnMethodConfiguration { + + @Bean + @Never + public ExampleBean bean1() { + return new ExampleBean(); + } + } + + static class ExampleBean { + } + +}