diff --git a/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java b/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java index dd7a077c679..5fbe4ff3c46 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java +++ b/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java @@ -92,9 +92,9 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { this.clientMessageConverterConfigurer = new DefaultClientMessageConverterConfigurer(this.commonMessageConverters); this.serverMessageConverterConfigurer = new DefaultServerMessageConverterConfigurer(this.commonMessageConverters); if (registerDefaults) { - this.commonMessageConverters.registerDefaults(classLoader); - this.clientMessageConverterConfigurer.registerDefaults(classLoader); - this.serverMessageConverterConfigurer.registerDefaults(classLoader); + this.commonMessageConverters.registerDefaults(); + this.clientMessageConverterConfigurer.registerDefaults(); + this.serverMessageConverterConfigurer.registerDefaults(); } } @@ -163,6 +163,41 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { static class DefaultMessageConverterConfigurer { + private static final boolean isJacksonPresent; + + private static final boolean isJackson2Present; + + private static final boolean isGsonPresent; + + private static final boolean isJsonbPresent; + + private static final boolean isKotlinSerializationJsonPresent; + + private static final boolean isJacksonXmlPresent; + + private static final boolean isJackson2XmlPresent; + + private static final boolean isJaxb2Present; + + private static final boolean isJacksonSmilePresent; + + private static final boolean isJackson2SmilePresent; + + private static final boolean isJacksonCborPresent; + + private static final boolean isJackson2CborPresent; + + private static final boolean isKotlinSerializationCborPresent; + + private static final boolean isJacksonYamlPresent; + + private static final boolean isJackson2YamlPresent; + + private static final boolean isKotlinSerializationProtobufPresent; + + private static final boolean isRomePresent; + + private final @Nullable DefaultMessageConverterConfigurer inheritedMessageConverters; private @Nullable ByteArrayHttpMessageConverter byteArrayMessageConverter; @@ -183,6 +218,28 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { private final List> additionalMessageConverters = new ArrayList<>(); + static { + ClassLoader classLoader = DefaultClientMessageConverterConfigurer.class.getClassLoader(); + isJacksonPresent = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader); + isJackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && + ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); + isGsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); + isJsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader); + isKotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader); + isJacksonSmilePresent = isJacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.smile.SmileMapper", classLoader); + isJackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader); + isJaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader); + isJacksonXmlPresent = isJacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.xml.XmlMapper", classLoader); + isJackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); + isJacksonCborPresent = isJacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.cbor.CBORMapper", classLoader); + isJackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader); + isJacksonYamlPresent = isJacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.yaml.YAMLMapper", classLoader); + isJackson2YamlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader); + isKotlinSerializationCborPresent = ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader); + isKotlinSerializationProtobufPresent = ClassUtils.isPresent("kotlinx.serialization.protobuf.ProtoBuf", classLoader); + isRomePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader); + } + DefaultMessageConverterConfigurer() { this(null); } @@ -294,138 +351,69 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { return result; } - void registerDefaults(ClassLoader classLoader) { + void registerDefaults() { this.byteArrayMessageConverter = new ByteArrayHttpMessageConverter(); this.stringMessageConverter = new StringHttpMessageConverter(); - if (isJacksonPresent(classLoader)) { + if (isJacksonPresent) { this.jsonMessageConverter = new JacksonJsonHttpMessageConverter(); } - else if (isJackson2Present(classLoader)) { + else if (isJackson2Present) { this.jsonMessageConverter = new MappingJackson2HttpMessageConverter(); } - else if (isGsonPresent(classLoader)) { + else if (isGsonPresent) { this.jsonMessageConverter = new GsonHttpMessageConverter(); } - else if (isJsonbPresent(classLoader)) { + else if (isJsonbPresent) { this.jsonMessageConverter = new JsonbHttpMessageConverter(); } - else if (isKotlinSerializationJsonPresent(classLoader)) { + else if (isKotlinSerializationJsonPresent) { this.jsonMessageConverter = new KotlinSerializationJsonHttpMessageConverter(); } - if (isJacksonXmlPresent(classLoader)) { + if (isJacksonXmlPresent) { this.xmlMessageConverter = new JacksonXmlHttpMessageConverter(); } - else if (isJackson2XmlPresent(classLoader)) { + else if (isJackson2XmlPresent) { this.xmlMessageConverter = new MappingJackson2XmlHttpMessageConverter(); } - else if (isJaxb2Present(classLoader)) { + else if (isJaxb2Present) { this.xmlMessageConverter = new Jaxb2RootElementHttpMessageConverter(); } - if (isJacksonSmilePresent(classLoader)) { + if (isJacksonSmilePresent) { this.smileMessageConverter = new JacksonSmileHttpMessageConverter(); } - else if (isJackson2SmilePresent(classLoader)) { + else if (isJackson2SmilePresent) { this.smileMessageConverter = new MappingJackson2SmileHttpMessageConverter(); } - if (isJacksonCborPresent(classLoader)) { + if (isJacksonCborPresent) { this.cborMessageConverter = new JacksonCborHttpMessageConverter(); } - else if (isJackson2CborPresent(classLoader)) { + else if (isJackson2CborPresent) { this.cborMessageConverter = new MappingJackson2CborHttpMessageConverter(); } - else if (isKotlinSerializationCborPresent(classLoader)) { + else if (isKotlinSerializationCborPresent) { this.cborMessageConverter = new KotlinSerializationCborHttpMessageConverter(); } - if (isJacksonYamlPresent(classLoader)) { + if (isJacksonYamlPresent) { this.yamlMessageConverter = new JacksonYamlHttpMessageConverter(); } - else if (isJackson2YamlPresent(classLoader)) { + else if (isJackson2YamlPresent) { this.yamlMessageConverter = new MappingJackson2YamlHttpMessageConverter(); } - if (isKotlinSerializationProtobufPresent(classLoader)) { + if (isKotlinSerializationProtobufPresent) { this.additionalMessageConverters.add(new KotlinSerializationProtobufHttpMessageConverter()); } - if (isRomePresent(classLoader)) { + if (isRomePresent) { this.additionalMessageConverters.add(new AtomFeedHttpMessageConverter()); this.additionalMessageConverters.add(new RssChannelHttpMessageConverter()); } } - private static boolean isRomePresent(ClassLoader classLoader) { - return ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader); - } - - private static boolean isJacksonPresent(ClassLoader classLoader) { - return ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader); - } - - private static boolean isJackson2Present(ClassLoader classLoader) { - return ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && - ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); - } - - private static boolean isGsonPresent(ClassLoader classLoader) { - return ClassUtils.isPresent("com.google.gson.Gson", classLoader); - } - - private static boolean isJsonbPresent(ClassLoader classLoader) { - return ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader); - } - - private static boolean isKotlinSerializationJsonPresent(ClassLoader classLoader) { - return ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader); - } - - private static boolean isJacksonSmilePresent(ClassLoader classLoader) { - return isJacksonPresent(classLoader) && ClassUtils.isPresent("tools.jackson.dataformat.smile.SmileMapper", classLoader); - } - - private static boolean isJackson2SmilePresent(ClassLoader classLoader) { - return ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader); - } - - private static boolean isJaxb2Present(ClassLoader classLoader) { - return ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader); - } - - private static boolean isJacksonXmlPresent(ClassLoader classLoader) { - return isJacksonPresent(classLoader) && ClassUtils.isPresent("tools.jackson.dataformat.xml.XmlMapper", classLoader); - } - - private static boolean isJackson2XmlPresent(ClassLoader classLoader) { - return ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); - } - - private static boolean isJacksonCborPresent(ClassLoader classLoader) { - return isJacksonPresent(classLoader) && ClassUtils.isPresent("tools.jackson.dataformat.cbor.CBORMapper", classLoader); - } - - private static boolean isJackson2CborPresent(ClassLoader classLoader) { - return ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader); - } - - private static boolean isJacksonYamlPresent(ClassLoader classLoader) { - return isJacksonPresent(classLoader) && ClassUtils.isPresent("tools.jackson.dataformat.yaml.YAMLMapper", classLoader); - } - - private static boolean isJackson2YamlPresent(ClassLoader classLoader) { - return ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader); - } - - private static boolean isKotlinSerializationCborPresent(ClassLoader classLoader) { - return ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader); - } - - private static boolean isKotlinSerializationProtobufPresent(ClassLoader classLoader) { - return ClassUtils.isPresent("kotlinx.serialization.protobuf.ProtoBuf", classLoader); - } - } static class DefaultClientMessageConverterConfigurer extends DefaultMessageConverterConfigurer implements ClientMessageConverterConfigurer { @@ -489,7 +477,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { } @Override - void registerDefaults(ClassLoader classLoader) { + void registerDefaults() { this.resourceMessageConverters = Collections.singletonList(new ResourceHttpMessageConverter(false)); } @@ -576,7 +564,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { } @Override - void registerDefaults(ClassLoader classLoader) { + void registerDefaults() { this.resourceMessageConverters = Arrays.asList(new ResourceHttpMessageConverter(), new ResourceRegionHttpMessageConverter()); } diff --git a/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java b/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java index dbd3b826e0e..9de7e5123d5 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java @@ -26,42 +26,24 @@ import java.util.Set; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import com.google.gson.Gson; -import com.rometools.rome.feed.WireFeed; -import jakarta.json.bind.Jsonb; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.dataformat.cbor.CBORMapper; -import tools.jackson.dataformat.smile.SmileMapper; -import tools.jackson.dataformat.xml.XmlMapper; -import tools.jackson.dataformat.yaml.YAMLMapper; import org.springframework.core.SmartClassLoader; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter; -import org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter; -import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter; import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter; import org.springframework.http.converter.feed.RssChannelHttpMessageConverter; -import org.springframework.http.converter.json.GsonHttpMessageConverter; import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; -import org.springframework.http.converter.json.JsonbHttpMessageConverter; -import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.protobuf.KotlinSerializationProtobufHttpMessageConverter; import org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter; -import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter; -import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; -import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.http.converter.yaml.JacksonYamlHttpMessageConverter; -import org.springframework.http.converter.yaml.MappingJackson2YamlHttpMessageConverter; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -282,101 +264,6 @@ class DefaultHttpMessageConvertersTests { } } - @Nested - class ClasspathDetectionTests { - - @Test - void jsonUsesJackson2WhenJacksonNotPresent() { - var classLoader = new FilteredClassLoader(ObjectMapper.class); - var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build(); - assertThat(converters.forServer()).hasAtLeastOneElementOfType(MappingJackson2HttpMessageConverter.class) - .doesNotHaveAnyElementsOfTypes(JacksonJsonHttpMessageConverter.class); - } - - @Test - void jsonUsesGsonWhenJacksonNotPresent() { - var classLoader = new FilteredClassLoader(ObjectMapper.class, com.fasterxml.jackson.databind.ObjectMapper.class); - var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build(); - assertThat(converters.forServer()).hasAtLeastOneElementOfType(GsonHttpMessageConverter.class) - .doesNotHaveAnyElementsOfTypes(JacksonJsonHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class); - } - - @Test - void jsonUsesJsonbWhenJacksonAndGsonNotPresent() { - var classLoader = new FilteredClassLoader(ObjectMapper.class, com.fasterxml.jackson.databind.ObjectMapper.class, Gson.class); - var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build(); - assertThat(converters.forServer()).hasAtLeastOneElementOfType(JsonbHttpMessageConverter.class) - .doesNotHaveAnyElementsOfTypes(JacksonJsonHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class, - GsonHttpMessageConverter.class); - } - - @Test - void jsonUsesKotlinWhenOthersNotPresent() { - var classLoader = new FilteredClassLoader(ObjectMapper.class, com.fasterxml.jackson.databind.ObjectMapper.class, Gson.class, Jsonb.class); - var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build(); - assertThat(converters.forServer()).hasAtLeastOneElementOfType(KotlinSerializationJsonHttpMessageConverter.class) - .doesNotHaveAnyElementsOfTypes(JacksonJsonHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class, - GsonHttpMessageConverter.class, JsonbHttpMessageConverter.class); - } - - @Test - void xmlUsesJackson2WhenJacksonNotPresent() { - var classLoader = new FilteredClassLoader(XmlMapper.class); - var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build(); - assertThat(converters.forServer()).hasAtLeastOneElementOfType(MappingJackson2XmlHttpMessageConverter.class) - .doesNotHaveAnyElementsOfTypes(JacksonXmlHttpMessageConverter.class); - } - - @Test - void xmlUsesJaxbWhenJacksonNotPresent() { - var classLoader = new FilteredClassLoader(XmlMapper.class, com.fasterxml.jackson.dataformat.xml.XmlMapper.class); - var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build(); - assertThat(converters.forServer()).hasAtLeastOneElementOfType(Jaxb2RootElementHttpMessageConverter.class) - .doesNotHaveAnyElementsOfTypes(JacksonXmlHttpMessageConverter.class, MappingJackson2XmlHttpMessageConverter.class); - } - - @Test - void smileUsesJackson2WhenJacksonNotPresent() { - var classLoader = new FilteredClassLoader(SmileMapper.class); - var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build(); - assertThat(converters.forServer()).hasAtLeastOneElementOfType(MappingJackson2SmileHttpMessageConverter.class) - .doesNotHaveAnyElementsOfTypes(JacksonSmileHttpMessageConverter.class); - } - - @Test - void cborUsesJackson2WhenJacksonNotPresent() { - var classLoader = new FilteredClassLoader(CBORMapper.class); - var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build(); - assertThat(converters.forServer()).hasAtLeastOneElementOfType(MappingJackson2CborHttpMessageConverter.class) - .doesNotHaveAnyElementsOfTypes(JacksonCborHttpMessageConverter.class); - } - - @Test - void cborUsesKotlinWhenJacksonNotPresent() { - var classLoader = new FilteredClassLoader(CBORMapper.class, com.fasterxml.jackson.dataformat.cbor.CBORFactory.class); - var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build(); - assertThat(converters.forServer()).hasAtLeastOneElementOfType(KotlinSerializationCborHttpMessageConverter.class) - .doesNotHaveAnyElementsOfTypes(JacksonCborHttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class); - } - - @Test - void yamlUsesJackson2WhenJacksonNotPresent() { - var classLoader = new FilteredClassLoader(YAMLMapper.class); - var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build(); - assertThat(converters.forServer()).hasAtLeastOneElementOfType(MappingJackson2YamlHttpMessageConverter.class) - .doesNotHaveAnyElementsOfTypes(JacksonYamlHttpMessageConverter.class); - } - - @Test - void atomAndRssNotConfiguredWhenRomeNotPresent() { - var classLoader = new FilteredClassLoader(WireFeed.class); - var converters = new DefaultHttpMessageConverters.DefaultBuilder(true, classLoader).build(); - assertThat(converters.forServer()).doesNotHaveAnyElementsOfTypes(AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class); - } - - } - - @SuppressWarnings("unchecked") private T findMessageConverter(Class converterType, Iterable> converters) { return (T) StreamSupport