From b1a3ca764c73e5da4bf10eb4b6be1116375ac227 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 20 Mar 2018 11:59:31 -0400 Subject: [PATCH] INT-4433: Optimize @Publisher metadata JIRA: https://jira.spring.io/browse/INT-4433 The current `MessagePublishingInterceptor` behavior is to parse expressions on each method invocation what is not so efficient at runtime * Introduce `default` `Expression`-based method to the `PublisherMetadataSource` contract and call existing String-based methods for backward compatibility. * Deprecate String-based `PublisherMetadataSource` methods in favor of newly introduced `Expression`-based * Implement new `getExpressionForPayload()` and `getExpressionsForHeaders()` in all the `PublisherMetadataSource` implementations * Cache parsed `Expression` s during initialization in the `PublisherMetadataSource` implementations or do that on demand in the `MethodAnnotationPublisherMetadataSource` by provided method basis * Introduce `MethodAnnotationPublisherMetadataSource#metadataCacheLimit` and populate its value from the `@EnablePublisher` or `` * Implement `entrySet()` and `values()` in the `ExpressionEvalMap` **Cherry-pick to 5.0.x** * Add `@SuppressWarnings("varargs")` to avoid compilation warning * Remove LRU cache logic - it's fine to cache all the info about methods in the classpath --- .../aop/MessagePublishingInterceptor.java | 50 ++---- ...thodAnnotationPublisherMetadataSource.java | 155 +++++++++++------- ...hodNameMappingPublisherMetadataSource.java | 73 +++++++-- .../aop/PublisherMetadataSource.java | 50 +++++- .../aop/SimplePublisherMetadataSource.java | 38 ++++- .../expression/ExpressionEvalMap.java | 27 ++- .../MessagePublishingInterceptorTests.java | 21 ++- ...nnotationPublisherMetadataSourceTests.java | 37 +++-- 8 files changed, 308 insertions(+), 143 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java index 75c719a037..172d90ee23 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,13 +29,10 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; -import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.ParseException; -import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.expression.ExpressionEvalMap; import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.support.AbstractIntegrationMessageBuilder; import org.springframework.integration.support.DefaultMessageBuilderFactory; @@ -45,7 +42,6 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.core.DestinationResolver; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * A {@link MethodInterceptor} that publishes Messages to a channel. The @@ -56,14 +52,13 @@ import org.springframework.util.StringUtils; * @author Mark Fisher * @author Artem Bilan * @author Gary Russell + * * @since 2.0 */ public class MessagePublishingInterceptor implements MethodInterceptor, BeanFactoryAware { private final MessagingTemplate messagingTemplate = new MessagingTemplate(); - private final ExpressionParser parser = new SpelExpressionParser(); - private volatile PublisherMetadataSource metadataSource; private volatile DestinationResolver channelResolver; @@ -119,14 +114,13 @@ public class MessagePublishingInterceptor implements MethodInterceptor, BeanFact @Override public final Object invoke(final MethodInvocation invocation) throws Throwable { - Assert.notNull(this.metadataSource, "PublisherMetadataSource is required."); final StandardEvaluationContext context = ExpressionUtils.createStandardEvaluationContext(this.beanFactory); Class targetClass = AopUtils.getTargetClass(invocation.getThis()); final Method method = AopUtils.getMostSpecificMethod(invocation.getMethod(), targetClass); String[] argumentNames = this.resolveArgumentNames(method); context.setVariable(PublisherMetadataSource.METHOD_NAME_VARIABLE_NAME, method.getName()); if (invocation.getArguments().length > 0 && argumentNames != null) { - Map argumentMap = new HashMap(); + Map argumentMap = new HashMap<>(); for (int i = 0; i < argumentNames.length; i++) { if (invocation.getArguments().length <= i) { break; @@ -155,18 +149,17 @@ public class MessagePublishingInterceptor implements MethodInterceptor, BeanFact return this.parameterNameDiscoverer.getParameterNames(method); } - private void publishMessage(Method method, StandardEvaluationContext context) throws Exception { - String payloadExpressionString = this.metadataSource.getPayloadExpression(method); - if (!StringUtils.hasText(payloadExpressionString)) { - payloadExpressionString = "#" + PublisherMetadataSource.RETURN_VALUE_VARIABLE_NAME; + private void publishMessage(Method method, StandardEvaluationContext context) { + Expression payloadExpression = this.metadataSource.getExpressionForPayload(method); + if (payloadExpression == null) { + payloadExpression = PublisherMetadataSource.RETURN_VALUE_EXPRESSION; } - Expression expression = this.parser.parseExpression(payloadExpressionString); - Object result = expression.getValue(context); + Object result = payloadExpression.getValue(context); if (result != null) { AbstractIntegrationMessageBuilder builder = (result instanceof Message) ? getMessageBuilderFactory().fromMessage((Message) result) : getMessageBuilderFactory().withPayload(result); - Map headers = this.evaluateHeaders(method, context); + Map headers = evaluateHeaders(method, context); if (headers != null) { builder.copyHeaders(headers); } @@ -197,26 +190,15 @@ public class MessagePublishingInterceptor implements MethodInterceptor, BeanFact } } - private Map evaluateHeaders(Method method, StandardEvaluationContext context) - throws ParseException, EvaluationException { + private Map evaluateHeaders(Method method, StandardEvaluationContext context) { - Map headerExpressionMap = this.metadataSource.getHeaderExpressions(method); + Map headerExpressionMap = this.metadataSource.getExpressionsForHeaders(method); if (headerExpressionMap != null) { - Map headers = new HashMap(); - for (Map.Entry headerExpressionEntry : headerExpressionMap.entrySet()) { - String headerExpression = headerExpressionEntry.getValue(); - if (StringUtils.hasText(headerExpression)) { - Expression expression = this.parser.parseExpression(headerExpression); - Object result = expression.getValue(context); - if (result != null) { - headers.put(headerExpressionEntry.getKey(), result); - } - } - } - if (headers.size() > 0) { - return headers; - } + return ExpressionEvalMap.from(headerExpressionMap) + .usingEvaluationContext(context) + .build(); } + return null; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java index 2f0f44cf57..ffe9e8e082 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,11 +22,13 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.expression.Expression; import org.springframework.integration.annotation.Publisher; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Payload; @@ -40,19 +42,26 @@ import org.springframework.util.StringUtils; * @author Mark Fisher * @author Artem Bilan * @author Gareth Chapman + * * @since 2.0 */ public class MethodAnnotationPublisherMetadataSource implements PublisherMetadataSource { + private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + + private final Map channels = new HashMap<>(); + + private final Map payloadExpressions = new HashMap<>(); + + private final Map> headersExpressions = new HashMap<>(); + private final Set> annotationTypes; private volatile String channelAttributeName = "channel"; - private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); - public MethodAnnotationPublisherMetadataSource() { - this(Collections.>singleton(Publisher.class)); + this(Collections.singleton(Publisher.class)); } public MethodAnnotationPublisherMetadataSource(Set> annotationTypes) { @@ -66,66 +75,98 @@ public class MethodAnnotationPublisherMetadataSource implements PublisherMetadat this.channelAttributeName = channelAttributeName; } + @Override public String getChannelName(Method method) { - String channelName = this.getAnnotationValue(method, this.channelAttributeName, String.class); - if (channelName == null) { - channelName = this.getAnnotationValue(method.getDeclaringClass(), this.channelAttributeName, String.class); - } - return (StringUtils.hasText(channelName) ? channelName : null); + return this.channels.computeIfAbsent(method, method1 -> { + String channelName = getAnnotationValue(method, this.channelAttributeName, String.class); + if (channelName == null) { + channelName = getAnnotationValue(method.getDeclaringClass(), this.channelAttributeName, String.class); + } + return StringUtils.hasText(channelName) ? channelName : null; + }); + } - public String getPayloadExpression(Method method) { - String payloadExpression = null; - Annotation methodPayloadAnnotation = AnnotationUtils.findAnnotation(method, Payload.class); + @Override + public Expression getExpressionForPayload(Method method) { + return this.payloadExpressions.computeIfAbsent(method, method1 -> { + Expression payloadExpression = null; + Annotation methodPayloadAnnotation = AnnotationUtils.findAnnotation(method, Payload.class); - if (methodPayloadAnnotation != null) { - payloadExpression = getAnnotationValue(methodPayloadAnnotation, null, String.class); - if (!StringUtils.hasText(payloadExpression)) { - payloadExpression = "#" + PublisherMetadataSource.RETURN_VALUE_VARIABLE_NAME; - } - } - - Annotation[][] annotationArray = method.getParameterAnnotations(); - for (int i = 0; i < annotationArray.length; i++) { - Annotation[] parameterAnnotations = annotationArray[i]; - for (Annotation currentAnnotation : parameterAnnotations) { - if (Payload.class.equals(currentAnnotation.annotationType())) { - Assert.state(payloadExpression == null, - "@Payload can be used at most once on a @Publisher method, " + - "either at method-level or on a single parameter"); - Assert.state("".equals(AnnotationUtils.getValue(currentAnnotation)), - "@Payload on a parameter for a @Publisher method may not contain an expression"); - payloadExpression = "#" + PublisherMetadataSource.ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]"; + if (methodPayloadAnnotation != null) { + String payloadExpressionString = getAnnotationValue(methodPayloadAnnotation, null, String.class); + if (!StringUtils.hasText(payloadExpressionString)) { + payloadExpression = RETURN_VALUE_EXPRESSION; + } + else { + payloadExpression = EXPRESSION_PARSER.parseExpression(payloadExpressionString); } } - } - if (payloadExpression == null - || payloadExpression.contains("#" + PublisherMetadataSource.RETURN_VALUE_VARIABLE_NAME)) { - Assert.isTrue(!void.class.equals(method.getReturnType()), - "When defining @Publisher on a void-returning method, an explicit payload " + - "expression that does not rely upon a #return value is required."); - } - return payloadExpression; - } - public Map getHeaderExpressions(Method method) { - Map headerExpressions = new HashMap(); - String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method); - Annotation[][] annotationArray = method.getParameterAnnotations(); - for (int i = 0; i < annotationArray.length; i++) { - Annotation[] parameterAnnotations = annotationArray[i]; - for (Annotation currentAnnotation : parameterAnnotations) { - if (Header.class.equals(currentAnnotation.annotationType())) { - String name = getAnnotationValue(currentAnnotation, null, String.class); - if (!StringUtils.hasText(name)) { - name = parameterNames[i]; + Annotation[][] annotationArray = method.getParameterAnnotations(); + for (int i = 0; i < annotationArray.length; i++) { + Annotation[] parameterAnnotations = annotationArray[i]; + for (Annotation currentAnnotation : parameterAnnotations) { + if (Payload.class.equals(currentAnnotation.annotationType())) { + Assert.state(payloadExpression == null, + "@Payload can be used at most once on a @Publisher method, " + + "either at method-level or on a single parameter"); + + Assert.state("".equals(AnnotationUtils.getValue(currentAnnotation)), + "@Payload on a parameter for a @Publisher method may not contain an expression"); + + payloadExpression = + EXPRESSION_PARSER.parseExpression("#" + ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]"); } - headerExpressions.put(name, - "#" + PublisherMetadataSource.ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]"); } } - } - return headerExpressions; + if (payloadExpression == null || + RETURN_VALUE_EXPRESSION.getExpressionString().equals(payloadExpression.getExpressionString())) { + Assert.isTrue(!void.class.equals(method.getReturnType()), + "When defining @Publisher on a void-returning method, an explicit payload " + + "expression that does not rely upon a #return value is required."); + } + return payloadExpression; + }); + } + + @Override + @Deprecated + public String getPayloadExpression(Method method) { + return getExpressionForPayload(method) + .getExpressionString(); + } + + @Override + public Map getExpressionsForHeaders(Method method) { + return this.headersExpressions.computeIfAbsent(method, method1 -> { + Map headerExpressions = new HashMap<>(); + String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method); + Annotation[][] annotationArray = method.getParameterAnnotations(); + for (int i = 0; i < annotationArray.length; i++) { + Annotation[] parameterAnnotations = annotationArray[i]; + for (Annotation currentAnnotation : parameterAnnotations) { + if (Header.class.equals(currentAnnotation.annotationType())) { + String name = getAnnotationValue(currentAnnotation, null, String.class); + if (!StringUtils.hasText(name)) { + name = parameterNames[i]; + } + headerExpressions.put(name, + EXPRESSION_PARSER.parseExpression("#" + ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]")); + } + } + } + return headerExpressions; + }); + } + + @Override + @Deprecated + public Map getHeaderExpressions(Method method) { + return getExpressionsForHeaders(method) + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getExpressionString())); } private T getAnnotationValue(Method method, String attributeName, Class expectedType) { @@ -137,7 +178,7 @@ public class MethodAnnotationPublisherMetadataSource implements PublisherMetadat throw new IllegalStateException( "method [" + method + "] contains more than one publisher annotation"); } - value = this.getAnnotationValue(annotation, attributeName, expectedType); + value = getAnnotationValue(annotation, attributeName, expectedType); } } return value; @@ -152,7 +193,7 @@ public class MethodAnnotationPublisherMetadataSource implements PublisherMetadat throw new IllegalStateException( "class [" + clazz + "] contains more than one publisher annotation"); } - value = this.getAnnotationValue(annotation, attributeName, expectedType); + value = getAnnotationValue(annotation, attributeName, expectedType); } } return value; @@ -161,7 +202,7 @@ public class MethodAnnotationPublisherMetadataSource implements PublisherMetadat @SuppressWarnings("unchecked") private T getAnnotationValue(Annotation annotation, String attributeName, Class expectedType) { T value = null; - Object valueAsObject = (attributeName == null) ? AnnotationUtils.getValue(annotation) + Object valueAsObject = (attributeName == null) ? AnnotationUtils.getValue(annotation) : AnnotationUtils.getValue(annotation, attributeName); if (valueAsObject != null) { if (expectedType.isAssignableFrom(valueAsObject.getClass())) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodNameMappingPublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodNameMappingPublisherMetadataSource.java index 38e3424274..81cad0b5b0 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodNameMappingPublisherMetadataSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodNameMappingPublisherMetadataSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,55 +19,94 @@ package org.springframework.integration.aop; import java.lang.reflect.Method; import java.util.Collections; import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.expression.Expression; import org.springframework.util.Assert; import org.springframework.util.PatternMatchUtils; /** * @author Mark Fisher + * @author Artem Bilan + * * @since 2.0 */ public class MethodNameMappingPublisherMetadataSource implements PublisherMetadataSource { - private final Map payloadExpressionMap; + private final Map payloadExpressionMap; - private volatile Map> headerExpressionMap = Collections.emptyMap(); + private volatile Map> headerExpressionMap = Collections.emptyMap(); private volatile Map channelMap = Collections.emptyMap(); public MethodNameMappingPublisherMetadataSource(Map payloadExpressionMap) { Assert.notEmpty(payloadExpressionMap, "payloadExpressionMap must not be empty"); - this.payloadExpressionMap = payloadExpressionMap; + this.payloadExpressionMap = + payloadExpressionMap.entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, + e -> EXPRESSION_PARSER.parseExpression(e.getValue()))); } public void setHeaderExpressionMap(Map> headerExpressionMap) { - this.headerExpressionMap = headerExpressionMap; + this.headerExpressionMap = + headerExpressionMap + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, + e -> e.getValue() + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, + entry -> EXPRESSION_PARSER.parseExpression(entry.getValue()))))); } public void setChannelMap(Map channelMap) { this.channelMap = channelMap; } + @Override + public Expression getExpressionForPayload(Method method) { + for (Map.Entry entry : this.payloadExpressionMap.entrySet()) { + if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) { + return entry.getValue(); + } + } + return null; + } + + @Override + @Deprecated public String getPayloadExpression(Method method) { - for (Map.Entry entry : this.payloadExpressionMap.entrySet()) { - if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) { - return entry.getValue(); - } - } - return null; + Expression expressionForPayload = getExpressionForPayload(method); + return expressionForPayload != null ? expressionForPayload.getExpressionString() : null; } + @Override + @Deprecated public Map getHeaderExpressions(Method method) { - for (Map.Entry> entry : this.headerExpressionMap.entrySet()) { - if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) { - return entry.getValue(); - } - } - return null; + Map expressionsForHeaders = getExpressionsForHeaders(method); + return expressionsForHeaders == null + ? null + : expressionsForHeaders.entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getExpressionString())); } + @Override + public Map getExpressionsForHeaders(Method method) { + return this.headerExpressionMap + .entrySet() + .stream() + .filter(e -> PatternMatchUtils.simpleMatch(e.getKey(), method.getName())) + .map(Map.Entry::getValue) + .findFirst() + .orElse(null); + } + + @Override public String getChannelName(Method method) { for (Map.Entry entry : this.channelMap.entrySet()) { if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherMetadataSource.java index 2b9cf52ad8..202b19a669 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherMetadataSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherMetadataSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,11 @@ package org.springframework.integration.aop; import java.lang.reflect.Method; import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; /** * Strategy for determining the channel name, payload expression, and header expressions @@ -25,6 +30,8 @@ import java.util.Map; * * @author Mark Fisher * @author Gary Russell + * @author Artem Bilan + * * @since 2.0 */ interface PublisherMetadataSource { @@ -37,6 +44,11 @@ interface PublisherMetadataSource { String EXCEPTION_VARIABLE_NAME = "exception"; + ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); + + Expression RETURN_VALUE_EXPRESSION = + EXPRESSION_PARSER.parseExpression("#" + PublisherMetadataSource.RETURN_VALUE_VARIABLE_NAME); + /** * Returns the channel name to which Messages should be published @@ -50,20 +62,50 @@ interface PublisherMetadataSource { /** * Returns the expression string to be evaluated for creating the Message * payload. - * * @param method The Method. * @return The payload expression. + * @deprecated since 5.0.4 in favor of {@link #getExpressionForPayload(Method)} */ + @Deprecated String getPayloadExpression(Method method); + /** + * Returns the SpEL expression to be evaluated for creating the Message + * payload. + * @param method the Method. + * @return rhe payload expression. + * @since 5.0.4 + */ + @SuppressWarnings("deprecation") + default Expression getExpressionForPayload(Method method) { + return EXPRESSION_PARSER.parseExpression(getPayloadExpression(method)); + } + + /** + * Returns the map of expression strings to be evaluated for any headers + * that should be set on the published Message. The keys in the Map are + * header names, the values are the expression strings. + * @param method The Method. + * @return The header expressions. + * @deprecated since 5.0.4 in favor of {@link #getExpressionsForHeaders(Method)} + */ + @Deprecated + Map getHeaderExpressions(Method method); + /** * Returns the map of expression strings to be evaluated for any headers * that should be set on the published Message. The keys in the Map are * header names, the values are the expression strings. - * * @param method The Method. * @return The header expressions. */ - Map getHeaderExpressions(Method method); + @SuppressWarnings("deprecation") + default Map getExpressionsForHeaders(Method method) { + return getHeaderExpressions(method) + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, + e -> EXPRESSION_PARSER.parseExpression(e.getValue()))); + } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java index 5ff4fa729a..7dc8b01977 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,9 @@ package org.springframework.integration.aop; import java.lang.reflect.Method; import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.expression.Expression; /** * Simple implementation of {@link PublisherMetadataSource} that allows for @@ -25,38 +28,63 @@ import java.util.Map; * array of header key=value expressions. * * @author Mark Fisher + * @author Artem Bilan + * * @since 2.0 */ public class SimplePublisherMetadataSource implements PublisherMetadataSource { private volatile String channelName; - private volatile String payloadExpression; + private volatile Expression payloadExpression; - private volatile Map headerExpressions; + private volatile Map headerExpressions; public void setChannelName(String channelName) { this.channelName = channelName; } + @Override public String getChannelName(Method method) { return this.channelName; } public void setPayloadExpression(String payloadExpression) { - this.payloadExpression = payloadExpression; + this.payloadExpression = EXPRESSION_PARSER.parseExpression(payloadExpression); } + @Override + @Deprecated public String getPayloadExpression(Method method) { + return this.payloadExpression.getExpressionString(); + } + + @Override + public Expression getExpressionForPayload(Method method) { return this.payloadExpression; } public void setHeaderExpressions(Map headerExpressions) { - this.headerExpressions = headerExpressions; + this.headerExpressions = + headerExpressions.entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, + e -> EXPRESSION_PARSER.parseExpression(e.getValue()))); } + @Override + @Deprecated public Map getHeaderExpressions(Method method) { + return this.headerExpressions + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, + e -> e.getValue().getExpressionString())); + } + + @Override + public Map getExpressionsForHeaders(Method method) { return this.headerExpressions; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java index f341b3be5f..a1b4c5fbda 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.AbstractMap; import java.util.Collection; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; @@ -60,11 +61,12 @@ import org.springframework.util.Assert; *

* * @author Artem Bilan + * * @since 3.0 */ public final class ExpressionEvalMap extends AbstractMap { - public static final EvaluationCallback SIMPLE_CALLBACK = expression -> expression.getValue(); + public static final EvaluationCallback SIMPLE_CALLBACK = Expression::getValue; private final Map original; @@ -100,9 +102,20 @@ public final class ExpressionEvalMap extends AbstractMap { return null; } + @Override + public Set> entrySet() { + return this.original.entrySet() + .stream() + .map(e -> new SimpleImmutableEntry<>(e.getKey(), get(e.getKey()))) + .collect(Collectors.toSet()); + } + @Override public Collection values() { - throw new UnsupportedOperationException(); + return this.original.values() + .stream() + .map(this::get) + .collect(Collectors.toList()); } @Override @@ -136,8 +149,8 @@ public final class ExpressionEvalMap extends AbstractMap { } @Override - public Set> entrySet() { - throw new UnsupportedOperationException(); + public String toString() { + return this.original.toString(); } @Override @@ -165,10 +178,6 @@ public final class ExpressionEvalMap extends AbstractMap { throw new UnsupportedOperationException(); } - @Override - public String toString() { - return this.original.toString(); - } public static ExpressionEvalMapBuilder from(Map expressions) { Assert.notNull(expressions, "'expressions' must not be null."); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java index 7ec4b95650..4fff07bd91 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import org.junit.Test; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.expression.Expression; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.support.channel.BeanFactoryChannelResolver; import org.springframework.messaging.Message; @@ -37,6 +38,8 @@ import org.springframework.messaging.core.DestinationResolver; /** * @author Mark Fisher * @author Oleg Zhurakousky + * @author Artem Bilan + * * @since 2.0 */ public class MessagePublishingInterceptorTests { @@ -124,19 +127,33 @@ public class MessagePublishingInterceptorTests { } @Override + @Deprecated public String getPayloadExpression(Method method) { - return "'test-' + #return"; + return getExpressionForPayload(method) + .getExpressionString(); } @Override + public Expression getExpressionForPayload(Method method) { + return EXPRESSION_PARSER.parseExpression("'test-' + #return"); + } + + @Override + @Deprecated public Map getHeaderExpressions(Method method) { return null; } + @Override + public Map getExpressionsForHeaders(Method method) { + return null; + } + @Override public String getChannelName(Method method) { return "c"; } + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSourceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSourceTests.java index 43b4756710..858392fa84 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSourceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,12 +27,15 @@ import java.util.Map; import org.junit.Test; import org.springframework.core.annotation.AliasFor; +import org.springframework.expression.Expression; import org.springframework.integration.annotation.Publisher; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Payload; /** * @author Mark Fisher + * @author Artem Bilan + * * @since 2.0 */ public class MethodAnnotationPublisherMetadataSourceTests { @@ -44,26 +47,26 @@ public class MethodAnnotationPublisherMetadataSourceTests { public void channelNameAndExplicitReturnValuePayload() { Method method = getMethod("methodWithChannelAndExplicitReturnAsPayload"); String channelName = source.getChannelName(method); - String payloadExpression = source.getPayloadExpression(method); + Expression payloadExpression = source.getExpressionForPayload(method); assertEquals("foo", channelName); - assertEquals("#return", payloadExpression); + assertEquals("#return", payloadExpression.getExpressionString()); } @Test public void channelNameAndEmptyPayloadAnnotation() { Method method = getMethod("methodWithChannelAndEmptyPayloadAnnotation"); String channelName = source.getChannelName(method); - String payloadExpression = source.getPayloadExpression(method); + Expression payloadExpression = source.getExpressionForPayload(method); assertEquals("foo", channelName); - assertEquals("#return", payloadExpression); + assertEquals("#return", payloadExpression.getExpressionString()); } @Test public void payloadButNoHeaders() { Method method = getMethod("methodWithPayloadAnnotation", String.class, int.class); - String expressionString = source.getPayloadExpression(method); + String expressionString = source.getExpressionForPayload(method).getExpressionString(); assertEquals("testExpression1", expressionString); - Map headerMap = source.getHeaderExpressions(method); + Map headerMap = source.getExpressionsForHeaders(method); assertNotNull(headerMap); assertEquals(0, headerMap.size()); } @@ -71,20 +74,20 @@ public class MethodAnnotationPublisherMetadataSourceTests { @Test public void payloadAndHeaders() { Method method = getMethod("methodWithHeaderAnnotations", String.class, String.class, String.class); - String expressionString = source.getPayloadExpression(method); + String expressionString = source.getExpressionForPayload(method).getExpressionString(); assertEquals("testExpression2", expressionString); - Map headerMap = source.getHeaderExpressions(method); + Map headerMap = source.getExpressionsForHeaders(method); assertNotNull(headerMap); assertEquals(2, headerMap.size()); - assertEquals("#args[1]", headerMap.get("foo")); - assertEquals("#args[2]", headerMap.get("bar")); + assertEquals("#args[1]", headerMap.get("foo").getExpressionString()); + assertEquals("#args[2]", headerMap.get("bar").getExpressionString()); } @Test public void voidReturnWithValidPayloadExpression() { Method method = getMethod("methodWithVoidReturnAndMethodNameAsPayload"); String channelName = source.getChannelName(method); - String payloadExpression = source.getPayloadExpression(method); + String payloadExpression = source.getExpressionForPayload(method).getExpressionString(); assertEquals("foo", channelName); assertEquals("#method", payloadExpression); } @@ -92,20 +95,20 @@ public class MethodAnnotationPublisherMetadataSourceTests { @Test(expected = IllegalArgumentException.class) public void voidReturnWithInvalidPayloadExpression() { Method method = getMethod("methodWithVoidReturnAndReturnValueAsPayload"); - source.getPayloadExpression(method); + source.getExpressionForPayload(method); } @Test public void voidReturnAndParameterPayloadAnnotation() { Method method = getMethod("methodWithVoidReturnAndParameterPayloadAnnotation", String.class); - String payloadExpression = source.getPayloadExpression(method); + String payloadExpression = source.getExpressionForPayload(method).getExpressionString(); assertEquals("#args[0]", payloadExpression); } @Test(expected = IllegalArgumentException.class) public void voidReturnAndNoPayloadAnnotation() { Method method = getMethod("methodWithVoidReturnAndNoPayloadAnnotation", String.class); - source.getPayloadExpression(method); + source.getExpressionForPayload(method); } @Test @@ -184,8 +187,10 @@ public class MethodAnnotationPublisherMetadataSourceTests { @Publisher @Retention(RetentionPolicy.RUNTIME) public @interface CustomPublisher { + @AliasFor(annotation = Publisher.class, attribute = "channel") String custom(); + } @CustomPublisher(custom = "foo") @@ -194,8 +199,10 @@ public class MethodAnnotationPublisherMetadataSourceTests { @CustomPublisher(custom = "bar") public class TestClass { + public void methodWithAnnotationOnTheDeclaringClass() { } + } }