Skip to content

Commit 572dc0e

Browse files
artembilangaryrussell
authored andcommitted
Short-circuit methods for lambdas from annotation (#2823)
* Short-circuit methods for lambdas from annotation When we have a `@ServiceActivator` or any other messaging annotations on `Function` or `Consumer` `@Bean`s, there is a restriction when we can't use lambdas because of target method argument type erasure in Java. * Use a `@Bean` method return to determine the target function argument type and wrap the call into the `LambdaMessageProcessor`. In this case we call the target method directly after possible payload conversion according expected generic type for `Function` or `Consumer`. There is just no reason to go a `MessagingMethodInvokerHelper` route for this lambda variants * Apply the short-circuit algorithm for Kotlin lambdas as well * Make some refactoring and improvements to `ClassUtils` if favor or similar API in the SF `ClassUtils` * Fix `No beanFactory` warning for the `ExpressionCommandMessageProcessor` * * Resolve Checkstyle violations * * Fix `resolveAttributeToBoolean()` argument name to be generic * Fix typo in the exception message for `IntegrationFlowDefinition.get()` * * Revert `ClassUtils.resolvePrimitiveType()` logic: an existing in SF does exactly opposite one
1 parent a599881 commit 572dc0e

14 files changed

+270
-169
lines changed

spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.Collection;
2323
import java.util.Collections;
2424
import java.util.List;
25+
import java.util.function.Consumer;
26+
import java.util.function.Function;
2527

2628
import org.aopalliance.aop.Advice;
2729
import org.apache.commons.logging.Log;
@@ -40,6 +42,7 @@
4042
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
4143
import org.springframework.context.annotation.Bean;
4244
import org.springframework.core.GenericTypeResolver;
45+
import org.springframework.core.ResolvableType;
4346
import org.springframework.core.annotation.AnnotatedElementUtils;
4447
import org.springframework.core.annotation.AnnotationUtils;
4548
import org.springframework.core.annotation.Order;
@@ -61,10 +64,13 @@
6164
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
6265
import org.springframework.integration.handler.AbstractMessageProducingHandler;
6366
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
67+
import org.springframework.integration.handler.LambdaMessageProcessor;
68+
import org.springframework.integration.handler.MessageProcessor;
6469
import org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper;
6570
import org.springframework.integration.handler.advice.HandleMessageAdvice;
6671
import org.springframework.integration.router.AbstractMessageRouter;
6772
import org.springframework.integration.scheduling.PollerMetadata;
73+
import org.springframework.integration.util.ClassUtils;
6874
import org.springframework.integration.util.MessagingAnnotationUtils;
6975
import org.springframework.lang.Nullable;
7076
import org.springframework.messaging.MessageChannel;
@@ -77,7 +83,6 @@
7783
import org.springframework.scheduling.support.CronTrigger;
7884
import org.springframework.scheduling.support.PeriodicTrigger;
7985
import org.springframework.util.Assert;
80-
import org.springframework.util.ClassUtils;
8186
import org.springframework.util.CollectionUtils;
8287
import org.springframework.util.ObjectUtils;
8388
import org.springframework.util.StringUtils;
@@ -440,7 +445,7 @@ protected String generateHandlerBeanName(String originalBeanName, Method method)
440445
String name = MessagingAnnotationUtils.endpointIdValue(method);
441446
if (!StringUtils.hasText(name)) {
442447
String baseName = originalBeanName + "." + method.getName() + "."
443-
+ ClassUtils.getShortNameAsProperty(this.annotationType);
448+
+ org.springframework.util.ClassUtils.getShortNameAsProperty(this.annotationType);
444449
name = baseName;
445450
int count = 1;
446451
while (this.beanFactory.containsBean(name)) {
@@ -510,9 +515,26 @@ protected void checkMessageHandlerAttributes(String handlerBeanName, List<Annota
510515
}
511516
}
512517

518+
protected boolean resolveAttributeToBoolean(String attribute) {
519+
return Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(attribute));
520+
}
521+
522+
@Nullable
523+
protected MessageProcessor<?> buildLambdaMessageProcessorForBeanMethod(Method method, Object target) {
524+
if ((target instanceof Function || target instanceof Consumer) && ClassUtils.isLambda(target.getClass())
525+
|| ClassUtils.isKotlinFaction1(target.getClass())) {
526+
527+
ResolvableType methodReturnType = ResolvableType.forMethodReturnType(method);
528+
Class<?> expectedPayloadType = methodReturnType.getGeneric(0).toClass();
529+
return new LambdaMessageProcessor(target, expectedPayloadType);
530+
}
531+
else {
532+
return null;
533+
}
534+
}
535+
513536
/**
514537
* Subclasses must implement this method to create the MessageHandler.
515-
*
516538
* @param bean The bean.
517539
* @param method The method.
518540
* @param annotations The messaging annotation (or meta-annotation hierarchy) on the method.

spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AggregatorAnnotationPostProcessor.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ public AggregatorAnnotationPostProcessor(ConfigurableListableBeanFactory beanFac
5151
@Override
5252
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
5353
MethodInvokingMessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(bean, method);
54-
processor.setBeanFactory(this.beanFactory);
5554

5655
MethodInvokingReleaseStrategy releaseStrategy = null;
5756
Method releaseStrategyMethod = MessagingAnnotationUtils.findAnnotatedMethod(bean, ReleaseStrategy.class);
@@ -60,29 +59,30 @@ protected MessageHandler createHandler(Object bean, Method method, List<Annotati
6059
}
6160

6261
MethodInvokingCorrelationStrategy correlationStrategy = null;
63-
Method correlationStrategyMethod = MessagingAnnotationUtils.findAnnotatedMethod(bean, CorrelationStrategy.class);
62+
Method correlationStrategyMethod =
63+
MessagingAnnotationUtils.findAnnotatedMethod(bean, CorrelationStrategy.class);
6464
if (correlationStrategyMethod != null) {
6565
correlationStrategy = new MethodInvokingCorrelationStrategy(bean, correlationStrategyMethod);
6666
}
6767

6868
AggregatingMessageHandler handler = new AggregatingMessageHandler(processor, new SimpleMessageStore(),
6969
correlationStrategy, releaseStrategy);
7070

71-
String discardChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "discardChannel", String.class);
71+
String discardChannelName =
72+
MessagingAnnotationUtils.resolveAttribute(annotations, "discardChannel", String.class);
7273
if (StringUtils.hasText(discardChannelName)) {
7374
handler.setDiscardChannelName(discardChannelName);
7475
}
75-
String outputChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "outputChannel", String.class);
76+
String outputChannelName =
77+
MessagingAnnotationUtils.resolveAttribute(annotations, "outputChannel", String.class);
7678
if (StringUtils.hasText(outputChannelName)) {
7779
handler.setOutputChannelName(outputChannelName);
7880
}
7981
String sendPartialResultsOnExpiry = MessagingAnnotationUtils.resolveAttribute(annotations,
8082
"sendPartialResultsOnExpiry", String.class);
8183
if (sendPartialResultsOnExpiry != null) {
82-
handler.setSendPartialResultOnExpiry(
83-
Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(sendPartialResultsOnExpiry)));
84+
handler.setSendPartialResultOnExpiry(resolveAttributeToBoolean(sendPartialResultsOnExpiry));
8485
}
85-
handler.setBeanFactory(this.beanFactory);
8686
return handler;
8787
}
8888

spring-integration-core/src/main/java/org/springframework/integration/config/annotation/FilterAnnotationPostProcessor.java

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostP
4545

4646
public FilterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
4747
super(beanFactory);
48-
this.messageHandlerAttributes.addAll(Arrays.<String>asList("discardChannel", "throwExceptionOnRejection",
48+
this.messageHandlerAttributes.addAll(Arrays.asList("discardChannel", "throwExceptionOnRejection",
4949
"adviceChain", "discardWithinAdvice"));
5050
}
5151

@@ -54,11 +54,11 @@ public FilterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory
5454
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
5555
MessageSelector selector;
5656
if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) {
57-
Object target = this.resolveTargetBeanFromMethodWithBeanAnnotation(method);
57+
Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method);
5858
if (target instanceof MessageSelector) {
5959
selector = (MessageSelector) target;
6060
}
61-
else if (this.extractTypeIfPossible(target, MessageFilter.class) != null) {
61+
else if (extractTypeIfPossible(target, MessageFilter.class) != null) {
6262
checkMessageHandlerAttributes(resolveTargetBeanName(method), annotations);
6363
return (MessageHandler) target;
6464
}
@@ -74,31 +74,25 @@ else if (this.extractTypeIfPossible(target, MessageFilter.class) != null) {
7474

7575
MessageFilter filter = new MessageFilter(selector);
7676

77-
String discardWithinAdvice = MessagingAnnotationUtils.resolveAttribute(annotations, "discardWithinAdvice",
78-
String.class);
77+
String discardWithinAdvice =
78+
MessagingAnnotationUtils.resolveAttribute(annotations, "discardWithinAdvice", String.class);
7979
if (StringUtils.hasText(discardWithinAdvice)) {
80-
discardWithinAdvice = this.beanFactory.resolveEmbeddedValue(discardWithinAdvice);
81-
if (StringUtils.hasText(discardWithinAdvice)) {
82-
filter.setDiscardWithinAdvice(Boolean.parseBoolean(discardWithinAdvice));
83-
}
80+
filter.setDiscardWithinAdvice(resolveAttributeToBoolean(discardWithinAdvice));
8481
}
8582

86-
87-
String throwExceptionOnRejection = MessagingAnnotationUtils.resolveAttribute(annotations,
88-
"throwExceptionOnRejection", String.class);
83+
String throwExceptionOnRejection =
84+
MessagingAnnotationUtils.resolveAttribute(annotations, "throwExceptionOnRejection", String.class);
8985
if (StringUtils.hasText(throwExceptionOnRejection)) {
90-
String throwExceptionOnRejectionValue = this.beanFactory.resolveEmbeddedValue(throwExceptionOnRejection);
91-
if (StringUtils.hasText(throwExceptionOnRejectionValue)) {
92-
filter.setThrowExceptionOnRejection(Boolean.parseBoolean(throwExceptionOnRejectionValue));
93-
}
86+
filter.setThrowExceptionOnRejection(resolveAttributeToBoolean(throwExceptionOnRejection));
9487
}
9588

96-
String discardChannelName = MessagingAnnotationUtils.resolveAttribute(annotations, "discardChannel", String.class);
89+
String discardChannelName =
90+
MessagingAnnotationUtils.resolveAttribute(annotations, "discardChannel", String.class);
9791
if (StringUtils.hasText(discardChannelName)) {
9892
filter.setDiscardChannelName(discardChannelName);
9993
}
10094

101-
this.setOutputChannelIfPresent(annotations, filter);
95+
setOutputChannelIfPresent(annotations, filter);
10296
return filter;
10397
}
10498

spring-integration-core/src/main/java/org/springframework/integration/config/annotation/InboundChannelAdapterAnnotationPostProcessor.java

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,10 @@
3131
import org.springframework.integration.core.MessageSource;
3232
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
3333
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
34+
import org.springframework.integration.util.ClassUtils;
3435
import org.springframework.integration.util.MessagingAnnotationUtils;
3536
import org.springframework.messaging.MessageHandler;
3637
import org.springframework.util.Assert;
37-
import org.springframework.util.ClassUtils;
38-
import org.springframework.util.ReflectionUtils;
3938

4039
/**
4140
* Post-processor for Methods annotated with {@link InboundChannelAdapter @InboundChannelAdapter}.
@@ -49,20 +48,6 @@
4948
public class InboundChannelAdapterAnnotationPostProcessor extends
5049
AbstractMethodAnnotationPostProcessor<InboundChannelAdapter> {
5150

52-
private static final Class<?> kotlinFunction0Class;
53-
54-
static {
55-
Class<?> kotlinClass = null;
56-
try {
57-
kotlinClass = ClassUtils.forName("kotlin.jvm.functions.Function0", ClassUtils.getDefaultClassLoader());
58-
}
59-
catch (ClassNotFoundException e) {
60-
//Ignore: assume no Kotlin in classpath
61-
}
62-
finally {
63-
kotlinFunction0Class = kotlinClass;
64-
}
65-
}
6651

6752
public InboundChannelAdapterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
6853
super(beanFactory);
@@ -75,8 +60,8 @@ protected String getInputChannelAttribute() {
7560

7661
@Override
7762
public Object postProcess(Object bean, String beanName, Method method, List<Annotation> annotations) {
78-
String channelName = MessagingAnnotationUtils
79-
.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class);
63+
String channelName =
64+
MessagingAnnotationUtils.resolveAttribute(annotations, AnnotationUtils.VALUE, String.class);
8065
Assert.hasText(channelName, "The channel ('value' attribute of @InboundChannelAdapter) can't be empty.");
8166

8267
MessageSource<?> messageSource = null;
@@ -105,31 +90,33 @@ private MessageSource<?> createMessageSource(Object beanArg, String beanName, Me
10590
Object bean = beanArg;
10691
Method method = methodArg;
10792
if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) {
108-
Object target = this.resolveTargetBeanFromMethodWithBeanAnnotation(method);
93+
Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method);
10994
Class<?> targetClass = target.getClass();
11095
Assert.isTrue(MessageSource.class.isAssignableFrom(targetClass) ||
11196
Supplier.class.isAssignableFrom(targetClass) ||
112-
(kotlinFunction0Class == null || kotlinFunction0Class.isAssignableFrom(targetClass)),
113-
"The '" + this.annotationType + "' on @Bean method " + "level is allowed only for: "
114-
+ MessageSource.class.getName() + " or " + Supplier.class.getName()
115-
+ (kotlinFunction0Class != null ? " or " + kotlinFunction0Class.getName() : "") + " beans");
97+
ClassUtils.isKotlinFaction0(targetClass),
98+
() -> "The '" + this.annotationType + "' on @Bean method " + "level is allowed only for: " +
99+
MessageSource.class.getName() + " or " + Supplier.class.getName() +
100+
(ClassUtils.KOTLIN_FUNCTION_0_CLASS != null
101+
? " or " + ClassUtils.KOTLIN_FUNCTION_0_CLASS.getName()
102+
: "") + " beans");
116103
if (target instanceof MessageSource<?>) {
117104
messageSource = (MessageSource<?>) target;
118105
}
119106
else if (target instanceof Supplier<?>) {
120-
method = ReflectionUtils.findMethod(Supplier.class, "get");
107+
method = ClassUtils.SUPPLIER_GET_METHOD;
121108
bean = target;
122109
}
123-
else if (kotlinFunction0Class != null) {
124-
method = ReflectionUtils.findMethod(kotlinFunction0Class, "invoke");
110+
else if (ClassUtils.KOTLIN_FUNCTION_0_INVOKE_METHOD != null) {
111+
method = ClassUtils.KOTLIN_FUNCTION_0_INVOKE_METHOD;
125112
bean = target;
126113
}
127114
}
128115
if (messageSource == null) {
129116
MethodInvokingMessageSource methodInvokingMessageSource = new MethodInvokingMessageSource();
130117
methodInvokingMessageSource.setObject(bean);
131118
methodInvokingMessageSource.setMethod(method);
132-
String messageSourceBeanName = this.generateHandlerBeanName(beanName, method);
119+
String messageSourceBeanName = generateHandlerBeanName(beanName, method);
133120
this.beanFactory.registerSingleton(messageSourceBeanName, methodInvokingMessageSource);
134121
messageSource = (MessageSource<?>) this.beanFactory
135122
.initializeBean(methodInvokingMessageSource, messageSourceBeanName);

spring-integration-core/src/main/java/org/springframework/integration/config/annotation/RouterAnnotationPostProcessor.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,21 @@ public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostP
4646

4747
public RouterAnnotationPostProcessor(ConfigurableListableBeanFactory beanFactory) {
4848
super(beanFactory);
49-
this.messageHandlerAttributes.addAll(Arrays.<String>asList("defaultOutputChannel", "applySequence",
49+
this.messageHandlerAttributes.addAll(Arrays.asList("defaultOutputChannel", "applySequence",
5050
"ignoreSendFailures", "resolutionRequired", "channelMappings", "prefix", "suffix"));
5151
}
5252

5353
@Override
5454
protected MessageHandler createHandler(Object bean, Method method, List<Annotation> annotations) {
5555
AbstractMessageRouter router;
5656
if (AnnotatedElementUtils.isAnnotated(method, Bean.class.getName())) {
57-
Object target = this.resolveTargetBeanFromMethodWithBeanAnnotation(method);
58-
router = this.extractTypeIfPossible(target, AbstractMessageRouter.class);
57+
Object target = resolveTargetBeanFromMethodWithBeanAnnotation(method);
58+
router = extractTypeIfPossible(target, AbstractMessageRouter.class);
5959
if (router == null) {
6060
if (target instanceof MessageHandler) {
61-
Assert.isTrue(this.routerAttributesProvided(annotations), "'defaultOutputChannel', 'applySequence', " +
62-
"'ignoreSendFailures', 'resolutionRequired', 'channelMappings', 'prefix' and 'suffix' " +
61+
Assert.isTrue(routerAttributesProvided(annotations),
62+
"'defaultOutputChannel', 'applySequence', 'ignoreSendFailures', 'resolutionRequired', " +
63+
"'channelMappings', 'prefix' and 'suffix' " +
6364
"can be applied to 'AbstractMessageRouter' implementations, but target handler is: " +
6465
target.getClass());
6566
return (MessageHandler) target;
@@ -84,26 +85,23 @@ protected MessageHandler createHandler(Object bean, Method method, List<Annotati
8485

8586
String applySequence = MessagingAnnotationUtils.resolveAttribute(annotations, "applySequence", String.class);
8687
if (StringUtils.hasText(applySequence)) {
87-
router.setApplySequence(Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(applySequence)));
88+
router.setApplySequence(resolveAttributeToBoolean(applySequence));
8889
}
8990

9091
String ignoreSendFailures = MessagingAnnotationUtils.resolveAttribute(annotations, "ignoreSendFailures",
9192
String.class);
9293
if (StringUtils.hasText(ignoreSendFailures)) {
93-
router.setIgnoreSendFailures(Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(ignoreSendFailures)));
94+
router.setIgnoreSendFailures(resolveAttributeToBoolean(ignoreSendFailures));
9495
}
9596

96-
if (this.routerAttributesProvided(annotations)) {
97+
if (routerAttributesProvided(annotations)) {
9798

9899
MethodInvokingRouter methodInvokingRouter = (MethodInvokingRouter) router;
99100

100101
String resolutionRequired = MessagingAnnotationUtils.resolveAttribute(annotations, "resolutionRequired",
101102
String.class);
102103
if (StringUtils.hasText(resolutionRequired)) {
103-
String resolutionRequiredValue = this.beanFactory.resolveEmbeddedValue(resolutionRequired);
104-
if (StringUtils.hasText(resolutionRequiredValue)) {
105-
methodInvokingRouter.setResolutionRequired(Boolean.parseBoolean(resolutionRequiredValue));
106-
}
104+
methodInvokingRouter.setResolutionRequired(resolveAttributeToBoolean(resolutionRequired));
107105
}
108106

109107
String prefix = MessagingAnnotationUtils.resolveAttribute(annotations, "prefix", String.class);

spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.core.annotation.AnnotatedElementUtils;
2727
import org.springframework.integration.annotation.ServiceActivator;
2828
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
29+
import org.springframework.integration.handler.MessageProcessor;
2930
import org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper;
3031
import org.springframework.integration.handler.ServiceActivatingHandler;
3132
import org.springframework.integration.util.MessagingAnnotationUtils;
@@ -63,7 +64,13 @@ protected MessageHandler createHandler(Object bean, Method method, List<Annotati
6364
return new ReplyProducingMessageHandlerWrapper((MessageHandler) target);
6465
}
6566
else {
66-
serviceActivator = new ServiceActivatingHandler(target);
67+
MessageProcessor<?> messageProcessor = buildLambdaMessageProcessorForBeanMethod(method, target);
68+
if (messageProcessor != null) {
69+
serviceActivator = new ServiceActivatingHandler(messageProcessor);
70+
}
71+
else {
72+
serviceActivator = new ServiceActivatingHandler(target);
73+
}
6774
}
6875
}
6976
else {
@@ -77,15 +84,15 @@ protected MessageHandler createHandler(Object bean, Method method, List<Annotati
7784

7885
String requiresReply = MessagingAnnotationUtils.resolveAttribute(annotations, "requiresReply", String.class);
7986
if (StringUtils.hasText(requiresReply)) {
80-
serviceActivator.setRequiresReply(Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(requiresReply)));
87+
serviceActivator.setRequiresReply(resolveAttributeToBoolean(requiresReply));
8188
}
8289

8390
String isAsync = MessagingAnnotationUtils.resolveAttribute(annotations, "async", String.class);
8491
if (StringUtils.hasText(isAsync)) {
85-
serviceActivator.setAsync(Boolean.parseBoolean(this.beanFactory.resolveEmbeddedValue(isAsync)));
92+
serviceActivator.setAsync(resolveAttributeToBoolean(isAsync));
8693
}
8794

88-
this.setOutputChannelIfPresent(annotations, serviceActivator);
95+
setOutputChannelIfPresent(annotations, serviceActivator);
8996
return serviceActivator;
9097
}
9198

0 commit comments

Comments
 (0)