From 28f9adf88e65752ab9bb974f44df19f5f49371d9 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 27 Jun 2025 14:58:53 +0200 Subject: [PATCH] Simplify media files detection in WebMvcConfigurationSupport Prior to this commit, `WebMvcConfigurationSupport` would configure file extensions/media types registrations based on classpath detection. Since gh-33894, the detection of message converters is located in a single place, `HttpMessageConverters`. This commit updates the `WebMvcConfigurationSupport` to use the actual message converters configured to decide which file extensions should be set up for content negotiation. See gh-33894 --- .../WebMvcConfigurationSupport.java | 70 +++++-------------- ...MvcConfigurationSupportExtensionTests.java | 4 -- .../WebMvcConfigurationSupportTests.java | 19 +++++ 3 files changed, 38 insertions(+), 55 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index a03789c6d5..cabcd4d104 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -16,11 +16,14 @@ package org.springframework.web.servlet.config.annotation; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import jakarta.servlet.ServletContext; import org.jspecify.annotations.Nullable; @@ -177,63 +180,19 @@ import org.springframework.web.util.pattern.PathPatternParser; */ public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware { - private static final boolean romePresent; - - private static final boolean jaxb2Present; - private static final boolean jacksonPresent; private static final boolean jackson2Present; - private static final boolean jacksonXmlPresent; - - private static final boolean jackson2XmlPresent; - - private static final boolean jacksonSmilePresent; - - private static final boolean jackson2SmilePresent; - - private static final boolean jacksonCborPresent; - - private static final boolean jackson2CborPresent; - - private static final boolean jacksonYamlPresent; - - private static final boolean jackson2YamlPresent; - - private static final boolean gsonPresent; - - private static final boolean jsonbPresent; - private static final boolean kotlinSerializationPresent; - private static final boolean kotlinSerializationCborPresent; - - private static final boolean kotlinSerializationJsonPresent; - - private static final boolean kotlinSerializationProtobufPresent; static { ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader(); - romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader); - jaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader); jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader); jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); - jacksonXmlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.xml.XmlMapper", classLoader); - jackson2XmlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); - jacksonSmilePresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.smile.SmileMapper", classLoader); - jackson2SmilePresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader); - jacksonCborPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.cbor.CBORMapper", classLoader); - jackson2CborPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader); - jacksonYamlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.yaml.YAMLMapper", classLoader); - jackson2YamlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader); - gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); - jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader); kotlinSerializationPresent = ClassUtils.isPresent("kotlinx.serialization.Serializable", classLoader); - kotlinSerializationCborPresent = kotlinSerializationPresent && ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader); - kotlinSerializationJsonPresent = kotlinSerializationPresent && ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader); - kotlinSerializationProtobufPresent = kotlinSerializationPresent && ClassUtils.isPresent("kotlinx.serialization.protobuf.ProtoBuf", classLoader); } @@ -444,23 +403,32 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv protected Map getDefaultMediaTypes() { Map map = new HashMap<>(4); - if (romePresent) { + List> messageConverters = getMessageConverters(); + Set supportedMediaTypes = messageConverters.stream() + .flatMap(converter -> converter.getSupportedMediaTypes().stream()) + .collect(Collectors.toSet()); + if (supportedMediaTypes.contains(MediaType.APPLICATION_ATOM_XML)) { map.put("atom", MediaType.APPLICATION_ATOM_XML); + } + if (supportedMediaTypes.contains(MediaType.APPLICATION_RSS_XML)) { map.put("rss", MediaType.APPLICATION_RSS_XML); } - if (jaxb2Present || jacksonXmlPresent || jackson2XmlPresent) { + MediaType xmlUtf8MediaType = new MediaType("application", "xml", StandardCharsets.UTF_8); + if (supportedMediaTypes.contains(MediaType.APPLICATION_XML) || + supportedMediaTypes.contains(xmlUtf8MediaType)) { map.put("xml", MediaType.APPLICATION_XML); } - if (jacksonPresent || jackson2Present || gsonPresent || jsonbPresent || kotlinSerializationJsonPresent) { + if (supportedMediaTypes.contains(MediaType.APPLICATION_JSON)) { map.put("json", MediaType.APPLICATION_JSON); } - if (jacksonSmilePresent || jackson2SmilePresent) { - map.put("smile", MediaType.valueOf("application/x-jackson-smile")); + MediaType smileMediaType = new MediaType("application", "x-jackson-smile"); + if (supportedMediaTypes.contains(smileMediaType)) { + map.put("smile", smileMediaType); } - if (jacksonCborPresent || jackson2CborPresent || kotlinSerializationCborPresent) { + if (supportedMediaTypes.contains(MediaType.APPLICATION_CBOR)) { map.put("cbor", MediaType.APPLICATION_CBOR); } - if (jacksonYamlPresent || jackson2YamlPresent) { + if (supportedMediaTypes.contains(MediaType.APPLICATION_ATOM_XML)) { map.put("yaml", MediaType.APPLICATION_YAML); } return map; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java index df7502bb62..e3b6190c65 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java @@ -91,7 +91,6 @@ import org.springframework.web.util.UrlPathHelper; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.springframework.http.MediaType.APPLICATION_JSON; -import static org.springframework.http.MediaType.APPLICATION_XML; /** * A test fixture with a subclass of {@link WebMvcConfigurationSupport} that also @@ -276,9 +275,6 @@ class WebMvcConfigurationSupportExtensionTests { ContentNegotiationManager manager = mapping.getContentNegotiationManager(); assertThat(manager.resolveMediaTypes(webRequest)).isEqualTo(Collections.singletonList(APPLICATION_JSON)); - request.setParameter("f", "xml"); - assertThat(manager.resolveMediaTypes(webRequest)).isEqualTo(Collections.singletonList(APPLICATION_XML)); - SimpleUrlHandlerMapping handlerMapping = (SimpleUrlHandlerMapping) this.config.resourceHandlerMapping( this.config.mvcContentNegotiationManager(), this.config.mvcConversionService(), this.config.mvcResourceUrlProvider()); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index a24e6bef88..816a532f26 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -45,6 +45,7 @@ import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractJacksonHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter; @@ -53,6 +54,7 @@ import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; @@ -213,6 +215,23 @@ class WebMvcConfigurationSupportTests { assertThat(bodyAdvice.get(3).getClass()).isEqualTo(KotlinResponseBodyAdvice.class); } + @Test + void contentNegotiationManager() { + ApplicationContext context = initContext(WebConfig.class); + ContentNegotiationManager contentNegotiation = context.getBean(ContentNegotiationManager.class); + Map mediaTypeMappings = contentNegotiation.getMediaTypeMappings(); + + assertThat(mediaTypeMappings) + .containsEntry("atom", MediaType.APPLICATION_ATOM_XML) + .containsEntry("rss", MediaType.APPLICATION_RSS_XML) + .containsEntry("rss", MediaType.APPLICATION_RSS_XML) + .containsEntry("xml", MediaType.APPLICATION_XML) + .containsEntry("json", MediaType.APPLICATION_JSON) + .containsEntry("smile", MediaType.valueOf("application/x-jackson-smile")) + .containsEntry("cbor", MediaType.APPLICATION_CBOR) + .containsEntry("yaml", MediaType.APPLICATION_YAML); + } + @Test void uriComponentsContributor() { ApplicationContext context = initContext(WebConfig.class);