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 55e84e4499..707a84a0a2 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 @@ -32,6 +32,7 @@ import org.springframework.context.annotation.Conditional; * not already contained in the {@link BeanFactory}. * * @author Phillip Webb + * @author Andy Wilkinson */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @@ -53,6 +54,21 @@ public @interface ConditionalOnMissingBean { */ String[] type() default {}; + /** + * The class type of beans that should be ignored when identifying matching beans. + * @return the class types of beans to ignore + * @since 1.2.5 + */ + Class[] ignored() default {}; + + /** + * The class type names of beans that should be ignored when identifying matching + * beans. + * @return the class type names of beans to ignore + * @since 1.2.5 + */ + String[] ignoredType() 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 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 011d211f74..26461ff827 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -53,6 +53,7 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author Dave Syer * @author Jakub Kubrynski + * @author Andy Wilkinson */ @Order(Ordered.LOWEST_PRECEDENCE) public class OnBeanCondition extends SpringBootCondition implements @@ -119,6 +120,10 @@ public class OnBeanCondition extends SpringBootCondition implements beanNames.addAll(getBeanNamesForType(beanFactory, type, context.getClassLoader(), considerHierarchy)); } + for (String ignoredType : beans.getIgnoredTypes()) { + beanNames.removeAll(getBeanNamesForType(beanFactory, ignoredType, + context.getClassLoader(), considerHierarchy)); + } for (String annotation : beans.getAnnotations()) { beanNames.addAll(Arrays.asList(getBeanNamesForAnnotation(beanFactory, annotation, context.getClassLoader(), considerHierarchy))); @@ -207,6 +212,8 @@ public class OnBeanCondition extends SpringBootCondition implements private final List annotations = new ArrayList(); + private final List ignoredTypes = new ArrayList(); + private final SearchStrategy strategy; public BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata, @@ -217,6 +224,8 @@ public class OnBeanCondition extends SpringBootCondition implements collect(attributes, "value", this.types); collect(attributes, "type", this.types); collect(attributes, "annotation", this.annotations); + collect(attributes, "ignored", this.ignoredTypes); + collect(attributes, "ignoredType", this.ignoredTypes); if (this.types.isEmpty() && this.names.isEmpty()) { addDeducedBeanType(context, metadata, this.types); } @@ -244,8 +253,10 @@ public class OnBeanCondition extends SpringBootCondition implements private void collect(MultiValueMap attributes, String key, List destination) { List valueList = (List) attributes.get(key); - for (String[] valueArray : valueList) { - Collections.addAll(destination, valueArray); + if (valueList != null) { + for (String[] valueArray : valueList) { + Collections.addAll(destination, valueArray); + } } } @@ -303,6 +314,10 @@ public class OnBeanCondition extends SpringBootCondition implements return this.annotations; } + public List getIgnoredTypes() { + return this.ignoredTypes; + } + @Override public String toString() { StringBuilder string = new StringBuilder(); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JacksonHttpMessageConvertersConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JacksonHttpMessageConvertersConfiguration.java index 9e315c6515..2acd118de7 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JacksonHttpMessageConvertersConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JacksonHttpMessageConvertersConfiguration.java @@ -54,7 +54,7 @@ class JacksonHttpMessageConvertersConfiguration { private HttpMapperProperties properties = new HttpMapperProperties(); @Bean - @ConditionalOnMissingBean + @ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class, ignoredType = "org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter") public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter( ObjectMapper objectMapper) { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter( 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 507c2549aa..c58edd25bb 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 @@ -32,6 +32,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.util.Assert; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; @@ -187,6 +188,28 @@ public class ConditionalOnMissingBeanTests { equalTo("fromFactory")); } + @Test + public void testOnMissingBeanConditionWithIgnoredSubclass() { + this.context.register(CustomExampleBeanConfiguration.class, + ConditionalOnIgnoredSubclass.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBeansOfType(ExampleBean.class).size(), is(equalTo(2))); + assertThat(this.context.getBeansOfType(CustomExampleBean.class).size(), + is(equalTo(1))); + } + + @Test + public void testOnMissingBeanConditionWithIgnoredSubclassByName() { + this.context.register(CustomExampleBeanConfiguration.class, + ConditionalOnIgnoredSubclassByName.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBeansOfType(ExampleBean.class).size(), is(equalTo(2))); + assertThat(this.context.getBeansOfType(CustomExampleBean.class).size(), + is(equalTo(1))); + } + @Configuration @ConditionalOnMissingBean(name = "foo") protected static class OnBeanNameConfiguration { @@ -299,6 +322,38 @@ public class ConditionalOnMissingBeanTests { } } + @Configuration + protected static class ConditionalOnIgnoredSubclass { + + @Bean + @ConditionalOnMissingBean(value = ExampleBean.class, ignored = CustomExampleBean.class) + public ExampleBean exampleBean() { + return new ExampleBean("test"); + } + + } + + @Configuration + protected static class ConditionalOnIgnoredSubclassByName { + + @Bean + @ConditionalOnMissingBean(value = ExampleBean.class, ignoredType = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests.CustomExampleBean") + public ExampleBean exampleBean() { + return new ExampleBean("test"); + } + + } + + @Configuration + protected static class CustomExampleBeanConfiguration { + + @Bean + public CustomExampleBean customExampleBean() { + return new CustomExampleBean(); + } + + } + @Configuration @ConditionalOnMissingBean(annotation = EnableScheduling.class) protected static class OnAnnotationConfiguration { @@ -369,6 +424,14 @@ public class ConditionalOnMissingBeanTests { } + public static class CustomExampleBean extends ExampleBean { + + public CustomExampleBean() { + super("custom subclass"); + } + + } + public static class ExampleFactoryBean implements FactoryBean { public ExampleFactoryBean(String value) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java index f5e02e1968..3caf102054 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -22,12 +22,17 @@ import java.util.List; import org.junit.After; import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.JacksonHttpMessageConvertersConfiguration.MappingJackson2HttpMessageConverterConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; @@ -37,8 +42,11 @@ import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConve import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** @@ -212,6 +220,36 @@ public class HttpMessageConvertersAutoConfigurationTests { .getPropertyValue("prettyPrint")); } + @Test + public void typeConstrainedConverterDoesNotPreventAutoConfigurationOfJacksonConverter() + throws Exception { + this.context.register(JacksonObjectMapperBuilderConfig.class, + TypeConstrainedConverterConfiguration.class, + HttpMessageConvertersAutoConfiguration.class); + this.context.refresh(); + + BeanDefinition beanDefinition = this.context + .getBeanDefinition("mappingJackson2HttpMessageConverter"); + assertThat(beanDefinition.getFactoryBeanName(), + is(equalTo(MappingJackson2HttpMessageConverterConfiguration.class + .getName()))); + } + + @Test + public void typeConstrainedConverterFromSpringDataDoesNotPreventAutoConfigurationOfJacksonConverter() + throws Exception { + this.context.register(JacksonObjectMapperBuilderConfig.class, + RepositoryRestMvcConfiguration.class, + HttpMessageConvertersAutoConfiguration.class); + this.context.refresh(); + + BeanDefinition beanDefinition = this.context + .getBeanDefinition("mappingJackson2HttpMessageConverter"); + assertThat(beanDefinition.getFactoryBeanName(), + is(equalTo(MappingJackson2HttpMessageConverterConfiguration.class + .getName()))); + } + private void assertConverterBeanExists(Class type, String beanName) { assertEquals(1, this.context.getBeansOfType(type).size()); List beanNames = Arrays.asList(this.context.getBeanDefinitionNames()); @@ -279,4 +317,14 @@ public class HttpMessageConvertersAutoConfigurationTests { } } + @Configuration + protected static class TypeConstrainedConverterConfiguration { + + @Bean + public TypeConstrainedMappingJackson2HttpMessageConverter typeConstrainedConverter() { + return new TypeConstrainedMappingJackson2HttpMessageConverter( + ResourceSupport.class); + } + } + }