Browse Source
* SPR-10534: Add support for DeferredImportSelector Support for @Conditional configuration Extend AnnotationMetadata and MethodMetadatapull/292/head
27 changed files with 1480 additions and 308 deletions
@ -0,0 +1,52 @@
@@ -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. |
||||
* |
||||
* <p>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. |
||||
* |
||||
* <p>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); |
||||
|
||||
} |
||||
@ -0,0 +1,70 @@
@@ -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(); |
||||
|
||||
} |
||||
@ -0,0 +1,58 @@
@@ -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. |
||||
* |
||||
* <p>A <em>condition</em> is any state that can be determined programmatically |
||||
* immediately before the bean is due to be created (see {@link Condition} for details). |
||||
* |
||||
* <p>The {@code @Conditional} annotation may be used in any of the following ways: |
||||
* <ul> |
||||
* <li>as a type-level annotation on any class directly or indirectly annotated with |
||||
* {@code @Component}, including {@link Configuration @Configuration} classes</li> |
||||
* <li>as a meta-annotation, for the purpose of composing custom stereotype |
||||
* annotations</li> |
||||
* <li>as a method-level annotation on any {@link Bean @Bean} method</li> |
||||
* </ul> |
||||
* |
||||
* <p>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<? extends Condition>[] value(); |
||||
|
||||
} |
||||
@ -0,0 +1,212 @@
@@ -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<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) { |
||||
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes( |
||||
CONDITIONAL_ANNOTATION, true); |
||||
Object values = attributes == null ? null : attributes.get("value"); |
||||
return (List<String[]>) (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()); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
/* |
||||
* 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 variation of {@link ImportSelector} that runs after all {@code @Configuration} beans |
||||
* have been processed. This type of selector can be particularly useful when the selected |
||||
* imports are {@code @Conditional}. |
||||
* |
||||
* <p>Implementations can also extend the {@link org.springframework.core.Ordered} |
||||
* interface or use the {@link org.springframework.core.annotation.Order} annotation to |
||||
* indicate a precedence against other {@link DeferredImportSelector}s. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 4.0 |
||||
*/ |
||||
public interface DeferredImportSelector extends ImportSelector { |
||||
|
||||
} |
||||
@ -0,0 +1,42 @@
@@ -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; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,170 @@
@@ -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 { |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,146 @@
@@ -0,0 +1,146 @@
|
||||
/* |
||||
* 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.mockito.Matchers.any; |
||||
import static org.mockito.Matchers.eq; |
||||
import static org.mockito.Mockito.inOrder; |
||||
import static org.mockito.Mockito.spy; |
||||
|
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
import org.junit.Test; |
||||
import org.mockito.InOrder; |
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.core.Ordered; |
||||
import org.springframework.core.annotation.Order; |
||||
import org.springframework.core.type.AnnotationMetadata; |
||||
|
||||
/** |
||||
* Tests for {@link ImportSelector} and {@link DeferredImportSelector}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
public class ImportSelectorTests { |
||||
|
||||
@Test |
||||
public void importSelectors() { |
||||
DefaultListableBeanFactory beanFactory = spy(new DefaultListableBeanFactory()); |
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( |
||||
beanFactory); |
||||
context.register(Config.class); |
||||
context.refresh(); |
||||
context.getBean(Config.class); |
||||
InOrder ordered = inOrder(beanFactory); |
||||
ordered.verify(beanFactory).registerBeanDefinition(eq("a"), any(BeanDefinition.class)); |
||||
ordered.verify(beanFactory).registerBeanDefinition(eq("b"), any(BeanDefinition.class)); |
||||
ordered.verify(beanFactory).registerBeanDefinition(eq("d"), any(BeanDefinition.class)); |
||||
ordered.verify(beanFactory).registerBeanDefinition(eq("c"), any(BeanDefinition.class)); |
||||
} |
||||
|
||||
@Sample |
||||
@Configuration |
||||
static class Config { |
||||
} |
||||
|
||||
@Target(ElementType.TYPE) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Import({ DeferredImportSelector1.class, DeferredImportSelector2.class, |
||||
ImportSelector1.class, ImportSelector2.class }) |
||||
public static @interface Sample { |
||||
} |
||||
|
||||
public static class ImportSelector1 implements ImportSelector { |
||||
|
||||
@Override |
||||
public String[] selectImports(AnnotationMetadata importingClassMetadata) { |
||||
return new String[] { ImportedSelector1.class.getName() }; |
||||
} |
||||
} |
||||
|
||||
public static class ImportSelector2 implements ImportSelector { |
||||
|
||||
@Override |
||||
public String[] selectImports(AnnotationMetadata importingClassMetadata) { |
||||
return new String[] { ImportedSelector2.class.getName() }; |
||||
} |
||||
} |
||||
|
||||
public static class DeferredImportSelector1 implements DeferredImportSelector, Ordered { |
||||
|
||||
@Override |
||||
public String[] selectImports(AnnotationMetadata importingClassMetadata) { |
||||
return new String[] { DeferredImportedSelector1.class.getName() }; |
||||
} |
||||
|
||||
@Override |
||||
public int getOrder() { |
||||
return Ordered.LOWEST_PRECEDENCE; |
||||
} |
||||
} |
||||
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE) |
||||
public static class DeferredImportSelector2 implements DeferredImportSelector { |
||||
|
||||
@Override |
||||
public String[] selectImports(AnnotationMetadata importingClassMetadata) { |
||||
return new String[] { DeferredImportedSelector2.class.getName() }; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
public static class ImportedSelector1 { |
||||
|
||||
@Bean |
||||
public String a() { |
||||
return "a"; |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
public static class ImportedSelector2 { |
||||
|
||||
@Bean |
||||
public String b() { |
||||
return "b"; |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
public static class DeferredImportedSelector1 { |
||||
|
||||
@Bean |
||||
public String c() { |
||||
return "c"; |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
public static class DeferredImportedSelector2 { |
||||
|
||||
@Bean |
||||
public String d() { |
||||
return "d"; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,160 @@
@@ -0,0 +1,160 @@
|
||||
/* |
||||
* 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.core.type; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.util.HashSet; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
/** |
||||
* Internal utility class used to collect all annotation values including those declared |
||||
* on meta-annotations. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 4.0 |
||||
*/ |
||||
class AnnotatedElementUtils { |
||||
|
||||
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, |
||||
String annotationType) { |
||||
final Set<String> types = new LinkedHashSet<String>(); |
||||
process(element, annotationType, new Processor<Object>() { |
||||
public Object process(Annotation annotation, int depth) { |
||||
if (depth > 0) { |
||||
types.add(annotation.annotationType().getName()); |
||||
} |
||||
return null; |
||||
} |
||||
}); |
||||
return (types.size() == 0 ? null : types); |
||||
} |
||||
|
||||
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, |
||||
String annotationType) { |
||||
return Boolean.TRUE.equals( |
||||
process(element, annotationType, new Processor<Boolean>() { |
||||
public Boolean process(Annotation annotation, int depth) { |
||||
if (depth > 0) { |
||||
return true; |
||||
} |
||||
return null; |
||||
} |
||||
})); |
||||
} |
||||
|
||||
public static boolean isAnnotated(AnnotatedElement element, String annotationType) { |
||||
return Boolean.TRUE.equals( |
||||
process(element, annotationType, new Processor<Boolean>() { |
||||
public Boolean process(Annotation annotation, int depth) { |
||||
return true; |
||||
} |
||||
})); |
||||
} |
||||
|
||||
public static Map<String, Object> getAnnotationAttributes(AnnotatedElement element, |
||||
String annotationType, final boolean classValuesAsString, |
||||
final boolean nestedAnnotationsAsMap) { |
||||
return process(element, annotationType, new Processor<Map<String, Object>>() { |
||||
public Map<String, Object> process(Annotation annotation, int depth) { |
||||
return AnnotationUtils.getAnnotationAttributes(annotation, |
||||
classValuesAsString, nestedAnnotationsAsMap); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public static MultiValueMap<String, Object> getAllAnnotationAttributes( |
||||
AnnotatedElement element, final String annotationType, |
||||
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { |
||||
final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>(); |
||||
process(element, annotationType, new Processor<Object>() { |
||||
public Object process(Annotation annotation, int depth) { |
||||
if (annotation.annotationType().getName().equals(annotationType)) { |
||||
for (Map.Entry<String, Object> entry : AnnotationUtils.getAnnotationAttributes( |
||||
annotation, classValuesAsString, nestedAnnotationsAsMap).entrySet()) { |
||||
attributes.add(entry.getKey(), entry.getValue()); |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
}); |
||||
return (attributes.size() == 0 ? null : attributes); |
||||
} |
||||
|
||||
/** |
||||
* Process all annotations of the specified annotation type and recursively all |
||||
* meta-annotations on the specified type. |
||||
* @param element the annotated element |
||||
* @param annotationType the annotation type to find. Only items of the specified type |
||||
* or meta-annotations of the specified type will be processed |
||||
* @param processor the processor |
||||
* @return the result of the processor |
||||
*/ |
||||
private static <T> T process(AnnotatedElement element, String annotationType, |
||||
Processor<T> processor) { |
||||
return recursivelyProcess(element, annotationType, processor, |
||||
new HashSet<AnnotatedElement>(), 0); |
||||
} |
||||
|
||||
private static <T> T recursivelyProcess(AnnotatedElement element, |
||||
String annotationType, Processor<T> processor, Set<AnnotatedElement> visited, |
||||
int depth) { |
||||
T result = null; |
||||
if (visited.add(element)) { |
||||
for (Annotation annotation : element.getAnnotations()) { |
||||
if (annotation.annotationType().getName().equals(annotationType) || depth > 0) { |
||||
result = result != null ? result : |
||||
processor.process(annotation, depth); |
||||
result = result != null ? result : |
||||
recursivelyProcess(annotation.annotationType(), annotationType, |
||||
processor, visited, depth + 1); |
||||
} |
||||
} |
||||
for (Annotation annotation : element.getAnnotations()) { |
||||
result = result != null ? result : |
||||
recursivelyProcess(annotation.annotationType(), annotationType, |
||||
processor, visited, depth); |
||||
} |
||||
|
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Callback interface used to process an annotation. |
||||
* @param <T> the result type |
||||
*/ |
||||
private static interface Processor<T> { |
||||
|
||||
/** |
||||
* Called to process the annotation. |
||||
* @param annotation the annotation to process |
||||
* @param depth the depth of the annotation relative to the initial match. For |
||||
* example a matched annotation will have a depth of 0, a meta-annotation 1 |
||||
* and a meta-meta-annotation 2 |
||||
* @return the result of the processing or {@code null} to continue |
||||
*/ |
||||
T process(Annotation annotation, int depth); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
/* |
||||
* 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.core.type; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
/** |
||||
* Defines access to the annotations of a specific type ({@link AnnotationMetadata class} |
||||
* or {@link MethodMetadata method}), in a form that does not necessarily require the |
||||
* class-loading. |
||||
* |
||||
* @author Juergen Hoeller |
||||
* @author Mark Fisher |
||||
* @author Mark Pollack |
||||
* @author Chris Beams |
||||
* @author Phillip Webb |
||||
* @since 4.0 |
||||
* @see AnnotationMetadata |
||||
* @see MethodMetadata |
||||
*/ |
||||
public interface AnnotatedTypeMetadata { |
||||
|
||||
/** |
||||
* Determine whether the underlying type has an annotation or |
||||
* meta-annotation of the given type defined. |
||||
* <p>If this method returns {@code true}, then |
||||
* {@link #getAnnotationAttributes} will return a non-null Map. |
||||
* @param annotationType the annotation type to look for |
||||
* @return whether a matching annotation is defined |
||||
*/ |
||||
boolean isAnnotated(String annotationType); |
||||
|
||||
/** |
||||
* Retrieve the attributes of the annotation of the given type, |
||||
* if any (i.e. if defined on the underlying class, as direct |
||||
* annotation or as meta-annotation). |
||||
* @param annotationType the annotation type to look for |
||||
* @return a Map of attributes, with the attribute name as key (e.g. "value") |
||||
* and the defined attribute value as Map value. This return value will be |
||||
* {@code null} if no matching annotation is defined. |
||||
*/ |
||||
Map<String, Object> getAnnotationAttributes(String annotationType); |
||||
|
||||
/** |
||||
* Retrieve the attributes of the annotation of the given type, |
||||
* if any (i.e. if defined on the underlying class, as direct |
||||
* annotation or as meta-annotation). |
||||
* @param annotationType the annotation type to look for |
||||
* @param classValuesAsString whether to convert class references to String |
||||
* class names for exposure as values in the returned Map, instead of Class |
||||
* references which might potentially have to be loaded first |
||||
* @return a Map of attributes, with the attribute name as key (e.g. "value") |
||||
* and the defined attribute value as Map value. This return value will be |
||||
* {@code null} if no matching annotation is defined. |
||||
*/ |
||||
Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString); |
||||
|
||||
/** |
||||
* Retrieve all attributes of all annotations of the given type, if any (i.e. if |
||||
* defined on the underlying method, as direct annotation or as meta-annotation). |
||||
* @param annotationType the annotation type to look for |
||||
* @return a MultiMap of attributes, with the attribute name as key (e.g. "value") and |
||||
* a list of the defined attribute values as Map value. This return value will |
||||
* be {@code null} if no matching annotation is defined. |
||||
*/ |
||||
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType); |
||||
|
||||
/** |
||||
* Retrieve all attributes of all annotations of the given type, if any (i.e. if |
||||
* defined on the underlying method, as direct annotation or as meta-annotation). |
||||
* @param annotationType the annotation type to look for |
||||
* @param classValuesAsString whether to convert class references to String |
||||
* @return a MultiMap of attributes, with the attribute name as key (e.g. "value") and |
||||
* a list of the defined attribute values as Map value. This return value will |
||||
* be {@code null} if no matching annotation is defined. |
||||
* @see #getAllAnnotationAttributes(String) |
||||
*/ |
||||
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, |
||||
boolean classValuesAsString); |
||||
|
||||
} |
||||
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
/* |
||||
* 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.core.type.classreading; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import org.springframework.asm.Type; |
||||
import org.springframework.core.annotation.AnnotationAttributes; |
||||
|
||||
/** |
||||
* Internal utility class used when reading annotations. |
||||
* |
||||
* @author Juergen Hoeller |
||||
* @author Mark Fisher |
||||
* @author Costin Leau |
||||
* @author Phillip Webb |
||||
* @since 4.0 |
||||
*/ |
||||
abstract class AnnotationReadingVisitorUtils { |
||||
|
||||
public static AnnotationAttributes convertClassValues(ClassLoader classLoader, |
||||
AnnotationAttributes original, boolean classValuesAsString) { |
||||
|
||||
if (original == null) { |
||||
return null; |
||||
} |
||||
|
||||
AnnotationAttributes result = new AnnotationAttributes(original.size()); |
||||
for (Map.Entry<String, Object> entry : original.entrySet()) { |
||||
try { |
||||
Object value = entry.getValue(); |
||||
if (value instanceof AnnotationAttributes) { |
||||
value = convertClassValues(classLoader, (AnnotationAttributes) value, |
||||
classValuesAsString); |
||||
} |
||||
else if (value instanceof AnnotationAttributes[]) { |
||||
AnnotationAttributes[] values = (AnnotationAttributes[])value; |
||||
for (int i = 0; i < values.length; i++) { |
||||
values[i] = convertClassValues(classLoader, values[i], |
||||
classValuesAsString); |
||||
} |
||||
} |
||||
else if (value instanceof Type) { |
||||
value = (classValuesAsString ? ((Type) value).getClassName() : |
||||
classLoader.loadClass(((Type) value).getClassName())); |
||||
} |
||||
else if (value instanceof Type[]) { |
||||
Type[] array = (Type[]) value; |
||||
Object[] convArray = (classValuesAsString ? new String[array.length] : new Class[array.length]); |
||||
for (int i = 0; i < array.length; i++) { |
||||
convArray[i] = (classValuesAsString ? array[i].getClassName() : |
||||
classLoader.loadClass(array[i].getClassName())); |
||||
} |
||||
value = convArray; |
||||
} |
||||
else if (classValuesAsString) { |
||||
if (value instanceof Class) { |
||||
value = ((Class<?>) value).getName(); |
||||
} |
||||
else if (value instanceof Class[]) { |
||||
Class<?>[] clazzArray = (Class[]) value; |
||||
String[] newValue = new String[clazzArray.length]; |
||||
for (int i = 0; i < clazzArray.length; i++) { |
||||
newValue[i] = clazzArray[i].getName(); |
||||
} |
||||
value = newValue; |
||||
} |
||||
} |
||||
result.put(entry.getKey(), value); |
||||
} |
||||
catch (Exception ex) { |
||||
// Class not found - can't resolve class reference in annotation attribute.
|
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue