GH-3344: Treat kotlin.Unit return as null in MMIH (#3346)
* GH-3344: Treat kotlin.Unit return as null in MMIH Fixes https://github.com/spring-projects/spring-integration/issues/3344 When function lambda doesn't return anything (e.g. a `void` method call is the last one), Kotlin produces a `kotlin.Unit` instance as a return value which is not null and produced as a reply message payload. * Fix `MessagingMethodInvokerHelper` to treat a `kotlin.Unit` as `null` for reply making Kotlin lambdas working the same way as Java lambdas when we don't return anything from from there **Cherry-pick to `5.3.x`** * * Introduce `ClassUtils.isKotlinUnit(Class)` API; use it in the `MessagingMethodInvokerHelper` instead of `.getName().equals()` * * Fix since on new `isKotlinUnit()` API
This commit is contained in:
committed by
Gary Russell
parent
ca60db50a3
commit
dfa6c847f8
@@ -576,6 +576,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator im
|
||||
localHandlerMethodFactory.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object invokeHandlerMethod(HandlerMethod handlerMethod, ParametersWrapper parameters) {
|
||||
try {
|
||||
return handlerMethod.invoke(parameters);
|
||||
@@ -1093,13 +1094,20 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator im
|
||||
this.invocableHandlerMethod = newInvocableHandlerMethod;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Object invoke(ParametersWrapper parameters) {
|
||||
Message<?> message = parameters.getMessage();
|
||||
if (this.canProcessMessageList) {
|
||||
message = new MutableMessage<>(parameters.getMessages(), parameters.getHeaders());
|
||||
}
|
||||
try {
|
||||
return this.invocableHandlerMethod.invoke(message);
|
||||
Object result = this.invocableHandlerMethod.invoke(message);
|
||||
if (result != null
|
||||
&& org.springframework.integration.util.ClassUtils.isKotlinUnit(result.getClass())) {
|
||||
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (RuntimeException ex) { // NOSONAR no way to handle conditional catch according Sonar rules
|
||||
throw ex;
|
||||
|
||||
@@ -83,6 +83,11 @@ public abstract class ClassUtils {
|
||||
*/
|
||||
public static final Class<?> KOTLIN_FUNCTION_1_CLASS;
|
||||
|
||||
/**
|
||||
* The {@code kotlin.Unit} class object.
|
||||
*/
|
||||
public static final Class<?> KOTLIN_UNIT_CLASS;
|
||||
|
||||
static {
|
||||
PRIMITIVE_WRAPPER_TYPE_MAP.put(Boolean.class, boolean.class);
|
||||
PRIMITIVE_WRAPPER_TYPE_MAP.put(Byte.class, byte.class);
|
||||
@@ -161,6 +166,18 @@ public abstract class ClassUtils {
|
||||
finally {
|
||||
KOTLIN_FUNCTION_1_CLASS = kotlinClass;
|
||||
}
|
||||
|
||||
kotlinClass = null;
|
||||
try {
|
||||
kotlinClass = org.springframework.util.ClassUtils.forName("kotlin.Unit",
|
||||
org.springframework.util.ClassUtils.getDefaultClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
//Ignore: assume no Kotlin in classpath
|
||||
}
|
||||
finally {
|
||||
KOTLIN_UNIT_CLASS = kotlinClass;
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?> findClosestMatch(Class<?> type, Set<Class<?>> candidates, boolean failOnTie) {
|
||||
@@ -247,4 +264,14 @@ public abstract class ClassUtils {
|
||||
return KOTLIN_FUNCTION_1_CLASS != null && KOTLIN_FUNCTION_1_CLASS.isAssignableFrom(aClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if class is {@code kotlin.Unit}.
|
||||
* @param aClass the {@link Class} to check.
|
||||
* @return true if class is a {@code kotlin.Unit} implementation.
|
||||
* @since 5.3.2
|
||||
*/
|
||||
public static boolean isKotlinUnit(Class<?> aClass) {
|
||||
return KOTLIN_UNIT_CLASS != null && KOTLIN_UNIT_CLASS.isAssignableFrom(aClass);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,12 +17,9 @@
|
||||
package org.springframework.integration.dsl
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isGreaterThanOrEqualTo
|
||||
import assertk.assertions.isInstanceOf
|
||||
import assertk.assertions.isNotNull
|
||||
import assertk.assertions.isTrue
|
||||
import assertk.assertions.size
|
||||
import assertk.assertions.*
|
||||
import org.apache.commons.logging.Log
|
||||
import org.apache.commons.logging.LogFactory
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.BeanFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
@@ -51,6 +48,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.test.StepVerifier
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.function.Function
|
||||
|
||||
/**
|
||||
@@ -210,6 +208,23 @@ class KotlinDslTests {
|
||||
assertThat(payload).isInstanceOf(List::class.java).size().isGreaterThanOrEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no reply from handle`() {
|
||||
val payloadReference = AtomicReference<String>()
|
||||
val integrationFlow =
|
||||
integrationFlow("handlerInputChanenl") {
|
||||
handle<String> { payload, _ -> payloadReference.set(payload) }
|
||||
}
|
||||
|
||||
val registration = this.integrationFlowContext.registration(integrationFlow).register()
|
||||
|
||||
registration.inputChannel.send(GenericMessage("test"))
|
||||
|
||||
assertThat(payloadReference.get()).isEqualTo("test")
|
||||
|
||||
registration.destroy()
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
class Config {
|
||||
|
||||
Reference in New Issue
Block a user