Skip to content

Commit 61cec46

Browse files
artembilangaryrussell
authored andcommitted
INT-4456: Explicit methods for Kotlin lambdas
JIRA: https://jira.spring.io/browse/INT-4456 Since Kotlin compiles lambdas different way than Java, they are not synthetic classes at runtime anymore. Therefore we fallback to the method invocation logic, but since Java 8 interfaces has `default` methods as well the `MessagingMethodInvokerHelper` can't select the target method for invocation * Use explicit `Function.apply()` for non-synthetic implementations * Add `RouterDslTests` Kotlin-based test-case **Cherry-pick to 5.0.x** * Add more explicit methods for invokers
1 parent b0be46a commit 61cec46

File tree

5 files changed

+172
-30
lines changed

5 files changed

+172
-30
lines changed

build.gradle

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
buildscript {
2+
ext.kotlinVersion = '1.2.40'
23
repositories {
34
maven { url 'https://repo.spring.io/plugins-release' }
45
}
56
dependencies {
67
classpath 'io.spring.gradle:dependency-management-plugin:1.0.5.RELEASE'
78
classpath 'io.spring.gradle:spring-io-plugin:0.0.8.RELEASE'
89
classpath 'io.spring.gradle:docbook-reference-plugin:0.3.1'
9-
classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.0'
10+
classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.3'
11+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
1012
}
1113
}
1214

@@ -60,6 +62,7 @@ subprojects { subproject ->
6062
apply plugin: 'idea'
6163
apply plugin: 'jacoco'
6264
apply plugin: 'checkstyle'
65+
apply plugin: 'kotlin'
6366

6467
sourceSets {
6568
test {
@@ -86,11 +89,18 @@ subprojects { subproject ->
8689
targetCompatibility = 1.8
8790
}
8891

92+
compileTestKotlin {
93+
kotlinOptions {
94+
jvmTarget = '1.8'
95+
}
96+
}
97+
8998
ext {
9099
activeMqVersion = '5.15.2'
91100
apacheSshdVersion = '1.6.0'
92101
aspectjVersion = '1.8.13'
93102
assertjVersion = '3.9.1'
103+
assertkVersion = '0.10'
94104
boonVersion = '0.34'
95105
commonsDbcp2Version = '2.1.1'
96106
commonsIoVersion = '2.4'
@@ -176,6 +186,14 @@ subprojects { subproject ->
176186

177187
testRuntime "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion"
178188
testRuntime "org.apache.logging.log4j:log4j-jcl:$log4jVersion"
189+
190+
testCompile("com.willowtreeapps.assertk:assertk:$assertkVersion") {
191+
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-reflect'
192+
}
193+
194+
testCompile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
195+
testRuntime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
196+
179197
}
180198

181199
// enable all compiler warnings; individual projects may customize further

spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
import org.springframework.integration.transformer.MessageTransformingHandler;
8989
import org.springframework.integration.transformer.MethodInvokingTransformer;
9090
import org.springframework.integration.transformer.Transformer;
91+
import org.springframework.integration.util.ClassUtils;
9192
import org.springframework.messaging.Message;
9293
import org.springframework.messaging.MessageChannel;
9394
import org.springframework.messaging.MessageHandler;
@@ -635,7 +636,7 @@ public <P, T> B transform(Class<P> payloadType, GenericTransformer<P, T> generic
635636
Transformer transformer = genericTransformer instanceof Transformer ? (Transformer) genericTransformer :
636637
(isLambda(genericTransformer)
637638
? new MethodInvokingTransformer(new LambdaMessageProcessor(genericTransformer, payloadType))
638-
: new MethodInvokingTransformer(genericTransformer));
639+
: new MethodInvokingTransformer(genericTransformer, ClassUtils.TRANSFORMER_TRANSFORM_METHOD));
639640
return addComponent(transformer)
640641
.handle(new MessageTransformingHandler(transformer), endpointConfigurer);
641642
}
@@ -828,7 +829,7 @@ public <P> B filter(Class<P> payloadType, GenericSelector<P> genericSelector,
828829
MessageSelector selector = genericSelector instanceof MessageSelector ? (MessageSelector) genericSelector :
829830
(isLambda(genericSelector)
830831
? new MethodInvokingSelector(new LambdaMessageProcessor(genericSelector, payloadType))
831-
: new MethodInvokingSelector(genericSelector));
832+
: new MethodInvokingSelector(genericSelector, ClassUtils.SELECTOR_ACCEPT_METHOD));
832833
return this.register(new FilterEndpointSpec(new MessageFilter(selector)), endpointConfigurer);
833834
}
834835

@@ -1024,12 +1025,12 @@ public <P> B handle(Class<P> payloadType, GenericHandler<P> handler) {
10241025
*/
10251026
public <P> B handle(Class<P> payloadType, GenericHandler<P> handler,
10261027
Consumer<GenericEndpointSpec<ServiceActivatingHandler>> endpointConfigurer) {
1027-
ServiceActivatingHandler serviceActivatingHandler = null;
1028+
ServiceActivatingHandler serviceActivatingHandler;
10281029
if (isLambda(handler)) {
10291030
serviceActivatingHandler = new ServiceActivatingHandler(new LambdaMessageProcessor(handler, payloadType));
10301031
}
10311032
else {
1032-
serviceActivatingHandler = new ServiceActivatingHandler(handler, "handle");
1033+
serviceActivatingHandler = new ServiceActivatingHandler(handler, ClassUtils.HANDLER_HANDLE_METHOD);
10331034
}
10341035
return this.handle(serviceActivatingHandler, endpointConfigurer);
10351036
}
@@ -1535,7 +1536,7 @@ public <P> B split(Class<P> payloadType, Function<P, ?> splitter,
15351536
Consumer<SplitterEndpointSpec<MethodInvokingSplitter>> endpointConfigurer) {
15361537
MethodInvokingSplitter split = isLambda(splitter)
15371538
? new MethodInvokingSplitter(new LambdaMessageProcessor(splitter, payloadType))
1538-
: new MethodInvokingSplitter(splitter);
1539+
: new MethodInvokingSplitter(splitter, ClassUtils.FUNCTION_APPLY_METHOD);
15391540
return this.split(split, endpointConfigurer);
15401541
}
15411542

@@ -1935,7 +1936,7 @@ public <P, T> B route(Class<P> payloadType, Function<P, T> router,
19351936
Consumer<RouterSpec<T, MethodInvokingRouter>> routerConfigurer) {
19361937
MethodInvokingRouter methodInvokingRouter = isLambda(router)
19371938
? new MethodInvokingRouter(new LambdaMessageProcessor(router, payloadType))
1938-
: new MethodInvokingRouter(router);
1939+
: new MethodInvokingRouter(router, ClassUtils.FUNCTION_APPLY_METHOD);
19391940
return route(new RouterSpec<>(methodInvokingRouter), routerConfigurer);
19401941
}
19411942

spring-integration-core/src/main/java/org/springframework/integration/dsl/RecipientListRouterSpec.java

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
import org.springframework.integration.core.MessageSelector;
2222
import org.springframework.integration.filter.ExpressionEvaluatingSelector;
2323
import org.springframework.integration.filter.MethodInvokingSelector;
24-
import org.springframework.integration.handler.LambdaMessageProcessor;
2524
import org.springframework.integration.router.RecipientListRouter;
25+
import org.springframework.integration.util.ClassUtils;
2626
import org.springframework.messaging.MessageChannel;
2727
import org.springframework.util.StringUtils;
2828

@@ -96,23 +96,20 @@ public RecipientListRouterSpec recipientMessageSelector(String channelName, Mess
9696
* @return the router spec.
9797
*/
9898
public <P> RecipientListRouterSpec recipient(String channelName, GenericSelector<P> selector) {
99+
MessageSelector messageSelector = wrapToMessageSelectorIfNecessary(selector);
100+
this.handler.addRecipient(channelName, messageSelector);
101+
return _this();
102+
}
103+
104+
private <P> MessageSelector wrapToMessageSelectorIfNecessary(GenericSelector<P> selector) {
99105
MessageSelector messageSelector;
100106
if (selector instanceof MessageSelector) {
101107
messageSelector = (MessageSelector) selector;
102108
}
103109
else {
104-
messageSelector =
105-
isLambda(selector)
106-
? new MethodInvokingSelector(new LambdaMessageProcessor(selector, null))
107-
: new MethodInvokingSelector(selector);
110+
messageSelector = new MethodInvokingSelector(selector, ClassUtils.SELECTOR_ACCEPT_METHOD);
108111
}
109-
this.handler.addRecipient(channelName, messageSelector);
110-
return _this();
111-
}
112-
113-
private static boolean isLambda(Object o) {
114-
Class<?> aClass = o.getClass();
115-
return aClass.isSynthetic() && !aClass.isAnonymousClass() && !aClass.isLocalClass();
112+
return messageSelector;
116113
}
117114

118115
/**
@@ -170,16 +167,7 @@ public RecipientListRouterSpec recipientMessageSelector(MessageChannel channel,
170167
* @return the router spec.
171168
*/
172169
public <P> RecipientListRouterSpec recipient(MessageChannel channel, GenericSelector<P> selector) {
173-
MessageSelector messageSelector;
174-
if (selector instanceof MessageSelector) {
175-
messageSelector = (MessageSelector) selector;
176-
}
177-
else {
178-
messageSelector =
179-
isLambda(selector)
180-
? new MethodInvokingSelector(new LambdaMessageProcessor(selector, null))
181-
: new MethodInvokingSelector(selector);
182-
}
170+
MessageSelector messageSelector = wrapToMessageSelectorIfNecessary(selector);
183171
this.handler.addRecipient(channel, messageSelector);
184172
return _this();
185173
}

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

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,16 +16,63 @@
1616

1717
package org.springframework.integration.util;
1818

19+
import java.lang.reflect.Method;
1920
import java.util.HashMap;
2021
import java.util.Map;
2122
import java.util.Set;
23+
import java.util.function.Function;
24+
25+
import org.springframework.integration.core.GenericSelector;
26+
import org.springframework.integration.transformer.GenericTransformer;
27+
import org.springframework.util.ReflectionUtils;
2228

2329
/**
2430
* @author Mark Fisher
31+
* @author Artem Bilan
32+
*
2533
* @since 2.0
2634
*/
2735
public abstract class ClassUtils {
2836

37+
/**
38+
* The {@link Function#apply(Object)} method object.
39+
*/
40+
public static final Method FUNCTION_APPLY_METHOD =
41+
ReflectionUtils.findMethod(Function.class, "apply", (Class<?>[]) null);
42+
43+
/**
44+
* The {@link GenericSelector#accept(Object)} method object.
45+
*/
46+
public static final Method SELECTOR_ACCEPT_METHOD =
47+
ReflectionUtils.findMethod(GenericSelector.class, "accept", (Class<?>[]) null);
48+
49+
/**
50+
* The {@link GenericSelector#accept(Object)} method object.
51+
*/
52+
public static final Method TRANSFORMER_TRANSFORM_METHOD =
53+
ReflectionUtils.findMethod(GenericTransformer.class, "transform", (Class<?>[]) null);
54+
55+
/**
56+
* The {@code org.springframework.integration.handler.GenericHandler#handle(Object, Map)} method object.
57+
*/
58+
public static final Method HANDLER_HANDLE_METHOD;
59+
60+
static {
61+
Class<?> genericHandlerClass = null;
62+
try {
63+
genericHandlerClass =
64+
org.springframework.util.ClassUtils.forName(
65+
"org.springframework.integration.handler.GenericHandler",
66+
org.springframework.util.ClassUtils.getDefaultClassLoader());
67+
}
68+
catch (ClassNotFoundException e) {
69+
ReflectionUtils.rethrowRuntimeException(e);
70+
}
71+
72+
HANDLER_HANDLE_METHOD = ReflectionUtils.findMethod(genericHandlerClass, "handle", (Class<?>[]) null);
73+
}
74+
75+
2976
/**
3077
* Map with primitive wrapper type as key and corresponding primitive
3178
* type as value, for example: Integer.class -> int.class.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.dsl.routers
18+
19+
import assertk.assert
20+
import assertk.assertions.isEqualTo
21+
import assertk.assertions.isInstanceOf
22+
import assertk.assertions.isNotNull
23+
import org.junit.jupiter.api.Test
24+
import org.springframework.beans.factory.annotation.Autowired
25+
import org.springframework.beans.factory.annotation.Qualifier
26+
import org.springframework.context.annotation.Bean
27+
import org.springframework.context.annotation.Configuration
28+
import org.springframework.integration.config.EnableIntegration
29+
import org.springframework.integration.dsl.IntegrationFlow
30+
import org.springframework.messaging.MessageChannel
31+
import org.springframework.messaging.PollableChannel
32+
import org.springframework.messaging.support.GenericMessage
33+
import org.springframework.test.annotation.DirtiesContext
34+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
35+
36+
/**
37+
* @author Artem Bilan
38+
*
39+
* @since 5.0.5
40+
*/
41+
@SpringJUnitConfig
42+
@DirtiesContext
43+
class RouterDslTests {
44+
45+
@Autowired
46+
@Qualifier("routerTwoSubFlows.input")
47+
private lateinit var routerTwoSubFlowsInput: MessageChannel
48+
49+
@Autowired
50+
@Qualifier("routerTwoSubFlowsOutput")
51+
private lateinit var routerTwoSubFlowsOutput: PollableChannel
52+
53+
@Test
54+
fun `route to two subflows using lambda`() {
55+
56+
this.routerTwoSubFlowsInput.send(GenericMessage<Any>(listOf(1, 2, 3, 4, 5, 6)))
57+
val receive = this.routerTwoSubFlowsOutput.receive(10000)
58+
59+
val payload = receive?.payload
60+
61+
assert(payload).isNotNull {
62+
it.isInstanceOf(List::class.java)
63+
}
64+
65+
assert(payload).isEqualTo(listOf(3, 4, 9, 8, 15, 12))
66+
}
67+
68+
69+
@Configuration
70+
@EnableIntegration
71+
open class Config {
72+
73+
@Bean
74+
open fun routerTwoSubFlows() =
75+
IntegrationFlow { f ->
76+
f.split()
77+
.route<Int, Boolean>({ p -> p % 2 == 0 },
78+
{ m ->
79+
m.subFlowMapping(true, { sf -> sf.handle<Int> { p, _ -> p * 2 } })
80+
.subFlowMapping(false, { sf -> sf.handle<Int> { p, _ -> p * 3 } })
81+
})
82+
.aggregate()
83+
.channel { c -> c.queue("routerTwoSubFlowsOutput") }
84+
}
85+
86+
}
87+
88+
}

0 commit comments

Comments
 (0)