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:
committed by
Gary Russell
parent
12301f7fae
commit
b1a3ca764c
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user