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:
Artem Bilan
2020-07-20 13:51:54 -04:00
committed by Gary Russell
parent ca60db50a3
commit dfa6c847f8
3 changed files with 57 additions and 7 deletions

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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 {