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 `<int:annotation-configuration>`
* 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
This commit is contained in:
Artem Bilan
2018-03-20 11:59:31 -04:00
committed by Gary Russell
parent 12301f7fae
commit b1a3ca764c
8 changed files with 308 additions and 143 deletions

View File

@@ -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<MessageChannel> 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<Object, Object> argumentMap = new HashMap<Object, Object>();
Map<Object, Object> 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<String, Object> headers = this.evaluateHeaders(method, context);
Map<String, Object> headers = evaluateHeaders(method, context);
if (headers != null) {
builder.copyHeaders(headers);
}
@@ -197,26 +190,15 @@ public class MessagePublishingInterceptor implements MethodInterceptor, BeanFact
}
}
private Map<String, Object> evaluateHeaders(Method method, StandardEvaluationContext context)
throws ParseException, EvaluationException {
private Map<String, Object> evaluateHeaders(Method method, StandardEvaluationContext context) {
Map<String, String> headerExpressionMap = this.metadataSource.getHeaderExpressions(method);
Map<String, Expression> headerExpressionMap = this.metadataSource.getExpressionsForHeaders(method);
if (headerExpressionMap != null) {
Map<String, Object> headers = new HashMap<String, Object>();
for (Map.Entry<String, String> 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;
}

View File

@@ -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<Method, String> channels = new HashMap<>();
private final Map<Method, Expression> payloadExpressions = new HashMap<>();
private final Map<Method, Map<String, Expression>> headersExpressions = new HashMap<>();
private final Set<Class<? extends Annotation>> annotationTypes;
private volatile String channelAttributeName = "channel";
private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
public MethodAnnotationPublisherMetadataSource() {
this(Collections.<Class<? extends Annotation>>singleton(Publisher.class));
this(Collections.singleton(Publisher.class));
}
public MethodAnnotationPublisherMetadataSource(Set<Class<? extends Annotation>> 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<String, String> getHeaderExpressions(Method method) {
Map<String, String> headerExpressions = new HashMap<String, String>();
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<String, Expression> getExpressionsForHeaders(Method method) {
return this.headersExpressions.computeIfAbsent(method, method1 -> {
Map<String, Expression> 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<String, String> getHeaderExpressions(Method method) {
return getExpressionsForHeaders(method)
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getExpressionString()));
}
private <T> T getAnnotationValue(Method method, String attributeName, Class<T> 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> T getAnnotationValue(Annotation annotation, String attributeName, Class<T> 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())) {

View File

@@ -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<String, String> payloadExpressionMap;
private final Map<String, Expression> payloadExpressionMap;
private volatile Map<String, Map<String, String>> headerExpressionMap = Collections.emptyMap();
private volatile Map<String, Map<String, Expression>> headerExpressionMap = Collections.emptyMap();
private volatile Map<String, String> channelMap = Collections.emptyMap();
public MethodNameMappingPublisherMetadataSource(Map<String, String> 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<String, Map<String, String>> 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<String, String> channelMap) {
this.channelMap = channelMap;
}
@Override
public Expression getExpressionForPayload(Method method) {
for (Map.Entry<String, Expression> 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<String, String> 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<String, String> getHeaderExpressions(Method method) {
for (Map.Entry<String, Map<String, String>> entry : this.headerExpressionMap.entrySet()) {
if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) {
return entry.getValue();
}
}
return null;
Map<String, Expression> expressionsForHeaders = getExpressionsForHeaders(method);
return expressionsForHeaders == null
? null
: expressionsForHeaders.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getExpressionString()));
}
@Override
public Map<String, Expression> 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<String, String> entry : this.channelMap.entrySet()) {
if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) {

View File

@@ -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<String, String> 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<String, String> getHeaderExpressions(Method method);
@SuppressWarnings("deprecation")
default Map<String, Expression> getExpressionsForHeaders(Method method) {
return getHeaderExpressions(method)
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
e -> EXPRESSION_PARSER.parseExpression(e.getValue())));
}
}

View File

@@ -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<String, String> headerExpressions;
private volatile Map<String, Expression> 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<String, String> 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<String, String> getHeaderExpressions(Method method) {
return this.headerExpressions
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().getExpressionString()));
}
@Override
public Map<String, Expression> getExpressionsForHeaders(Method method) {
return this.headerExpressions;
}

View File

@@ -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;
* </p>
*
* @author Artem Bilan
*
* @since 3.0
*/
public final class ExpressionEvalMap extends AbstractMap<String, Object> {
public static final EvaluationCallback SIMPLE_CALLBACK = expression -> expression.getValue();
public static final EvaluationCallback SIMPLE_CALLBACK = Expression::getValue;
private final Map<String, ?> original;
@@ -100,9 +102,20 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
return null;
}
@Override
public Set<Map.Entry<String, Object>> entrySet() {
return this.original.entrySet()
.stream()
.map(e -> new SimpleImmutableEntry<>(e.getKey(), get(e.getKey())))
.collect(Collectors.toSet());
}
@Override
public Collection<Object> 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<String, Object> {
}
@Override
public Set<Map.Entry<String, Object>> entrySet() {
throw new UnsupportedOperationException();
public String toString() {
return this.original.toString();
}
@Override
@@ -165,10 +178,6 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return this.original.toString();
}
public static ExpressionEvalMapBuilder from(Map<String, ?> expressions) {
Assert.notNull(expressions, "'expressions' must not be null.");

View File

@@ -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<String, String> getHeaderExpressions(Method method) {
return null;
}
@Override
public Map<String, Expression> getExpressionsForHeaders(Method method) {
return null;
}
@Override
public String getChannelName(Method method) {
return "c";
}
}
}

View File

@@ -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<String, String> headerMap = source.getHeaderExpressions(method);
Map<String, Expression> 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<String, String> headerMap = source.getHeaderExpressions(method);
Map<String, Expression> 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() {
}
}
}