From e9bca3e6def0dd156a4d82eee8ee0b9c1d054899 Mon Sep 17 00:00:00 2001 From: Ralf Ueberfuhr Date: Wed, 9 Nov 2022 07:17:20 +0100 Subject: [PATCH] Add reflection hints for PropertyNamingStrategies See gh-33080 --- .../jackson/JacksonRuntimeHints.java | 54 +++++++++++++++++++ .../resources/META-INF/spring/aot.factories | 3 +- .../jackson/JacksonRuntimeHintsTests.java | 41 ++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonRuntimeHints.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonRuntimeHintsTests.java 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 new file mode 100644 index 0000000000..81229bddd1 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonRuntimeHints.java @@ -0,0 +1,54 @@ +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 ece7966c11..7d90d926e9 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,5 +1,6 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\ -org.springframework.boot.autoconfigure.template.TemplateRuntimeHints +org.springframework.boot.autoconfigure.template.TemplateRuntimeHints,\ +org.springframework.boot.autoconfigure.jackson.JacksonRuntimeHints 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/JacksonRuntimeHintsTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonRuntimeHintsTests.java new file mode 100644 index 0000000000..311aac0225 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonRuntimeHintsTests.java @@ -0,0 +1,41 @@ +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)); + } + +}