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:
committed by
Gary Russell
parent
532941c546
commit
9780a6c893
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user