diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java index c60d393faff..3d6790ed185 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.condition; +import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -45,6 +46,13 @@ public @interface ConditionalOnBean { */ Class[] value() default {}; + /** + * The annotation type decorating a bean that should be checked. The condition matches + * when each class specified is missing from beans in the {@link ApplicationContext}. + * @return the class types of beans to check + */ + Class[] annotation() default {}; + /** * The names of beans to check. The condition matches when any of the bean names * specified is contained in the {@link ApplicationContext}. diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java index a4ebe596194..9ebf3280ddd 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.condition; +import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -45,6 +46,14 @@ public @interface ConditionalOnMissingBean { */ Class[] value() default {}; + /** + * The annotation type decorating a bean that should be checked. The condition matches + * when each class specified is missing from all beans in the + * {@link ApplicationContext}. + * @return the class types of beans to check + */ + Class[] annotation() default {}; + /** * The names of beans to check. The condition matches when each bean name specified is * missing in the {@link ApplicationContext}. diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java index 24aa63de7ee..8bfc89639ff 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java @@ -16,10 +16,12 @@ package org.springframework.boot.autoconfigure.condition; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryUtils; @@ -36,6 +38,7 @@ import org.springframework.util.ClassUtils; import org.springframework.util.MultiValueMap; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodCallback; +import org.springframework.util.StringUtils; /** * {@link Condition} that checks for the presence or absence of specific beans. @@ -96,6 +99,11 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit context.getClassLoader(), considerHierarchy))); } + for (String annotation : beans.getAnnotations()) { + beanNames.addAll(Arrays.asList(getBeanNamesForAnnotation(beanFactory, + annotation, context.getClassLoader(), considerHierarchy))); + } + for (String beanName : beans.getNames()) { if (containsBean(beanFactory, beanName, considerHierarchy)) { beanNames.add(beanName); @@ -132,10 +140,45 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit } } + private String[] getBeanNamesForAnnotation( + ConfigurableListableBeanFactory beanFactory, String type, + ClassLoader classLoader, boolean considerHierarchy) throws LinkageError { + String[] result = NO_BEANS; + try { + @SuppressWarnings("unchecked") + Class typeClass = (Class) ClassUtils + .forName(type, classLoader); + Map annotated = beanFactory.getBeansWithAnnotation(typeClass); + result = annotated.keySet().toArray(new String[annotated.size()]); + if (considerHierarchy) { + if (beanFactory.getParentBeanFactory() instanceof ConfigurableListableBeanFactory) { + String[] parentResult = getBeanNamesForAnnotation( + (ConfigurableListableBeanFactory) beanFactory + .getParentBeanFactory(), + type, classLoader, true); + List resultList = new ArrayList(); + resultList.addAll(Arrays.asList(result)); + for (String beanName : parentResult) { + if (!resultList.contains(beanName) + && !beanFactory.containsLocalBean(beanName)) { + resultList.add(beanName); + } + } + result = StringUtils.toStringArray(resultList); + } + } + return result; + } + catch (ClassNotFoundException ex) { + return NO_BEANS; + } + } + private static class BeanSearchSpec { private List names = new ArrayList(); private List types = new ArrayList(); + private List annotations = new ArrayList(); private SearchStrategy strategy; public BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata, @@ -144,12 +187,16 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit .getAllAnnotationAttributes(annotationType.getName(), true); collect(attributes, "name", this.names); collect(attributes, "value", this.types); + collect(attributes, "annotation", this.annotations); if (this.types.isEmpty() && this.names.isEmpty()) { addDeducedBeanType(context, metadata, this.types); } - Assert.isTrue(!this.types.isEmpty() || !this.names.isEmpty(), "@" - + ClassUtils.getShortName(annotationType) - + " annotations must specify at least one bean"); + Assert.isTrue( + !this.types.isEmpty() || !this.names.isEmpty() + || !this.annotations.isEmpty(), + "@" + + ClassUtils.getShortName(annotationType) + + " annotations must specify at least one bean (type, name or annotation)"); this.strategy = (SearchStrategy) metadata.getAnnotationAttributes( annotationType.getName()).get("search"); } @@ -204,6 +251,10 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit return this.types; } + public List getAnnotations() { + return this.annotations; + } + @Override public String toString() { return new ToStringCreator(this).append("names", this.names) diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java index aa322f9777a..b861a85cc42 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java @@ -23,6 +23,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportResource; +import org.springframework.scheduling.annotation.EnableScheduling; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -32,6 +33,7 @@ import static org.junit.Assert.assertTrue; * * @author Dave Syer */ +// FIXME: unignore test @Ignore public class ConditionalOnBeanTests { @@ -77,6 +79,14 @@ public class ConditionalOnBeanTests { assertEquals("bar", this.context.getBean("bar")); } + @Test + public void testAnnotationOnBeanCondition() { + this.context.register(FooConfiguration.class, OnAnnotationConfiguration.class); + this.context.refresh(); + assertTrue(this.context.containsBean("bar")); + assertEquals("bar", this.context.getBean("bar")); + } + @Configuration @ConditionalOnBean(name = "foo") protected static class OnBeanNameConfiguration { @@ -86,6 +96,15 @@ public class ConditionalOnBeanTests { } } + @Configuration + @ConditionalOnBean(annotation = EnableScheduling.class) + protected static class OnAnnotationConfiguration { + @Bean + public String bar() { + return "bar"; + } + } + @Configuration @ConditionalOnBean(String.class) protected static class OnBeanClassConfiguration { @@ -96,6 +115,7 @@ public class ConditionalOnBeanTests { } @Configuration + @EnableScheduling protected static class FooConfiguration { @Bean public String foo() { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java index 9f67bd8d14f..150dd08f813 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java @@ -20,6 +20,7 @@ import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; @@ -84,6 +85,14 @@ public class ConditionalOnMissingBeanTests { assertThat(this.context.getBeansOfType(ExampleBean.class).size(), equalTo(1)); } + @Test + public void testAnnotationOnMissingBeanCondition() { + this.context.register(FooConfiguration.class, OnAnnotationConfiguration.class); + this.context.refresh(); + assertFalse(this.context.containsBean("bar")); + assertEquals("foo", this.context.getBean("foo")); + } + @Configuration @ConditionalOnMissingBean(name = "foo") protected static class OnBeanNameConfiguration { @@ -94,6 +103,16 @@ public class ConditionalOnMissingBeanTests { } @Configuration + @ConditionalOnMissingBean(annotation = EnableScheduling.class) + protected static class OnAnnotationConfiguration { + @Bean + public String bar() { + return "bar"; + } + } + + @Configuration + @EnableScheduling protected static class FooConfiguration { @Bean public String foo() {