Ignore type-constrained converter when auto-configuring Jackson converter
Previously, JacksonHttpMessageConvertersConfiguration would configure a general-purpose MappingJackson2HttpMessageConverter only if there was no existing MappingJackson2HttpMessageConverter in the application context. This was problematic when a TypeConstrainedMappingJackson2HttpMessageConverter bean was present. Such a bean is only capable of performing conversion for a specific type, and therefore is no substitute for a general purpose converter, yet its presence was causing the auto-configuration of a general purpose converters to be turned off. This would leave Spring MVC’s default converter being used for application/json requests which would not honour the user’s Jackson configuration. This commit enhances @ConditionalOnMissingBean so that the annotation can be used to specify one or more types that should be ignored when searching for beans. This allows the TypeConstrainedMappingJackson2HttpMessageConverter beans that are published by Spring Data REST to be ignored such that the general-purpose MappingJackson2HttpMessageConverter is still auto-configured. Fixes gh-2914
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<String> annotations = new ArrayList<String>();
|
||||
|
||||
private final List<String> ignoredTypes = new ArrayList<String>();
|
||||
|
||||
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<String, Object> attributes, String key,
|
||||
List<String> destination) {
|
||||
List<String[]> 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<String> getIgnoredTypes() {
|
||||
return this.ignoredTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder string = new StringBuilder();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<ExampleBean> {
|
||||
|
||||
public ExampleFactoryBean(String value) {
|
||||
|
||||
@@ -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<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user