diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java index cf91fa64b..80496d5cf 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java @@ -25,12 +25,14 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.google.gson.Gson; import io.cloudevents.spring.messaging.CloudEventMessageConverter; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -59,6 +61,7 @@ import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.core.KotlinDetector; import org.springframework.core.ResolvableType; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.support.ConfigurableConversionService; @@ -215,6 +218,7 @@ public class ContextFunctionCatalogAutoConfiguration { return new GsonMapper(gson); } + @SuppressWarnings("unchecked") private JsonMapper jackson(ApplicationContext context) { ObjectMapper mapper; try { @@ -225,6 +229,19 @@ public class ContextFunctionCatalogAutoConfiguration { mapper.registerModule(new JavaTimeModule()); } + if (KotlinDetector.isKotlinPresent()) { + try { + if (!mapper.getRegisteredModuleIds().contains("com.fasterxml.jackson.module.kotlin.KotlinModule")) { + Class kotlinModuleClass = (Class) + ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", ClassUtils.getDefaultClassLoader()); + Module kotlinModule = BeanUtils.instantiateClass(kotlinModuleClass); + mapper.registerModule(kotlinModule); + } + } + catch (ClassNotFoundException ex) { + // jackson-module-kotlin not available + } + } mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); mapper.configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java index 5c8828e87..2522d9c48 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java @@ -22,7 +22,6 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import com.fasterxml.jackson.module.kotlin.KotlinModule; import kotlin.Unit; import kotlin.jvm.functions.Function0; import kotlin.jvm.functions.Function1; @@ -37,14 +36,10 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.cloud.function.context.FunctionRegistration; import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.ResolvableType; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.ObjectUtils; /** @@ -62,19 +57,6 @@ public class KotlinLambdaToFunctionAutoConfiguration { protected final Log logger = LogFactory.getLog(getClass()); - @Bean - @ConditionalOnMissingBean - @ConditionalOnClass(name = {"org.springframework.http.converter.json.Jackson2ObjectMapperBuilder", - "com.fasterxml.jackson.module.kotlin.KotlinModule"}) - Jackson2ObjectMapperBuilderCustomizer customizer() { - return new Jackson2ObjectMapperBuilderCustomizer() { - @Override - public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) { - jacksonObjectMapperBuilder.modulesToInstall(KotlinModule.class); - } - }; - } - @SuppressWarnings({ "unchecked", "rawtypes" }) public static final class KotlinFunctionWrapper implements Function, Supplier, Consumer, diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/utils/JsonMapperTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/utils/JsonMapperTests.java index eb80f5101..1b7e28eef 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/utils/JsonMapperTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/utils/JsonMapperTests.java @@ -16,6 +16,7 @@ package org.springframework.cloud.function.utils; +import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; import java.util.Date; @@ -38,6 +39,7 @@ import org.springframework.cloud.function.json.JsonMapper; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.core.ResolvableType; +import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -84,6 +86,16 @@ public class JsonMapperTests { assertThat(convertedJson).contains("\"zonedDateTime\":\"2024-10-16T16:13:29.964361+02:00\""); } + @Test + public void testKotlinModuleRegistration() throws Exception { + ApplicationContext context = SpringApplication.run(EmptyConfiguration.class); + JsonMapper jsonMapper = context.getBean(JsonMapper.class); + Field mapperField = ReflectionUtils.findField(jsonMapper.getClass(), "mapper"); + mapperField.setAccessible(true); + ObjectMapper mapper = (ObjectMapper) mapperField.get(jsonMapper); + assertThat(mapper.getRegisteredModuleIds()).contains("com.fasterxml.jackson.module.kotlin.KotlinModule"); + } + @ParameterizedTest @MethodSource("params") public void vanillaArray(JsonMapper mapper) {