Use MergedAnnotations in MethodAnnPublisher (#2914)

* For better performance and consistency use a `MergedAnnotations` in
the `MethodAnnotationPublisherMetadataSource` instead of
`AnnotatedElementUtils`
* Remove deprecated API from the `PublisherMetadataSource` hierarchy
This commit is contained in:
Artem Bilan
2019-05-01 14:11:20 -04:00
committed by Gary Russell
parent 532941c546
commit 9780a6c893
5 changed files with 93 additions and 208 deletions

View File

@@ -17,20 +17,21 @@
package org.springframework.integration.aop;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
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.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.expression.Expression;
import org.springframework.integration.annotation.Publisher;
import org.springframework.lang.Nullable;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.util.Assert;
@@ -80,143 +81,105 @@ public class MethodAnnotationPublisherMetadataSource implements PublisherMetadat
@Override
public String getChannelName(Method method) {
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;
});
return this.channels.computeIfAbsent(method,
method1 -> {
String channelName = getAnnotationValue(method, this.channelAttributeName);
if (channelName == null) {
channelName = getAnnotationValue(method.getDeclaringClass(), this.channelAttributeName);
}
return StringUtils.hasText(channelName) ? channelName : null;
});
}
@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) {
String payloadExpressionString = getAnnotationValue(methodPayloadAnnotation, null, String.class);
if (!StringUtils.hasText(payloadExpressionString)) {
payloadExpression = RETURN_VALUE_EXPRESSION;
}
else {
payloadExpression = EXPRESSION_PARSER.parseExpression(payloadExpressionString);
}
}
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(AnnotationUtils.synthesizeAnnotation(currentAnnotation, null))),
"@Payload on a parameter for a @Publisher method may not contain an expression");
payloadExpression =
EXPRESSION_PARSER.parseExpression("#" + ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]");
return this.payloadExpressions.computeIfAbsent(method,
method1 -> {
Expression payloadExpression = null;
MergedAnnotation<Payload> payloadMergedAnnotation =
MergedAnnotations.from(method, MergedAnnotations.SearchStrategy.EXHAUSTIVE)
.get(Payload.class);
if (payloadMergedAnnotation.isPresent()) {
String payloadExpressionString = payloadMergedAnnotation.getString("expression");
if (!StringUtils.hasText(payloadExpressionString)) {
payloadExpression = RETURN_VALUE_EXPRESSION;
}
else {
payloadExpression = EXPRESSION_PARSER.parseExpression(payloadExpressionString);
}
}
}
}
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();
Annotation[][] annotationArray = method.getParameterAnnotations();
for (int i = 0; i < annotationArray.length; i++) {
Annotation[] parameterAnnotations = annotationArray[i];
payloadMergedAnnotation = MergedAnnotations.from(parameterAnnotations).get(Payload.class);
if (payloadMergedAnnotation.isPresent()) {
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(payloadMergedAnnotation.getString("expression")),
"@Payload on a parameter for a @Publisher method may not contain an 'expression'");
payloadExpression =
EXPRESSION_PARSER.parseExpression("#" + ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]");
}
}
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
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];
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];
MergedAnnotation<Header> headerMergedAnnotation =
MergedAnnotations.from(parameterAnnotations).get(Header.class);
if (headerMergedAnnotation.isPresent()) {
String name =
headerMergedAnnotation
.getString("name");
if (!StringUtils.hasText(name)) {
if (parameterNames != null) {
name = parameterNames[i];
}
else {
name = method.getName() + ".arg#" + i;
}
}
headerExpressions.put(name,
EXPRESSION_PARSER.parseExpression('#' + ARGUMENT_MAP_VARIABLE_NAME + '[' + i + ']'));
}
headerExpressions.put(name,
EXPRESSION_PARSER.parseExpression("#" + ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]"));
}
}
}
return headerExpressions;
});
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) {
T value = null;
@Nullable
private String getAnnotationValue(AnnotatedElement element, String attributeName) {
MergedAnnotations mergedAnnotations =
MergedAnnotations.from(element, MergedAnnotations.SearchStrategy.EXHAUSTIVE);
String value = null;
for (Class<? extends Annotation> annotationType : this.annotationTypes) {
Annotation annotation = AnnotatedElementUtils.findMergedAnnotation(method, annotationType);
if (annotation != null) {
MergedAnnotation<? extends Annotation> mergedAnnotation = mergedAnnotations.get(annotationType);
if (mergedAnnotation.isPresent()) {
if (value != null) {
throw new IllegalStateException(
"method [" + method + "] contains more than one publisher annotation");
"The [" + element + "] contains more than one publisher annotation");
}
value = getAnnotationValue(annotation, attributeName, expectedType);
}
}
return value;
}
private <T> T getAnnotationValue(Class<?> clazz, String attributeName, Class<T> expectedType) {
T value = null;
for (Class<? extends Annotation> annotationType : this.annotationTypes) {
Annotation annotation = AnnotatedElementUtils.findMergedAnnotation(clazz, annotationType);
if (annotation != null) {
if (value != null) {
throw new IllegalStateException(
"class [" + clazz + "] contains more than one publisher annotation");
}
value = getAnnotationValue(annotation, attributeName, expectedType);
}
}
return value;
}
@SuppressWarnings("unchecked")
private <T> T getAnnotationValue(Annotation annotation, String attributeName, Class<T> expectedType) {
T value = null;
Object valueAsObject = (attributeName == null) ?
AnnotationUtils.getValue(AnnotationUtils.synthesizeAnnotation(annotation, null)) :
AnnotationUtils.getValue(annotation, attributeName);
if (valueAsObject != null) {
if (expectedType.isAssignableFrom(valueAsObject.getClass())) {
value = (T) valueAsObject;
}
else {
throw new IllegalArgumentException("expected type [" + expectedType.getName() +
"] for attribute '" + attributeName + "' on publisher annotation [" +
annotation.annotationType() + "], but actual type was [" + valueAsObject.getClass() + "]");
value = mergedAnnotation.getString(attributeName);
}
}
return value;

View File

@@ -77,24 +77,6 @@ public class MethodNameMappingPublisherMetadataSource implements PublisherMetada
return null;
}
@Override
@Deprecated
public String getPayloadExpression(Method method) {
Expression expressionForPayload = getExpressionForPayload(method);
return expressionForPayload != null ? expressionForPayload.getExpressionString() : null;
}
@Override
@Deprecated
public Map<String, String> getHeaderExpressions(Method method) {
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

View File

@@ -18,7 +18,6 @@ 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;
@@ -59,16 +58,6 @@ interface PublisherMetadataSource {
*/
String getChannelName(Method method);
/**
* 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.
@@ -76,21 +65,7 @@ interface PublisherMetadataSource {
* @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);
Expression getExpressionForPayload(Method method);
/**
* Returns the map of expression strings to be evaluated for any headers
@@ -99,13 +74,6 @@ interface PublisherMetadataSource {
* @param method The Method.
* @return The header expressions.
*/
@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())));
}
Map<String, Expression> getExpressionsForHeaders(Method method);
}

View File

@@ -54,12 +54,6 @@ public class SimplePublisherMetadataSource implements PublisherMetadataSource {
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;
@@ -73,16 +67,6 @@ public class SimplePublisherMetadataSource implements PublisherMetadataSource {
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

@@ -73,15 +73,16 @@ public class MessagePublishingInterceptorTests {
@Test
public void demoMethodNameMappingExpressionSource() {
Map<String, String> expressionMap = new HashMap<String, String>();
Map<String, String> expressionMap = new HashMap<>();
expressionMap.put("test", "#return");
MethodNameMappingPublisherMetadataSource metadataSource = new MethodNameMappingPublisherMetadataSource(expressionMap);
Map<String, String> channelMap = new HashMap<String, String>();
MethodNameMappingPublisherMetadataSource metadataSource =
new MethodNameMappingPublisherMetadataSource(expressionMap);
Map<String, String> channelMap = new HashMap<>();
channelMap.put("test", "c");
metadataSource.setChannelMap(channelMap);
Map<String, Map<String, String>> headerExpressionMap = new HashMap<String, Map<String, String>>();
Map<String, String> headerExpressions = new HashMap<String, String>();
Map<String, Map<String, String>> headerExpressionMap = new HashMap<>();
Map<String, String> headerExpressions = new HashMap<>();
headerExpressions.put("bar", "#return");
headerExpressions.put("name", "'oleg'");
headerExpressionMap.put("test", headerExpressions);
@@ -125,24 +126,11 @@ public class MessagePublishingInterceptorTests {
super();
}
@Override
@Deprecated
public String getPayloadExpression(Method method) {
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;