Skip to content

Commit b1ad2c3

Browse files
committed
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
1 parent 663967f commit b1ad2c3

File tree

5 files changed

+147
-5
lines changed

5 files changed

+147
-5
lines changed

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
* not already contained in the {@link BeanFactory}.
3333
*
3434
* @author Phillip Webb
35+
* @author Andy Wilkinson
3536
*/
3637
@Target({ ElementType.TYPE, ElementType.METHOD })
3738
@Retention(RetentionPolicy.RUNTIME)
@@ -53,6 +54,21 @@
5354
*/
5455
String[] type() default {};
5556

57+
/**
58+
* The class type of beans that should be ignored when identifying matching beans.
59+
* @return the class types of beans to ignore
60+
* @since 1.2.5
61+
*/
62+
Class<?>[] ignored() default {};
63+
64+
/**
65+
* The class type names of beans that should be ignored when identifying matching
66+
* beans.
67+
* @return the class type names of beans to ignore
68+
* @since 1.2.5
69+
*/
70+
String[] ignoredType() default {};
71+
5672
/**
5773
* The annotation type decorating a bean that should be checked. The condition matches
5874
* when each class specified is missing from all beans in the

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -53,6 +53,7 @@
5353
* @author Phillip Webb
5454
* @author Dave Syer
5555
* @author Jakub Kubrynski
56+
* @author Andy Wilkinson
5657
*/
5758
@Order(Ordered.LOWEST_PRECEDENCE)
5859
public class OnBeanCondition extends SpringBootCondition implements
@@ -119,6 +120,10 @@ private List<String> getMatchingBeans(ConditionContext context, BeanSearchSpec b
119120
beanNames.addAll(getBeanNamesForType(beanFactory, type,
120121
context.getClassLoader(), considerHierarchy));
121122
}
123+
for (String ignoredType : beans.getIgnoredTypes()) {
124+
beanNames.removeAll(getBeanNamesForType(beanFactory, ignoredType,
125+
context.getClassLoader(), considerHierarchy));
126+
}
122127
for (String annotation : beans.getAnnotations()) {
123128
beanNames.addAll(Arrays.asList(getBeanNamesForAnnotation(beanFactory,
124129
annotation, context.getClassLoader(), considerHierarchy)));
@@ -207,6 +212,8 @@ private static class BeanSearchSpec {
207212

208213
private final List<String> annotations = new ArrayList<String>();
209214

215+
private final List<String> ignoredTypes = new ArrayList<String>();
216+
210217
private final SearchStrategy strategy;
211218

212219
public BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata,
@@ -217,6 +224,8 @@ public BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata,
217224
collect(attributes, "value", this.types);
218225
collect(attributes, "type", this.types);
219226
collect(attributes, "annotation", this.annotations);
227+
collect(attributes, "ignored", this.ignoredTypes);
228+
collect(attributes, "ignoredType", this.ignoredTypes);
220229
if (this.types.isEmpty() && this.names.isEmpty()) {
221230
addDeducedBeanType(context, metadata, this.types);
222231
}
@@ -244,8 +253,10 @@ private String annotationName(Class<?> annotationType) {
244253
private void collect(MultiValueMap<String, Object> attributes, String key,
245254
List<String> destination) {
246255
List<String[]> valueList = (List) attributes.get(key);
247-
for (String[] valueArray : valueList) {
248-
Collections.addAll(destination, valueArray);
256+
if (valueList != null) {
257+
for (String[] valueArray : valueList) {
258+
Collections.addAll(destination, valueArray);
259+
}
249260
}
250261
}
251262

@@ -303,6 +314,10 @@ public List<String> getAnnotations() {
303314
return this.annotations;
304315
}
305316

317+
public List<String> getIgnoredTypes() {
318+
return this.ignoredTypes;
319+
}
320+
306321
@Override
307322
public String toString() {
308323
StringBuilder string = new StringBuilder();

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JacksonHttpMessageConvertersConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ protected static class MappingJackson2HttpMessageConverterConfiguration {
5454
private HttpMapperProperties properties = new HttpMapperProperties();
5555

5656
@Bean
57-
@ConditionalOnMissingBean
57+
@ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class, ignoredType = "org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter")
5858
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(
5959
ObjectMapper objectMapper) {
6060
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.util.Assert;
3333

3434
import static org.hamcrest.Matchers.equalTo;
35+
import static org.hamcrest.Matchers.is;
3536
import static org.junit.Assert.assertEquals;
3637
import static org.junit.Assert.assertFalse;
3738
import static org.junit.Assert.assertThat;
@@ -187,6 +188,28 @@ public void testOnMissingBeanConditionWithFactoryBeanInXml() {
187188
equalTo("fromFactory"));
188189
}
189190

191+
@Test
192+
public void testOnMissingBeanConditionWithIgnoredSubclass() {
193+
this.context.register(CustomExampleBeanConfiguration.class,
194+
ConditionalOnIgnoredSubclass.class,
195+
PropertyPlaceholderAutoConfiguration.class);
196+
this.context.refresh();
197+
assertThat(this.context.getBeansOfType(ExampleBean.class).size(), is(equalTo(2)));
198+
assertThat(this.context.getBeansOfType(CustomExampleBean.class).size(),
199+
is(equalTo(1)));
200+
}
201+
202+
@Test
203+
public void testOnMissingBeanConditionWithIgnoredSubclassByName() {
204+
this.context.register(CustomExampleBeanConfiguration.class,
205+
ConditionalOnIgnoredSubclassByName.class,
206+
PropertyPlaceholderAutoConfiguration.class);
207+
this.context.refresh();
208+
assertThat(this.context.getBeansOfType(ExampleBean.class).size(), is(equalTo(2)));
209+
assertThat(this.context.getBeansOfType(CustomExampleBean.class).size(),
210+
is(equalTo(1)));
211+
}
212+
190213
@Configuration
191214
@ConditionalOnMissingBean(name = "foo")
192215
protected static class OnBeanNameConfiguration {
@@ -299,6 +322,38 @@ public ExampleBean createExampleBean() {
299322
}
300323
}
301324

325+
@Configuration
326+
protected static class ConditionalOnIgnoredSubclass {
327+
328+
@Bean
329+
@ConditionalOnMissingBean(value = ExampleBean.class, ignored = CustomExampleBean.class)
330+
public ExampleBean exampleBean() {
331+
return new ExampleBean("test");
332+
}
333+
334+
}
335+
336+
@Configuration
337+
protected static class ConditionalOnIgnoredSubclassByName {
338+
339+
@Bean
340+
@ConditionalOnMissingBean(value = ExampleBean.class, ignoredType = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests.CustomExampleBean")
341+
public ExampleBean exampleBean() {
342+
return new ExampleBean("test");
343+
}
344+
345+
}
346+
347+
@Configuration
348+
protected static class CustomExampleBeanConfiguration {
349+
350+
@Bean
351+
public CustomExampleBean customExampleBean() {
352+
return new CustomExampleBean();
353+
}
354+
355+
}
356+
302357
@Configuration
303358
@ConditionalOnMissingBean(annotation = EnableScheduling.class)
304359
protected static class OnAnnotationConfiguration {
@@ -369,6 +424,14 @@ public String toString() {
369424

370425
}
371426

427+
public static class CustomExampleBean extends ExampleBean {
428+
429+
public CustomExampleBean() {
430+
super("custom subclass");
431+
}
432+
433+
}
434+
372435
public static class ExampleFactoryBean implements FactoryBean<ExampleBean> {
373436

374437
public ExampleFactoryBean(String value) {

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,12 +22,17 @@
2222
import org.junit.After;
2323
import org.junit.Test;
2424
import org.springframework.beans.DirectFieldAccessor;
25+
import org.springframework.beans.factory.config.BeanDefinition;
2526
import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
2627
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
28+
import org.springframework.boot.autoconfigure.web.JacksonHttpMessageConvertersConfiguration.MappingJackson2HttpMessageConverterConfiguration;
2729
import org.springframework.boot.test.EnvironmentTestUtils;
2830
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2931
import org.springframework.context.annotation.Bean;
3032
import org.springframework.context.annotation.Configuration;
33+
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
34+
import org.springframework.hateoas.ResourceSupport;
35+
import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
3136
import org.springframework.http.converter.StringHttpMessageConverter;
3237
import org.springframework.http.converter.json.GsonHttpMessageConverter;
3338
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@@ -37,8 +42,11 @@
3742
import com.fasterxml.jackson.databind.ObjectMapper;
3843
import com.google.gson.Gson;
3944

45+
import static org.hamcrest.Matchers.equalTo;
46+
import static org.hamcrest.Matchers.is;
4047
import static org.junit.Assert.assertEquals;
4148
import static org.junit.Assert.assertNull;
49+
import static org.junit.Assert.assertThat;
4250
import static org.junit.Assert.assertTrue;
4351

4452
/**
@@ -212,6 +220,36 @@ public void httpMapperPropertiesAreAppliedWhenConfigured() throws Exception {
212220
.getPropertyValue("prettyPrint"));
213221
}
214222

223+
@Test
224+
public void typeConstrainedConverterDoesNotPreventAutoConfigurationOfJacksonConverter()
225+
throws Exception {
226+
this.context.register(JacksonObjectMapperBuilderConfig.class,
227+
TypeConstrainedConverterConfiguration.class,
228+
HttpMessageConvertersAutoConfiguration.class);
229+
this.context.refresh();
230+
231+
BeanDefinition beanDefinition = this.context
232+
.getBeanDefinition("mappingJackson2HttpMessageConverter");
233+
assertThat(beanDefinition.getFactoryBeanName(),
234+
is(equalTo(MappingJackson2HttpMessageConverterConfiguration.class
235+
.getName())));
236+
}
237+
238+
@Test
239+
public void typeConstrainedConverterFromSpringDataDoesNotPreventAutoConfigurationOfJacksonConverter()
240+
throws Exception {
241+
this.context.register(JacksonObjectMapperBuilderConfig.class,
242+
RepositoryRestMvcConfiguration.class,
243+
HttpMessageConvertersAutoConfiguration.class);
244+
this.context.refresh();
245+
246+
BeanDefinition beanDefinition = this.context
247+
.getBeanDefinition("mappingJackson2HttpMessageConverter");
248+
assertThat(beanDefinition.getFactoryBeanName(),
249+
is(equalTo(MappingJackson2HttpMessageConverterConfiguration.class
250+
.getName())));
251+
}
252+
215253
private void assertConverterBeanExists(Class<?> type, String beanName) {
216254
assertEquals(1, this.context.getBeansOfType(type).size());
217255
List<String> beanNames = Arrays.asList(this.context.getBeanDefinitionNames());
@@ -279,4 +317,14 @@ public StringHttpMessageConverter customStringMessageConverter() {
279317
}
280318
}
281319

320+
@Configuration
321+
protected static class TypeConstrainedConverterConfiguration {
322+
323+
@Bean
324+
public TypeConstrainedMappingJackson2HttpMessageConverter typeConstrainedConverter() {
325+
return new TypeConstrainedMappingJackson2HttpMessageConverter(
326+
ResourceSupport.class);
327+
}
328+
}
329+
282330
}

0 commit comments

Comments
 (0)