diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java index 267833da52..2daf34669b 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryMethodInvoker.java @@ -16,7 +16,6 @@ package org.springframework.data.repository.core.support; import kotlin.Unit; -import kotlin.reflect.KFunction; import kotlinx.coroutines.flow.Flow; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -46,6 +45,7 @@ * * @author Mark Paluch * @author Christoph Strobl + * @author Greg Taube * @since 2.4 * @see #forFragmentMethod(Method, Object, Method) * @see #forRepositoryQuery(Method, RepositoryQuery) @@ -319,15 +319,8 @@ public static CoroutineAdapterInformation create(Method declaredMethod, Method b baseClassMethod.getParameterCount()); } - KFunction declaredFunction = KotlinDetector.isKotlinType(declaredMethod.getDeclaringClass()) - ? KotlinReflectionUtils.findKotlinFunction(declaredMethod) - : null; - KFunction baseClassFunction = KotlinDetector.isKotlinType(baseClassMethod.getDeclaringClass()) - ? KotlinReflectionUtils.findKotlinFunction(baseClassMethod) - : null; - - boolean suspendedDeclaredMethod = declaredFunction != null && declaredFunction.isSuspend(); - boolean suspendedBaseClassMethod = baseClassFunction != null && baseClassFunction.isSuspend(); + boolean suspendedDeclaredMethod = KotlinReflectionUtils.isSuspend(declaredMethod); + boolean suspendedBaseClassMethod = KotlinReflectionUtils.isSuspend(baseClassMethod); boolean reactiveBaseClassMethod = !suspendedBaseClassMethod && ReactiveWrapperConverters.supports(baseClassMethod.getReturnType()); diff --git a/src/main/java/org/springframework/data/util/KotlinReflectionUtils.java b/src/main/java/org/springframework/data/util/KotlinReflectionUtils.java index ef0941d285..267e8ecde3 100644 --- a/src/main/java/org/springframework/data/util/KotlinReflectionUtils.java +++ b/src/main/java/org/springframework/data/util/KotlinReflectionUtils.java @@ -43,6 +43,7 @@ * @author Mark Paluch * @author Christoph Strobl * @author Johannes Englmeier + * @author Greg Taube * @since 2.3 * @see org.springframework.core.KotlinDetector#isKotlinReflectPresent() */ @@ -109,8 +110,11 @@ public static boolean isDataClass(Class type) { */ public static boolean isSuspend(Method method) { - KFunction invokedFunction = KotlinDetector.isKotlinType(method.getDeclaringClass()) ? findKotlinFunction(method) - : null; + if (!KotlinDetector.isSuspendingFunction(method) || !KotlinDetector.isKotlinType(method.getDeclaringClass())) { + return false; + } + + KFunction invokedFunction = findKotlinFunction(method); return invokedFunction != null && invokedFunction.isSuspend(); } diff --git a/src/test/kotlin/org/springframework/data/util/KotlinReflectionUtilsSuspendUnitTests.kt b/src/test/kotlin/org/springframework/data/util/KotlinReflectionUtilsSuspendUnitTests.kt new file mode 100644 index 0000000000..5cc8b05d2c --- /dev/null +++ b/src/test/kotlin/org/springframework/data/util/KotlinReflectionUtilsSuspendUnitTests.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2026-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.util + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.data.repository.sample.User +import kotlin.coroutines.Continuation + +/** + * Unit tests for [KotlinReflectionUtils.isSuspend]. + * + * @author Greg Taube + */ +class KotlinReflectionUtilsSuspendUnitTests { + + @Test // GH-3544 + fun `detects suspend functions`() { + + val method = SuspendingInterface::class.java.getDeclaredMethod("suspending", Continuation::class.java) + + assertThat(KotlinReflectionUtils.isSuspend(method)).isTrue() + } + + @Test // GH-3544 + fun `considers Continuation parameter of a regular function as non-suspending`() { + + val method = SuspendingInterface::class.java.getDeclaredMethod("continuationParameter", Continuation::class.java) + + assertThat(KotlinReflectionUtils.isSuspend(method)).isFalse() + } + + @Test // GH-3544 + fun `considers regular functions as non-suspending`() { + + assertThat(KotlinReflectionUtils.isSuspend(SuspendingInterface::class.java.getDeclaredMethod("regular"))).isFalse() + assertThat(KotlinReflectionUtils.isSuspend(SuspendingInterface::class.java.getDeclaredMethod("withParameter", User::class.java))).isFalse() + } + + @Test // GH-3544 + fun `considers Java methods as non-suspending`() { + + assertThat(KotlinReflectionUtils.isSuspend(Object::class.java.getDeclaredMethod("toString"))).isFalse() + } + + interface SuspendingInterface { + + suspend fun suspending(): User + + fun continuationParameter(continuation: Continuation): User + + fun regular(): User + + fun withParameter(user: User): User + } +}