Skip to content

Commit 2d3924a

Browse files
artembilangaryrussell
authored andcommitted
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
1 parent f19f23f commit 2d3924a

File tree

8 files changed

+300
-135
lines changed

8 files changed

+300
-135
lines changed

spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,13 +29,10 @@
2929
import org.springframework.beans.factory.BeanFactoryAware;
3030
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
3131
import org.springframework.core.ParameterNameDiscoverer;
32-
import org.springframework.expression.EvaluationException;
3332
import org.springframework.expression.Expression;
34-
import org.springframework.expression.ExpressionParser;
35-
import org.springframework.expression.ParseException;
36-
import org.springframework.expression.spel.standard.SpelExpressionParser;
3733
import org.springframework.expression.spel.support.StandardEvaluationContext;
3834
import org.springframework.integration.core.MessagingTemplate;
35+
import org.springframework.integration.expression.ExpressionEvalMap;
3936
import org.springframework.integration.expression.ExpressionUtils;
4037
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
4138
import org.springframework.integration.support.DefaultMessageBuilderFactory;
@@ -45,7 +42,6 @@
4542
import org.springframework.messaging.MessageChannel;
4643
import org.springframework.messaging.core.DestinationResolver;
4744
import org.springframework.util.Assert;
48-
import org.springframework.util.StringUtils;
4945

5046
/**
5147
* A {@link MethodInterceptor} that publishes Messages to a channel. The
@@ -56,14 +52,13 @@
5652
* @author Mark Fisher
5753
* @author Artem Bilan
5854
* @author Gary Russell
55+
*
5956
* @since 2.0
6057
*/
6158
public class MessagePublishingInterceptor implements MethodInterceptor, BeanFactoryAware {
6259

6360
private final MessagingTemplate messagingTemplate = new MessagingTemplate();
6461

65-
private final ExpressionParser parser = new SpelExpressionParser();
66-
6762
private volatile PublisherMetadataSource metadataSource;
6863

6964
private volatile DestinationResolver<MessageChannel> channelResolver;
@@ -119,14 +114,13 @@ protected MessageBuilderFactory getMessageBuilderFactory() {
119114

120115
@Override
121116
public final Object invoke(final MethodInvocation invocation) throws Throwable {
122-
Assert.notNull(this.metadataSource, "PublisherMetadataSource is required.");
123117
final StandardEvaluationContext context = ExpressionUtils.createStandardEvaluationContext(this.beanFactory);
124118
Class<?> targetClass = AopUtils.getTargetClass(invocation.getThis());
125119
final Method method = AopUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
126120
String[] argumentNames = this.resolveArgumentNames(method);
127121
context.setVariable(PublisherMetadataSource.METHOD_NAME_VARIABLE_NAME, method.getName());
128122
if (invocation.getArguments().length > 0 && argumentNames != null) {
129-
Map<Object, Object> argumentMap = new HashMap<Object, Object>();
123+
Map<Object, Object> argumentMap = new HashMap<>();
130124
for (int i = 0; i < argumentNames.length; i++) {
131125
if (invocation.getArguments().length <= i) {
132126
break;
@@ -155,18 +149,17 @@ private String[] resolveArgumentNames(Method method) {
155149
return this.parameterNameDiscoverer.getParameterNames(method);
156150
}
157151

158-
private void publishMessage(Method method, StandardEvaluationContext context) throws Exception {
159-
String payloadExpressionString = this.metadataSource.getPayloadExpression(method);
160-
if (!StringUtils.hasText(payloadExpressionString)) {
161-
payloadExpressionString = "#" + PublisherMetadataSource.RETURN_VALUE_VARIABLE_NAME;
152+
private void publishMessage(Method method, StandardEvaluationContext context) {
153+
Expression payloadExpression = this.metadataSource.getExpressionForPayload(method);
154+
if (payloadExpression == null) {
155+
payloadExpression = PublisherMetadataSource.RETURN_VALUE_EXPRESSION;
162156
}
163-
Expression expression = this.parser.parseExpression(payloadExpressionString);
164-
Object result = expression.getValue(context);
157+
Object result = payloadExpression.getValue(context);
165158
if (result != null) {
166159
AbstractIntegrationMessageBuilder<?> builder = (result instanceof Message<?>)
167160
? getMessageBuilderFactory().fromMessage((Message<?>) result)
168161
: getMessageBuilderFactory().withPayload(result);
169-
Map<String, Object> headers = this.evaluateHeaders(method, context);
162+
Map<String, Object> headers = evaluateHeaders(method, context);
170163
if (headers != null) {
171164
builder.copyHeaders(headers);
172165
}
@@ -197,26 +190,15 @@ private void publishMessage(Method method, StandardEvaluationContext context) th
197190
}
198191
}
199192

200-
private Map<String, Object> evaluateHeaders(Method method, StandardEvaluationContext context)
201-
throws ParseException, EvaluationException {
193+
private Map<String, Object> evaluateHeaders(Method method, StandardEvaluationContext context) {
202194

203-
Map<String, String> headerExpressionMap = this.metadataSource.getHeaderExpressions(method);
195+
Map<String, Expression> headerExpressionMap = this.metadataSource.getExpressionsForHeaders(method);
204196
if (headerExpressionMap != null) {
205-
Map<String, Object> headers = new HashMap<String, Object>();
206-
for (Map.Entry<String, String> headerExpressionEntry : headerExpressionMap.entrySet()) {
207-
String headerExpression = headerExpressionEntry.getValue();
208-
if (StringUtils.hasText(headerExpression)) {
209-
Expression expression = this.parser.parseExpression(headerExpression);
210-
Object result = expression.getValue(context);
211-
if (result != null) {
212-
headers.put(headerExpressionEntry.getKey(), result);
213-
}
214-
}
215-
}
216-
if (headers.size() > 0) {
217-
return headers;
218-
}
197+
return ExpressionEvalMap.from(headerExpressionMap)
198+
.usingEvaluationContext(context)
199+
.build();
219200
}
201+
220202
return null;
221203
}
222204

spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java

Lines changed: 95 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,11 +22,13 @@
2222
import java.util.HashMap;
2323
import java.util.Map;
2424
import java.util.Set;
25+
import java.util.stream.Collectors;
2526

2627
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
2728
import org.springframework.core.ParameterNameDiscoverer;
2829
import org.springframework.core.annotation.AnnotatedElementUtils;
2930
import org.springframework.core.annotation.AnnotationUtils;
31+
import org.springframework.expression.Expression;
3032
import org.springframework.integration.annotation.Publisher;
3133
import org.springframework.messaging.handler.annotation.Header;
3234
import org.springframework.messaging.handler.annotation.Payload;
@@ -40,19 +42,26 @@
4042
* @author Mark Fisher
4143
* @author Artem Bilan
4244
* @author Gareth Chapman
45+
*
4346
* @since 2.0
4447
*/
4548
public class MethodAnnotationPublisherMetadataSource implements PublisherMetadataSource {
4649

50+
private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
51+
52+
private final Map<Method, String> channels = new HashMap<>();
53+
54+
private final Map<Method, Expression> payloadExpressions = new HashMap<>();
55+
56+
private final Map<Method, Map<String, Expression>> headersExpressions = new HashMap<>();
57+
4758
private final Set<Class<? extends Annotation>> annotationTypes;
4859

4960
private volatile String channelAttributeName = "channel";
5061

51-
private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
52-
5362

5463
public MethodAnnotationPublisherMetadataSource() {
55-
this(Collections.<Class<? extends Annotation>>singleton(Publisher.class));
64+
this(Collections.singleton(Publisher.class));
5665
}
5766

5867
public MethodAnnotationPublisherMetadataSource(Set<Class<? extends Annotation>> annotationTypes) {
@@ -66,66 +75,98 @@ public void setChannelAttributeName(String channelAttributeName) {
6675
this.channelAttributeName = channelAttributeName;
6776
}
6877

78+
@Override
6979
public String getChannelName(Method method) {
70-
String channelName = this.getAnnotationValue(method, this.channelAttributeName, String.class);
71-
if (channelName == null) {
72-
channelName = this.getAnnotationValue(method.getDeclaringClass(), this.channelAttributeName, String.class);
73-
}
74-
return (StringUtils.hasText(channelName) ? channelName : null);
80+
return this.channels.computeIfAbsent(method, method1 -> {
81+
String channelName = getAnnotationValue(method, this.channelAttributeName, String.class);
82+
if (channelName == null) {
83+
channelName = getAnnotationValue(method.getDeclaringClass(), this.channelAttributeName, String.class);
84+
}
85+
return StringUtils.hasText(channelName) ? channelName : null;
86+
});
87+
7588
}
7689

77-
public String getPayloadExpression(Method method) {
78-
String payloadExpression = null;
79-
Annotation methodPayloadAnnotation = AnnotationUtils.findAnnotation(method, Payload.class);
90+
@Override
91+
public Expression getExpressionForPayload(Method method) {
92+
return this.payloadExpressions.computeIfAbsent(method, method1 -> {
93+
Expression payloadExpression = null;
94+
Annotation methodPayloadAnnotation = AnnotationUtils.findAnnotation(method, Payload.class);
8095

81-
if (methodPayloadAnnotation != null) {
82-
payloadExpression = getAnnotationValue(methodPayloadAnnotation, null, String.class);
83-
if (!StringUtils.hasText(payloadExpression)) {
84-
payloadExpression = "#" + PublisherMetadataSource.RETURN_VALUE_VARIABLE_NAME;
96+
if (methodPayloadAnnotation != null) {
97+
String payloadExpressionString = getAnnotationValue(methodPayloadAnnotation, null, String.class);
98+
if (!StringUtils.hasText(payloadExpressionString)) {
99+
payloadExpression = RETURN_VALUE_EXPRESSION;
100+
}
101+
else {
102+
payloadExpression = EXPRESSION_PARSER.parseExpression(payloadExpressionString);
103+
}
85104
}
86-
}
87105

88-
Annotation[][] annotationArray = method.getParameterAnnotations();
89-
for (int i = 0; i < annotationArray.length; i++) {
90-
Annotation[] parameterAnnotations = annotationArray[i];
91-
for (Annotation currentAnnotation : parameterAnnotations) {
92-
if (Payload.class.equals(currentAnnotation.annotationType())) {
93-
Assert.state(payloadExpression == null,
94-
"@Payload can be used at most once on a @Publisher method, " +
95-
"either at method-level or on a single parameter");
96-
Assert.state("".equals(AnnotationUtils.getValue(currentAnnotation)),
97-
"@Payload on a parameter for a @Publisher method may not contain an expression");
98-
payloadExpression = "#" + PublisherMetadataSource.ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]";
106+
Annotation[][] annotationArray = method.getParameterAnnotations();
107+
for (int i = 0; i < annotationArray.length; i++) {
108+
Annotation[] parameterAnnotations = annotationArray[i];
109+
for (Annotation currentAnnotation : parameterAnnotations) {
110+
if (Payload.class.equals(currentAnnotation.annotationType())) {
111+
Assert.state(payloadExpression == null,
112+
"@Payload can be used at most once on a @Publisher method, " +
113+
"either at method-level or on a single parameter");
114+
115+
Assert.state("".equals(AnnotationUtils.getValue(currentAnnotation)),
116+
"@Payload on a parameter for a @Publisher method may not contain an expression");
117+
118+
payloadExpression =
119+
EXPRESSION_PARSER.parseExpression("#" + ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]");
120+
}
99121
}
100122
}
101-
}
102-
if (payloadExpression == null
103-
|| payloadExpression.contains("#" + PublisherMetadataSource.RETURN_VALUE_VARIABLE_NAME)) {
104-
Assert.isTrue(!void.class.equals(method.getReturnType()),
105-
"When defining @Publisher on a void-returning method, an explicit payload " +
106-
"expression that does not rely upon a #return value is required.");
107-
}
108-
return payloadExpression;
123+
if (payloadExpression == null ||
124+
RETURN_VALUE_EXPRESSION.getExpressionString().equals(payloadExpression.getExpressionString())) {
125+
Assert.isTrue(!void.class.equals(method.getReturnType()),
126+
"When defining @Publisher on a void-returning method, an explicit payload " +
127+
"expression that does not rely upon a #return value is required.");
128+
}
129+
return payloadExpression;
130+
});
109131
}
110132

111-
public Map<String, String> getHeaderExpressions(Method method) {
112-
Map<String, String> headerExpressions = new HashMap<String, String>();
113-
String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);
114-
Annotation[][] annotationArray = method.getParameterAnnotations();
115-
for (int i = 0; i < annotationArray.length; i++) {
116-
Annotation[] parameterAnnotations = annotationArray[i];
117-
for (Annotation currentAnnotation : parameterAnnotations) {
118-
if (Header.class.equals(currentAnnotation.annotationType())) {
119-
String name = getAnnotationValue(currentAnnotation, null, String.class);
120-
if (!StringUtils.hasText(name)) {
121-
name = parameterNames[i];
133+
@Override
134+
@Deprecated
135+
public String getPayloadExpression(Method method) {
136+
return getExpressionForPayload(method)
137+
.getExpressionString();
138+
}
139+
140+
@Override
141+
public Map<String, Expression> getExpressionsForHeaders(Method method) {
142+
return this.headersExpressions.computeIfAbsent(method, method1 -> {
143+
Map<String, Expression> headerExpressions = new HashMap<>();
144+
String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);
145+
Annotation[][] annotationArray = method.getParameterAnnotations();
146+
for (int i = 0; i < annotationArray.length; i++) {
147+
Annotation[] parameterAnnotations = annotationArray[i];
148+
for (Annotation currentAnnotation : parameterAnnotations) {
149+
if (Header.class.equals(currentAnnotation.annotationType())) {
150+
String name = getAnnotationValue(currentAnnotation, null, String.class);
151+
if (!StringUtils.hasText(name)) {
152+
name = parameterNames[i];
153+
}
154+
headerExpressions.put(name,
155+
EXPRESSION_PARSER.parseExpression("#" + ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]"));
122156
}
123-
headerExpressions.put(name,
124-
"#" + PublisherMetadataSource.ARGUMENT_MAP_VARIABLE_NAME + "[" + i + "]");
125157
}
126158
}
127-
}
128-
return headerExpressions;
159+
return headerExpressions;
160+
});
161+
}
162+
163+
@Override
164+
@Deprecated
165+
public Map<String, String> getHeaderExpressions(Method method) {
166+
return getExpressionsForHeaders(method)
167+
.entrySet()
168+
.stream()
169+
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getExpressionString()));
129170
}
130171

131172
private <T> T getAnnotationValue(Method method, String attributeName, Class<T> expectedType) {
@@ -137,7 +178,7 @@ private <T> T getAnnotationValue(Method method, String attributeName, Class<T> e
137178
throw new IllegalStateException(
138179
"method [" + method + "] contains more than one publisher annotation");
139180
}
140-
value = this.getAnnotationValue(annotation, attributeName, expectedType);
181+
value = getAnnotationValue(annotation, attributeName, expectedType);
141182
}
142183
}
143184
return value;
@@ -152,7 +193,7 @@ private <T> T getAnnotationValue(Class<?> clazz, String attributeName, Class<T>
152193
throw new IllegalStateException(
153194
"class [" + clazz + "] contains more than one publisher annotation");
154195
}
155-
value = this.getAnnotationValue(annotation, attributeName, expectedType);
196+
value = getAnnotationValue(annotation, attributeName, expectedType);
156197
}
157198
}
158199
return value;
@@ -161,7 +202,7 @@ private <T> T getAnnotationValue(Class<?> clazz, String attributeName, Class<T>
161202
@SuppressWarnings("unchecked")
162203
private <T> T getAnnotationValue(Annotation annotation, String attributeName, Class<T> expectedType) {
163204
T value = null;
164-
Object valueAsObject = (attributeName == null) ? AnnotationUtils.getValue(annotation)
205+
Object valueAsObject = (attributeName == null) ? AnnotationUtils.getValue(annotation)
165206
: AnnotationUtils.getValue(annotation, attributeName);
166207
if (valueAsObject != null) {
167208
if (expectedType.isAssignableFrom(valueAsObject.getClass())) {

0 commit comments

Comments
 (0)