diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java index 895ba78b87..32b330d1fa 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java @@ -26,17 +26,22 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.cfg.ConstructorDetector; import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -75,6 +80,7 @@ import org.springframework.util.ReflectionUtils; * @author Johannes Edmeier * @author Phillip Webb * @author EddĂș MelĂ©ndez + * @author Ralf Ueberfuhr * @since 1.1.0 */ @AutoConfiguration @@ -342,4 +348,36 @@ public class JacksonAutoConfiguration { } + static class JacksonAutoConfigurationRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + if (ClassUtils.isPresent("com.fasterxml.jackson.databind.PropertyNamingStrategy", classLoader)) { + registerPropertyNamingStrategyHints(hints.reflection()); + } + } + + /** + * Register hints for the {@code configurePropertyNamingStrategyField} method to + * use. + * @param hints reflection hints + */ + private void registerPropertyNamingStrategyHints(ReflectionHints hints) { + registerPropertyNamingStrategyHints(hints, PropertyNamingStrategies.class); + // PropertyNamingStrategy is used pre Jackson 2.12 + registerPropertyNamingStrategyHints(hints, PropertyNamingStrategy.class); + } + + private void registerPropertyNamingStrategyHints(ReflectionHints hints, Class type) { + Stream.of(type.getDeclaredFields()).filter(this::isPropertyNamingStrategyField) + .forEach(hints::registerField); + } + + private boolean isPropertyNamingStrategyField(Field candidate) { + return ReflectionUtils.isPublicStaticFinal(candidate) + && candidate.getType().isAssignableFrom(PropertyNamingStrategy.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonRuntimeHints.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonRuntimeHints.java deleted file mode 100644 index 81229bddd1..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonRuntimeHints.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.springframework.boot.autoconfigure.jackson; - -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; -import org.springframework.aot.hint.ReflectionHints; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.util.ClassUtils; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.stream.Stream; - -/** - * {@link RuntimeHintsRegistrar} for Jackson. - * This is necessary for the configuration of Jackson's propertyNamingStrategy - * to work in native build and execution. - * - * @author Ralf Ueberfuhr - */ -class JacksonRuntimeHints implements RuntimeHintsRegistrar { - - /* - * We need this for - * - * JacksonAutoConfiguration - * .Jackson2ObjectMapperBuilderCustomizerConfiguration - * .StandardJackson2ObjectMapperBuilderCustomizer - * .configurePropertyNamingStrategyField(...) - * - * to get the field using reflection! - */ - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - if(ClassUtils.isPresent("com.fasterxml.jackson.databind.PropertyNamingStrategy", classLoader)) { - registerHints(hints.reflection()); - } - } - - private void registerHints(ReflectionHints reflection) { - Field[] fieldsOfStrategies = PropertyNamingStrategies.class.getDeclaredFields(); - // Jackson 2.12 pre - Field[] fieldsOfStrategy = PropertyNamingStrategy.class.getDeclaredFields(); - // Find all static fields that provide a PropertyNamingStrategy - // (this way we automatically support new constants - // that may be added by Jackson in the future) - Stream.concat(Stream.of(fieldsOfStrategies), Stream.of(fieldsOfStrategy)) - .filter(f -> Modifier.isStatic(f.getModifiers())) - .filter(f -> f.getType().isAssignableFrom(PropertyNamingStrategy.class)) - .forEach(reflection::registerField); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories index 7d90d926e9..52d9ac0211 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories @@ -1,6 +1,6 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\ -org.springframework.boot.autoconfigure.template.TemplateRuntimeHints,\ -org.springframework.boot.autoconfigure.jackson.JacksonRuntimeHints +org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.JacksonAutoConfigurationRuntimeHints,\ +org.springframework.boot.autoconfigure.template.TemplateRuntimeHints org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingProcessor diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java index 0f2d292790..163a30d8f7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java @@ -23,6 +23,7 @@ import java.time.Duration; import java.util.Date; import java.util.HashSet; import java.util.Set; +import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator.Mode; @@ -38,7 +39,9 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.cfg.ConstructorDetector; @@ -50,10 +53,14 @@ import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.ReflectionHintsPredicates; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.beans.factory.BeanCurrentlyInCreationException; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.JacksonAutoConfigurationRuntimeHints; import org.springframework.boot.jackson.JsonComponent; import org.springframework.boot.jackson.JsonMixin; import org.springframework.boot.jackson.JsonMixinModule; @@ -81,6 +88,7 @@ import static org.mockito.Mockito.mock; * @author Sebastien Deleuze * @author Johannes Edmeier * @author Grzegorz Poznachowski + * @author Ralf Ueberfuhr */ class JacksonAutoConfigurationTests { @@ -456,6 +464,22 @@ class JacksonAutoConfigurationTests { }); } + @Test + void shouldRegisterPropertyNamingStrategyHints() { + shouldRegisterPropertyNamingStrategyHints(PropertyNamingStrategies.class, "LOWER_CAMEL_CASE", + "UPPER_CAMEL_CASE", "SNAKE_CASE", "UPPER_SNAKE_CASE", "LOWER_CASE", "KEBAB_CASE", "LOWER_DOT_CASE"); + shouldRegisterPropertyNamingStrategyHints(PropertyNamingStrategy.class, "LOWER_CAMEL_CASE", "UPPER_CAMEL_CASE", + "SNAKE_CASE", "LOWER_CASE", "KEBAB_CASE", "LOWER_DOT_CASE"); + } + + private void shouldRegisterPropertyNamingStrategyHints(Class type, String... fieldNames) { + RuntimeHints hints = new RuntimeHints(); + new JacksonAutoConfigurationRuntimeHints().registerHints(hints, getClass().getClassLoader()); + ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection(); + Stream.of(fieldNames).map((name) -> reflection.onField(type, name)) + .forEach((predicate) -> assertThat(predicate).accepts(hints)); + } + private void assertParameterNamesModuleCreatorBinding(Mode expectedMode, Class... configClasses) { this.contextRunner.withUserConfiguration(configClasses).run((context) -> { DeserializationConfig deserializationConfig = context.getBean(ObjectMapper.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonRuntimeHintsTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonRuntimeHintsTests.java deleted file mode 100644 index 311aac0225..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonRuntimeHintsTests.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.springframework.boot.autoconfigure.jackson; - -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; -import org.junit.jupiter.api.Test; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.predicate.ReflectionHintsPredicates; -import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; - -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; - -class JacksonRuntimeHintsTests { - - @Test - void shouldRegisterHints() { - shouldRegisterFieldHintsFor(PropertyNamingStrategies.class, - "LOWER_CAMEL_CASE", "UPPER_CAMEL_CASE", - "SNAKE_CASE", "UPPER_SNAKE_CASE", - "LOWER_CASE", "KEBAB_CASE", "LOWER_DOT_CASE"); - } - - @Test - void shouldRegisterJackson_2_12_pre_Hints() { - shouldRegisterFieldHintsFor(PropertyNamingStrategy.class, - "LOWER_CAMEL_CASE", "UPPER_CAMEL_CASE", - "SNAKE_CASE", - "LOWER_CASE", "KEBAB_CASE", "LOWER_DOT_CASE"); - } - - private void shouldRegisterFieldHintsFor(Class clazz, String... fieldNames) { - RuntimeHints hints = new RuntimeHints(); - new JacksonRuntimeHints().registerHints(hints, getClass().getClassLoader()); - ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection(); - Stream.of(fieldNames) - .map(name -> reflection.onField(clazz, name)) - .forEach(predicate -> assertThat(predicate).accepts(hints)); - } - -}