Skip to content

Commit dfa6c84

Browse files
artembilangaryrussell
authored andcommitted
GH-3344: Treat kotlin.Unit return as null in MMIH (#3346)
* GH-3344: Treat kotlin.Unit return as null in MMIH Fixes #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
1 parent ca60db5 commit dfa6c84

File tree

3 files changed

+57
-7
lines changed

3 files changed

+57
-7
lines changed

spring-integration-core/src/main/java/org/springframework/integration/handler/support/MessagingMethodInvokerHelper.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ private void configureLocalMessageHandlerFactory() {
576576
localHandlerMethodFactory.afterPropertiesSet();
577577
}
578578

579+
@Nullable
579580
private Object invokeHandlerMethod(HandlerMethod handlerMethod, ParametersWrapper parameters) {
580581
try {
581582
return handlerMethod.invoke(parameters);
@@ -1093,13 +1094,20 @@ void setInvocableHandlerMethod(InvocableHandlerMethod newInvocableHandlerMethod)
10931094
this.invocableHandlerMethod = newInvocableHandlerMethod;
10941095
}
10951096

1097+
@Nullable
10961098
public Object invoke(ParametersWrapper parameters) {
10971099
Message<?> message = parameters.getMessage();
10981100
if (this.canProcessMessageList) {
10991101
message = new MutableMessage<>(parameters.getMessages(), parameters.getHeaders());
11001102
}
11011103
try {
1102-
return this.invocableHandlerMethod.invoke(message);
1104+
Object result = this.invocableHandlerMethod.invoke(message);
1105+
if (result != null
1106+
&& org.springframework.integration.util.ClassUtils.isKotlinUnit(result.getClass())) {
1107+
1108+
result = null;
1109+
}
1110+
return result;
11031111
}
11041112
catch (RuntimeException ex) { // NOSONAR no way to handle conditional catch according Sonar rules
11051113
throw ex;

spring-integration-core/src/main/java/org/springframework/integration/util/ClassUtils.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ public abstract class ClassUtils {
8383
*/
8484
public static final Class<?> KOTLIN_FUNCTION_1_CLASS;
8585

86+
/**
87+
* The {@code kotlin.Unit} class object.
88+
*/
89+
public static final Class<?> KOTLIN_UNIT_CLASS;
90+
8691
static {
8792
PRIMITIVE_WRAPPER_TYPE_MAP.put(Boolean.class, boolean.class);
8893
PRIMITIVE_WRAPPER_TYPE_MAP.put(Byte.class, byte.class);
@@ -161,6 +166,18 @@ public abstract class ClassUtils {
161166
finally {
162167
KOTLIN_FUNCTION_1_CLASS = kotlinClass;
163168
}
169+
170+
kotlinClass = null;
171+
try {
172+
kotlinClass = org.springframework.util.ClassUtils.forName("kotlin.Unit",
173+
org.springframework.util.ClassUtils.getDefaultClassLoader());
174+
}
175+
catch (ClassNotFoundException e) {
176+
//Ignore: assume no Kotlin in classpath
177+
}
178+
finally {
179+
KOTLIN_UNIT_CLASS = kotlinClass;
180+
}
164181
}
165182

166183
public static Class<?> findClosestMatch(Class<?> type, Set<Class<?>> candidates, boolean failOnTie) {
@@ -247,4 +264,14 @@ public static boolean isKotlinFaction1(Class<?> aClass) {
247264
return KOTLIN_FUNCTION_1_CLASS != null && KOTLIN_FUNCTION_1_CLASS.isAssignableFrom(aClass);
248265
}
249266

267+
/**
268+
* Check if class is {@code kotlin.Unit}.
269+
* @param aClass the {@link Class} to check.
270+
* @return true if class is a {@code kotlin.Unit} implementation.
271+
* @since 5.3.2
272+
*/
273+
public static boolean isKotlinUnit(Class<?> aClass) {
274+
return KOTLIN_UNIT_CLASS != null && KOTLIN_UNIT_CLASS.isAssignableFrom(aClass);
275+
}
276+
250277
}

spring-integration-core/src/test/kotlin/org/springframework/integration/dsl/KotlinDslTests.kt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@
1717
package org.springframework.integration.dsl
1818

1919
import assertk.assertThat
20-
import assertk.assertions.isEqualTo
21-
import assertk.assertions.isGreaterThanOrEqualTo
22-
import assertk.assertions.isInstanceOf
23-
import assertk.assertions.isNotNull
24-
import assertk.assertions.isTrue
25-
import assertk.assertions.size
20+
import assertk.assertions.*
21+
import org.apache.commons.logging.Log
22+
import org.apache.commons.logging.LogFactory
2623
import org.junit.jupiter.api.Test
2724
import org.springframework.beans.factory.BeanFactory
2825
import org.springframework.beans.factory.annotation.Autowired
@@ -51,6 +48,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
5148
import reactor.core.publisher.Flux
5249
import reactor.test.StepVerifier
5350
import java.util.*
51+
import java.util.concurrent.atomic.AtomicReference
5452
import java.util.function.Function
5553

5654
/**
@@ -210,6 +208,23 @@ class KotlinDslTests {
210208
assertThat(payload).isInstanceOf(List::class.java).size().isGreaterThanOrEqualTo(1)
211209
}
212210

211+
@Test
212+
fun `no reply from handle`() {
213+
val payloadReference = AtomicReference<String>()
214+
val integrationFlow =
215+
integrationFlow("handlerInputChanenl") {
216+
handle<String> { payload, _ -> payloadReference.set(payload) }
217+
}
218+
219+
val registration = this.integrationFlowContext.registration(integrationFlow).register()
220+
221+
registration.inputChannel.send(GenericMessage("test"))
222+
223+
assertThat(payloadReference.get()).isEqualTo("test")
224+
225+
registration.destroy()
226+
}
227+
213228
@Configuration
214229
@EnableIntegration
215230
class Config {

0 commit comments

Comments
 (0)