Skip to content

Commit f36181b

Browse files
artembilangaryrussell
authored andcommitted
GH-3660: Invoke well-known lambdas explicitly
Fixes #3660 * Change the `LambdaMessageProcessor` to invoke `Function`, `GenericHandler`, `GenericSelector` & `GenericTransformer` explicitly without reflection. This allows to avoid `--add-opens` for Java types to be used from reflections. Plus this removes some overhead with native images. And in general it is faster than reflection invocation.
1 parent 1fbfad2 commit f36181b

File tree

2 files changed

+61
-11
lines changed

2 files changed

+61
-11
lines changed

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

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2021 the original author or authors.
2+
* Copyright 2016-2022 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.
@@ -21,6 +21,7 @@
2121
import java.lang.reflect.Modifier;
2222
import java.util.Map;
2323
import java.util.Set;
24+
import java.util.function.Function;
2425

2526
import org.apache.commons.logging.Log;
2627
import org.apache.commons.logging.LogFactory;
@@ -31,8 +32,11 @@
3132
import org.springframework.core.MethodIntrospector;
3233
import org.springframework.core.log.LogMessage;
3334
import org.springframework.integration.context.IntegrationContextUtils;
35+
import org.springframework.integration.core.GenericSelector;
36+
import org.springframework.integration.transformer.GenericTransformer;
3437
import org.springframework.lang.Nullable;
3538
import org.springframework.messaging.Message;
39+
import org.springframework.messaging.MessageHeaders;
3640
import org.springframework.messaging.converter.MessageConverter;
3741
import org.springframework.util.Assert;
3842
import org.springframework.util.ClassUtils;
@@ -85,11 +89,20 @@ public LambdaMessageProcessor(Object target, @Nullable Class<?> expectedType) {
8589
"classes with single method - functional interface implementations.");
8690

8791
this.method = methods.iterator().next();
88-
ReflectionUtils.makeAccessible(this.method);
92+
if (!isExplicit(target)) {
93+
ReflectionUtils.makeAccessible(this.method);
94+
}
8995
this.parameterTypes = this.method.getParameterTypes();
9096
this.expectedType = expectedType;
9197
}
9298

99+
private static boolean isExplicit(Object target) {
100+
return target instanceof Function<?, ?> ||
101+
target instanceof GenericHandler<?> ||
102+
target instanceof GenericSelector<?> ||
103+
target instanceof GenericTransformer<?, ?>;
104+
}
105+
93106
@Override
94107
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
95108
this.messageConverter =
@@ -102,20 +115,22 @@ public Object processMessage(Message<?> message) {
102115
Object[] args = buildArgs(message);
103116

104117
try {
105-
Object result = this.method.invoke(this.target, args);
118+
Object result = invokeMethod(args);
106119
if (result != null && org.springframework.integration.util.ClassUtils.isKotlinUnit(result.getClass())) {
107120
result = null;
108121
}
109122
return result;
110123
}
124+
catch (RuntimeException ex) {
125+
if (ex instanceof ClassCastException classCastException) {
126+
logClassCastException(classCastException);
127+
}
128+
throw ex;
129+
}
111130
catch (InvocationTargetException e) {
112131
final Throwable cause = e.getCause();
113-
if (e.getTargetException() instanceof ClassCastException) {
114-
LOGGER.error("Could not invoke the method '" + this.method + "' due to a class cast exception, " +
115-
"if using a lambda in the DSL, consider using an overloaded EIP method " +
116-
"that takes a Class<?> argument to explicitly specify the type. " +
117-
"An example of when this often occurs is if the lambda is configured to " +
118-
"receive a Message<?> argument.", cause);
132+
if (e.getTargetException() instanceof ClassCastException classCastException) {
133+
logClassCastException(classCastException);
119134
}
120135
if (cause instanceof RuntimeException) { // NOSONAR
121136
throw (RuntimeException) cause;
@@ -170,4 +185,41 @@ else if (Map.class.isAssignableFrom(parameterType)) {
170185
return args;
171186
}
172187

188+
@SuppressWarnings({ "unchecked", "rawtypes" })
189+
private Object invokeMethod(Object[] args) throws InvocationTargetException, IllegalAccessException {
190+
if (this.target instanceof Function function) {
191+
return function.apply(args[0]);
192+
}
193+
else if (this.target instanceof GenericSelector selector) {
194+
return selector.accept(args[0]);
195+
}
196+
else if (this.target instanceof GenericTransformer transformer) {
197+
return transformer.transform(args[0]);
198+
}
199+
else if (this.target instanceof GenericHandler handler) {
200+
return handler.handle(args[0], (MessageHeaders) args[1]);
201+
}
202+
else {
203+
return this.method.invoke(this.target, args);
204+
}
205+
206+
/* TODO when preview features are available in the next Java version
207+
return switch (this.target) {
208+
case Function function -> function.apply(args[0]);
209+
case GenericSelector selector -> selector.accept(args[0]);
210+
case GenericTransformer transformer -> transformer.transform(args[0]);
211+
case GenericHandler handler -> handler.handle(args[0], (MessageHeaders) args[1]);
212+
default -> this.method.invoke(this.target, args);
213+
};
214+
*/
215+
}
216+
217+
private void logClassCastException(ClassCastException classCastException) {
218+
LOGGER.error("Could not invoke the method '" + this.method + "' due to a class cast exception, " +
219+
"if using a lambda in the DSL, consider using an overloaded EIP method " +
220+
"that takes a Class<?> argument to explicitly specify the type. " +
221+
"An example of when this often occurs is if the lambda is configured to " +
222+
"receive a Message<?> argument.", classCastException);
223+
}
224+
173225
}

spring-integration-core/src/test/java/org/springframework/integration/dsl/LambdaMessageProcessorTests.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.Objects;
2424
import java.util.function.Function;
2525

26-
import org.junit.jupiter.api.Disabled;
2726
import org.junit.jupiter.api.Test;
2827

2928
import org.springframework.beans.factory.BeanFactory;
@@ -107,7 +106,6 @@ public boolean accept(Date payload) {
107106

108107

109108
@Test
110-
@Disabled("Until https://github.com/spring-projects/spring-integration/issues/3660")
111109
public void testCustomConverter() {
112110
LambdaMessageProcessor lmp = new LambdaMessageProcessor(Function.identity(), TestPojo.class);
113111
lmp.setBeanFactory(this.beanFactory);

0 commit comments

Comments
 (0)