INT-4219: Add @UseSpelInvoker
JIRA: https://jira.spring.io/browse/INT-4219 Defer Expression Compilation; Resolve Placeholders Uncomment BeanExpressionResolver Stuff Fix Test Failures (Deferred Expression Parsing) More Test Fixes Broke tests that manipulated the EXPRESSION_PARSER - change EXPRESSION_PARSER_DEFAULT to reference EXPRESSION_PARSER from the super class. More Tests and Docs Doc Polishing
This commit is contained in:
committed by
Artem Bilan
parent
56708b448c
commit
a50d866f81
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2017 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* Indicates that a POJO handler method ({@code @ServiceActivator, @Transformer, } etc.,
|
||||
* or such methods invoked from XML definitions) should be invoked using SpEL.
|
||||
* <p>In versions prior to 5.0, such methods were always invoked using SpEL. In 5.0, the
|
||||
* framework switched to using
|
||||
* {@link org.springframework.messaging.handler.invocation.InvocableHandlerMethod} instead
|
||||
* which is generally more efficient than (interpreted) SpEL.
|
||||
* <p>There may be some unanticipated corner case where it is necessary to revert to using
|
||||
* SpEL. Also, for very high performance requirements, you may wish to consider using
|
||||
* compiled SpEL which is often the fastest solution (when the expression is compilable).
|
||||
* <p>Applying this annotation to those methods will cause SpEL to be used for the
|
||||
* invocation. An optional {@code compilerMode} property (aliased to value) is also provided.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 5.0
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface UseSpelInvoker {
|
||||
|
||||
/**
|
||||
* Specify that the annotated method (or methods in the annotated class) will be
|
||||
* invoked using SpEL instead of an
|
||||
* {@link org.springframework.messaging.handler.invocation.InvocableHandlerMethod}
|
||||
* with the specified compilerMode. If left empty, the default runtime compiler
|
||||
* mode will be used. Must evaluate to a String containing a valid compiler mode.
|
||||
* @return The compilerMode.
|
||||
* @see org.springframework.expression.spel.SpelCompilerMode
|
||||
*/
|
||||
@AliasFor("compilerMode")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Specify that the annotated method (or methods in the annotated class) will be
|
||||
* invoked using SpEL instead of an
|
||||
* {@link org.springframework.messaging.handler.invocation.InvocableHandlerMethod}
|
||||
* with the specified compilerMode. If left empty, the default runtime compiler
|
||||
* mode will be used. Must evaluate to a String containing a valid compiler mode.
|
||||
* @return The compilerMode.
|
||||
* @see org.springframework.expression.spel.SpelCompilerMode
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String compilerMode() default "";
|
||||
|
||||
}
|
||||
@@ -42,7 +42,12 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanExpressionContext;
|
||||
import org.springframework.beans.factory.config.BeanExpressionResolver;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.context.expression.StandardBeanExpressionResolver;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
@@ -54,12 +59,16 @@ import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.spel.SpelCompilerMode;
|
||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.annotation.Default;
|
||||
import org.springframework.integration.annotation.Payloads;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.annotation.UseSpelInvoker;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.handler.support.CollectionArgumentResolver;
|
||||
import org.springframework.integration.handler.support.MapArgumentResolver;
|
||||
@@ -114,11 +123,22 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
// Number of times to try an InvocableHandlerMethod before giving up in favor of an expression.
|
||||
private static final int FAILED_ATTEMPTS_THRESHOLD = 100;
|
||||
|
||||
private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
|
||||
private static final ExpressionParser EXPRESSION_PARSER_DEFAULT = EXPRESSION_PARSER;
|
||||
|
||||
private static final ExpressionParser EXPRESSION_PARSER_OFF = new SpelExpressionParser(
|
||||
new SpelParserConfiguration(SpelCompilerMode.OFF, null));
|
||||
|
||||
private static final ExpressionParser EXPRESSION_PARSER_IMMEDIATE = new SpelExpressionParser(
|
||||
new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null));
|
||||
|
||||
private static final ExpressionParser EXPRESSION_PARSER_MIXED = new SpelExpressionParser(
|
||||
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
|
||||
|
||||
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER =
|
||||
new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
private static final Map<SpelCompilerMode, ExpressionParser> SPEL_COMPILERS = new HashMap<>();
|
||||
|
||||
private static final TypeDescriptor messageTypeDescriptor = TypeDescriptor.valueOf(Message.class);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -129,6 +149,11 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
|
||||
private static final TypeDescriptor messageArrayTypeDescriptor = TypeDescriptor.valueOf(Message[].class);
|
||||
|
||||
static {
|
||||
SPEL_COMPILERS.put(SpelCompilerMode.OFF, EXPRESSION_PARSER_OFF);
|
||||
SPEL_COMPILERS.put(SpelCompilerMode.IMMEDIATE, EXPRESSION_PARSER_IMMEDIATE);
|
||||
SPEL_COMPILERS.put(SpelCompilerMode.MIXED, EXPRESSION_PARSER_MIXED);
|
||||
}
|
||||
|
||||
private final DefaultMessageHandlerMethodFactory messageHandlerMethodFactory =
|
||||
new DefaultMessageHandlerMethodFactory();
|
||||
@@ -163,6 +188,11 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
|
||||
private HandlerMethod defaultHandlerMethod;
|
||||
|
||||
private BeanExpressionResolver resolver = new StandardBeanExpressionResolver();
|
||||
|
||||
private BeanExpressionContext expressionContext;
|
||||
|
||||
|
||||
public MessagingMethodInvokerHelper(Object targetObject, Method method, Class<?> expectedType,
|
||||
boolean canProcessMessageList) {
|
||||
this(targetObject, null, method, expectedType, canProcessMessageList);
|
||||
@@ -205,6 +235,14 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
super.setBeanFactory(beanFactory);
|
||||
this.messageHandlerMethodFactory.setBeanFactory(beanFactory);
|
||||
if (beanFactory instanceof ConfigurableListableBeanFactory) {
|
||||
BeanExpressionResolver beanExpressionResolver = ((ConfigurableListableBeanFactory) beanFactory)
|
||||
.getBeanExpressionResolver();
|
||||
if (beanExpressionResolver != null) {
|
||||
this.resolver = beanExpressionResolver;
|
||||
}
|
||||
this.expressionContext = new BeanExpressionContext((ConfigurableListableBeanFactory) beanFactory, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -276,6 +314,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
this.messageHandlerMethodFactory.createInvocableHandlerMethod(targetObject, method);
|
||||
this.handlerMethod = new HandlerMethod(invocableHandlerMethod, canProcessMessageList);
|
||||
this.defaultHandlerMethod = null;
|
||||
checkSpelInvokerRequired(getTargetClass(targetObject), method, this.handlerMethod);
|
||||
}
|
||||
catch (IneligibleMethodException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
@@ -381,51 +420,16 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
@SuppressWarnings("unchecked")
|
||||
private T processInternal(ParametersWrapper parameters) throws Exception {
|
||||
if (!this.initialized) {
|
||||
synchronized (this) {
|
||||
if (!this.initialized) {
|
||||
PayloadExpressionArgumentResolver payloadExpressionArgumentResolver =
|
||||
new PayloadExpressionArgumentResolver();
|
||||
payloadExpressionArgumentResolver.setBeanFactory(getBeanFactory());
|
||||
|
||||
PayloadsArgumentResolver payloadsArgumentResolver = new PayloadsArgumentResolver();
|
||||
payloadsArgumentResolver.setBeanFactory(getBeanFactory());
|
||||
|
||||
CollectionArgumentResolver collectionArgumentResolver =
|
||||
new CollectionArgumentResolver(this.canProcessMessageList);
|
||||
collectionArgumentResolver.setBeanFactory(getBeanFactory());
|
||||
|
||||
MapArgumentResolver mapArgumentResolver = new MapArgumentResolver();
|
||||
mapArgumentResolver.setBeanFactory(getBeanFactory());
|
||||
|
||||
List<HandlerMethodArgumentResolver> customArgumentResolvers = new LinkedList<>();
|
||||
customArgumentResolvers.add(payloadExpressionArgumentResolver);
|
||||
customArgumentResolvers.add(payloadsArgumentResolver);
|
||||
customArgumentResolvers.add(collectionArgumentResolver);
|
||||
customArgumentResolvers.add(mapArgumentResolver);
|
||||
|
||||
this.messageHandlerMethodFactory.setCustomArgumentResolvers(customArgumentResolvers);
|
||||
|
||||
if (getBeanFactory() != null &&
|
||||
getBeanFactory()
|
||||
.containsBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) {
|
||||
this.messageHandlerMethodFactory
|
||||
.setMessageConverter(getBeanFactory()
|
||||
.getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME,
|
||||
MessageConverter.class));
|
||||
}
|
||||
|
||||
this.messageHandlerMethodFactory.afterPropertiesSet();
|
||||
prepareEvaluationContext();
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
initialize();
|
||||
}
|
||||
|
||||
HandlerMethod candidate = findHandlerMethodForParameters(parameters);
|
||||
HandlerMethod candidate = this.findHandlerMethodForParameters(parameters);
|
||||
if (candidate == null) {
|
||||
candidate = this.defaultHandlerMethod;
|
||||
}
|
||||
Assert.notNull(candidate, "No candidate methods found for messages.");
|
||||
if (!candidate.initialized) {
|
||||
initializeHandler(candidate);
|
||||
}
|
||||
Expression expression = candidate.expression;
|
||||
|
||||
T result;
|
||||
@@ -446,6 +450,61 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeHandler(HandlerMethod candidate) {
|
||||
ExpressionParser parser;
|
||||
if (candidate.useSpelInvoker == null) {
|
||||
parser = EXPRESSION_PARSER_DEFAULT;
|
||||
}
|
||||
else {
|
||||
String compilerMode = resolveExpression(candidate.useSpelInvoker.compilerMode(),
|
||||
"UseSpelInvoker.compilerMode:").toUpperCase();
|
||||
parser = !StringUtils.hasText(compilerMode)
|
||||
? EXPRESSION_PARSER_DEFAULT
|
||||
: SPEL_COMPILERS.get(SpelCompilerMode.valueOf(compilerMode));
|
||||
}
|
||||
candidate.expression = parser.parseExpression(candidate.expressionString);
|
||||
candidate.initialized = true;
|
||||
}
|
||||
|
||||
private synchronized void initialize() throws Exception {
|
||||
if (!this.initialized) {
|
||||
PayloadExpressionArgumentResolver payloadExpressionArgumentResolver =
|
||||
new PayloadExpressionArgumentResolver();
|
||||
payloadExpressionArgumentResolver.setBeanFactory(getBeanFactory());
|
||||
|
||||
PayloadsArgumentResolver payloadsArgumentResolver = new PayloadsArgumentResolver();
|
||||
payloadsArgumentResolver.setBeanFactory(getBeanFactory());
|
||||
|
||||
CollectionArgumentResolver collectionArgumentResolver =
|
||||
new CollectionArgumentResolver(this.canProcessMessageList);
|
||||
collectionArgumentResolver.setBeanFactory(getBeanFactory());
|
||||
|
||||
MapArgumentResolver mapArgumentResolver = new MapArgumentResolver();
|
||||
mapArgumentResolver.setBeanFactory(getBeanFactory());
|
||||
|
||||
List<HandlerMethodArgumentResolver> customArgumentResolvers = new LinkedList<>();
|
||||
customArgumentResolvers.add(payloadExpressionArgumentResolver);
|
||||
customArgumentResolvers.add(payloadsArgumentResolver);
|
||||
customArgumentResolvers.add(collectionArgumentResolver);
|
||||
customArgumentResolvers.add(mapArgumentResolver);
|
||||
|
||||
this.messageHandlerMethodFactory.setCustomArgumentResolvers(customArgumentResolvers);
|
||||
|
||||
if (getBeanFactory() != null &&
|
||||
getBeanFactory()
|
||||
.containsBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) {
|
||||
this.messageHandlerMethodFactory
|
||||
.setMessageConverter(getBeanFactory()
|
||||
.getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME,
|
||||
MessageConverter.class));
|
||||
}
|
||||
|
||||
this.messageHandlerMethodFactory.afterPropertiesSet();
|
||||
prepareEvaluationContext();
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private T invokeHandlerMethod(HandlerMethod handlerMethod, ParametersWrapper parameters) throws Exception {
|
||||
try {
|
||||
@@ -550,6 +609,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
InvocableHandlerMethod invocableHandlerMethod =
|
||||
this.messageHandlerMethodFactory.createInvocableHandlerMethod(targetObject, method1);
|
||||
handlerMethod1 = new HandlerMethod(invocableHandlerMethod, this.canProcessMessageList);
|
||||
checkSpelInvokerRequired(targetClass, method1, handlerMethod1);
|
||||
}
|
||||
catch (IneligibleMethodException e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
@@ -657,6 +717,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
this.messageHandlerMethodFactory.createInvocableHandlerMethod(targetObject,
|
||||
method);
|
||||
HandlerMethod handlerMethod = new HandlerMethod(invocableHandlerMethod, this.canProcessMessageList);
|
||||
checkSpelInvokerRequired(targetClass, method, handlerMethod);
|
||||
handlerMethods.put(CANDIDATE_METHODS, Collections.singletonMap(Object.class, handlerMethod));
|
||||
handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods);
|
||||
return handlerMethods;
|
||||
@@ -685,6 +746,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
Map<Class<?>, HandlerMethod> candidateMethods) {
|
||||
if (AopUtils.isAopProxy(targetObject)) {
|
||||
final AtomicReference<Method> targetMethod = new AtomicReference<>();
|
||||
final AtomicReference<Class<?>> targetClass = new AtomicReference<>();
|
||||
Class<?>[] interfaces = ((Advised) targetObject).getProxiedInterfaces();
|
||||
for (Class<?> clazz : interfaces) {
|
||||
ReflectionUtils.doWithMethods(clazz, method1 -> {
|
||||
@@ -693,6 +755,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
}
|
||||
else {
|
||||
targetMethod.set(method1);
|
||||
targetClass.set(clazz);
|
||||
}
|
||||
}, method12 -> method12.getName().equals(methodName));
|
||||
}
|
||||
@@ -702,6 +765,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
InvocableHandlerMethod invocableHandlerMethod =
|
||||
this.messageHandlerMethodFactory.createInvocableHandlerMethod(targetObject, method);
|
||||
HandlerMethod handlerMethod = new HandlerMethod(invocableHandlerMethod, this.canProcessMessageList);
|
||||
checkSpelInvokerRequired(targetClass.get(), method, handlerMethod);
|
||||
Class<?> targetParameterType = handlerMethod.getTargetParameterType();
|
||||
if (handlerMethod.isMessageMethod()) {
|
||||
if (candidateMessageMethods.containsKey(targetParameterType)) {
|
||||
@@ -727,6 +791,37 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSpelInvokerRequired(final Class<?> targetClass, Method methodArg, HandlerMethod handlerMethod) {
|
||||
Method method = AopUtils.getMostSpecificMethod(methodArg, targetClass);
|
||||
UseSpelInvoker useSpel = AnnotationUtils.findAnnotation(method, UseSpelInvoker.class);
|
||||
if (useSpel == null) {
|
||||
useSpel = AnnotationUtils.findAnnotation(targetClass, UseSpelInvoker.class);
|
||||
}
|
||||
if (useSpel != null) {
|
||||
handlerMethod.spelOnly = true;
|
||||
handlerMethod.useSpelInvoker = useSpel;
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveExpression(String value, String msg) {
|
||||
String resolvedValue = resolve(value);
|
||||
|
||||
if (!(resolvedValue.startsWith("#{") && value.endsWith("}"))) {
|
||||
return resolvedValue;
|
||||
}
|
||||
|
||||
Object evaluated = this.resolver.evaluate(resolvedValue, this.expressionContext);
|
||||
Assert.isInstanceOf(String.class, evaluated, msg);
|
||||
return (String) evaluated;
|
||||
}
|
||||
|
||||
private String resolve(String value) {
|
||||
if (getBeanFactory() != null && getBeanFactory() instanceof ConfigurableBeanFactory) {
|
||||
return ((ConfigurableBeanFactory) getBeanFactory()).resolveEmbeddedValue(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private Class<?> getTargetClass(Object targetObject) {
|
||||
Class<?> targetClass = targetObject.getClass();
|
||||
if (AopUtils.isAopProxy(targetObject)) {
|
||||
@@ -806,12 +901,14 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
*/
|
||||
private static class HandlerMethod {
|
||||
|
||||
private final Expression expression;
|
||||
private final String expressionString;
|
||||
|
||||
private final InvocableHandlerMethod invocableHandlerMethod;
|
||||
|
||||
private final boolean canProcessMessageList;
|
||||
|
||||
private volatile Expression expression;
|
||||
|
||||
private volatile TypeDescriptor targetParameterTypeDescriptor;
|
||||
|
||||
private volatile Class<?> targetParameterType = Void.class;
|
||||
@@ -820,6 +917,10 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
|
||||
private volatile boolean spelOnly;
|
||||
|
||||
private volatile UseSpelInvoker useSpelInvoker;
|
||||
|
||||
private volatile boolean initialized;
|
||||
|
||||
// The number of times InvocableHandlerMethod was attempted and failed - enables us to eventually
|
||||
// give up trying to call it when it just doesn't seem to be possible.
|
||||
// Switching to spelOnly afterwards forever.
|
||||
@@ -828,7 +929,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
HandlerMethod(InvocableHandlerMethod invocableHandlerMethod, boolean canProcessMessageList) {
|
||||
this.invocableHandlerMethod = invocableHandlerMethod;
|
||||
this.canProcessMessageList = canProcessMessageList;
|
||||
this.expression = generateExpression(this.invocableHandlerMethod.getMethod());
|
||||
this.expressionString = generateExpression(this.invocableHandlerMethod.getMethod());
|
||||
}
|
||||
|
||||
|
||||
@@ -854,7 +955,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
return this.invocableHandlerMethod.toString();
|
||||
}
|
||||
|
||||
private Expression generateExpression(Method method) {
|
||||
private String generateExpression(Method method) {
|
||||
StringBuilder sb = new StringBuilder("#target." + method.getName() + "(");
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||
@@ -977,7 +1078,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
|
||||
if (this.targetParameterTypeDescriptor == null) {
|
||||
this.targetParameterTypeDescriptor = TypeDescriptor.valueOf(Void.class);
|
||||
}
|
||||
return EXPRESSION_PARSER.parseExpression(sb.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String determineHeaderExpression(Annotation headerAnnotation, MethodParameter methodParameter) {
|
||||
|
||||
@@ -68,7 +68,7 @@ public class ReleaseStrategyFactoryBeanTests {
|
||||
ReleaseStrategy delegate = factory.getObject();
|
||||
assertThat(delegate, instanceOf(MethodInvokingReleaseStrategy.class));
|
||||
assertThat(TestUtils.getPropertyValue(delegate, "adapter.delegate.targetObject", Bar.class), is(bar));
|
||||
assertThat(TestUtils.getPropertyValue(delegate, "adapter.delegate.handlerMethod.expression.expression"),
|
||||
assertThat(TestUtils.getPropertyValue(delegate, "adapter.delegate.handlerMethod.expressionString"),
|
||||
equalTo("#target.doRelease2(messages)"));
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ public class ReleaseStrategyFactoryBeanTests {
|
||||
ReleaseStrategy delegate = factory.getObject();
|
||||
assertThat(delegate, instanceOf(MethodInvokingReleaseStrategy.class));
|
||||
assertThat(TestUtils.getPropertyValue(delegate, "adapter.delegate.targetObject", Baz.class), is(baz));
|
||||
assertThat(TestUtils.getPropertyValue(delegate, "adapter.delegate.handlerMethod.expression.expression"),
|
||||
assertThat(TestUtils.getPropertyValue(delegate, "adapter.delegate.handlerMethod.expressionString"),
|
||||
equalTo("#target.doRelease2(messages)"));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.integration.configuration;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -38,6 +39,7 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -81,6 +83,7 @@ import org.springframework.integration.annotation.Publisher;
|
||||
import org.springframework.integration.annotation.Role;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.annotation.Transformer;
|
||||
import org.springframework.integration.annotation.UseSpelInvoker;
|
||||
import org.springframework.integration.channel.AbstractMessageChannel;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.NullChannel;
|
||||
@@ -435,8 +438,23 @@ public class EnableIntegrationTests {
|
||||
@Test
|
||||
public void testMessagingGateway() throws InterruptedException {
|
||||
String payload = "bar";
|
||||
assertEquals(payload.toUpperCase(), this.testGateway.echo(payload));
|
||||
assertEquals(payload.toUpperCase() + "2", this.testGateway2.echo2(payload));
|
||||
String result = this.testGateway.echo(payload);
|
||||
assertEquals(payload.toUpperCase(), result.substring(0, payload.length()));
|
||||
assertThat(result, containsString("InvocableHandlerMethod"));
|
||||
assertThat(result, not(containsString("SpelExpression")));
|
||||
result = this.testGateway2.echo2(payload);
|
||||
assertNotNull(result);
|
||||
assertEquals(payload.toUpperCase() + "2", result.substring(0, payload.length() + 1));
|
||||
assertThat(result, not(containsString("InvocableHandlerMethod")));
|
||||
assertThat(result, containsString("SpelExpression"));
|
||||
assertThat(result, containsString("CompoundExpression.getValueInternal"));
|
||||
assertNotNull(this.testGateway2.echo2("baz"));
|
||||
result = this.testGateway2.echo2("baz"); // third one should be compiled
|
||||
assertNotNull(result);
|
||||
assertEquals("BAZ2", result.substring(0, 4));
|
||||
assertThat(result, not(containsString("InvocableHandlerMethod")));
|
||||
assertThat(result, containsString("SpelExpression"));
|
||||
assertThat(result, containsString("Ex2.getValue("));
|
||||
this.testGateway.sendAsync("foo");
|
||||
assertTrue(this.asyncAnnotationProcessLatch.await(1, TimeUnit.SECONDS));
|
||||
assertNotSame(Thread.currentThread(), this.asyncAnnotationProcessThread.get());
|
||||
@@ -1230,17 +1248,18 @@ public class EnableIntegrationTests {
|
||||
assertEquals("FOO", message.getHeaders().get("foo"));
|
||||
assertTrue(message.getHeaders().containsKey("calledMethod"));
|
||||
assertEquals("echo", message.getHeaders().get("calledMethod"));
|
||||
return this.handle(message.getPayload());
|
||||
return this.handle(message.getPayload()) + Arrays.asList(new Throwable().getStackTrace()).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transformer(inputChannel = "gatewayChannel2")
|
||||
@UseSpelInvoker(compilerMode = "${xxxxxxxx:IMMEDIATE}")
|
||||
public String transform2(Message<String> message) {
|
||||
assertTrue(message.getHeaders().containsKey("foo"));
|
||||
assertEquals("FOO", message.getHeaders().get("foo"));
|
||||
assertTrue(message.getHeaders().containsKey("calledMethod"));
|
||||
assertEquals("echo2", message.getHeaders().get("calledMethod"));
|
||||
return this.handle(message.getPayload()) + "2";
|
||||
return this.handle(message.getPayload()) + "2" + Arrays.asList(new Throwable().getStackTrace()).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,6 +25,8 @@ import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@@ -49,10 +51,12 @@ import org.junit.rules.ExpectedException;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.expression.spel.SpelCompilerMode;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.annotation.UseSpelInvoker;
|
||||
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
|
||||
import org.springframework.integration.gateway.RequestReplyExchanger;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
@@ -798,6 +802,69 @@ public class MethodInvokingMessageProcessorTests {
|
||||
assertTrue(adviceCalled.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseSpelInvoker() throws Exception {
|
||||
UseSpelInvokerBean bean = new UseSpelInvokerBean();
|
||||
MessagingMethodInvokerHelper<?> helper = new MessagingMethodInvokerHelper<>(bean,
|
||||
UseSpelInvokerBean.class.getDeclaredMethod("foo", String.class), false);
|
||||
Message<?> message = new GenericMessage<>("Test");
|
||||
helper.process(message);
|
||||
assertEquals(SpelCompilerMode.OFF,
|
||||
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
|
||||
|
||||
helper = new MessagingMethodInvokerHelper<>(bean,
|
||||
UseSpelInvokerBean.class.getDeclaredMethod("bar", String.class), false);
|
||||
helper.process(message);
|
||||
assertEquals(SpelCompilerMode.IMMEDIATE,
|
||||
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
|
||||
|
||||
helper = new MessagingMethodInvokerHelper<>(bean,
|
||||
UseSpelInvokerBean.class.getDeclaredMethod("baz", String.class), false);
|
||||
helper.process(message);
|
||||
assertEquals(SpelCompilerMode.MIXED,
|
||||
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
|
||||
|
||||
helper = new MessagingMethodInvokerHelper<>(bean,
|
||||
UseSpelInvokerBean.class.getDeclaredMethod("qux", String.class), false);
|
||||
helper.process(message);
|
||||
assertEquals(SpelCompilerMode.OFF,
|
||||
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
|
||||
|
||||
helper = new MessagingMethodInvokerHelper<>(bean,
|
||||
UseSpelInvokerBean.class.getDeclaredMethod("fiz", String.class), false);
|
||||
try {
|
||||
helper.process(message);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(),
|
||||
equalTo("No enum constant org.springframework.expression.spel.SpelCompilerMode.JUNK"));
|
||||
}
|
||||
|
||||
helper = new MessagingMethodInvokerHelper<>(bean,
|
||||
UseSpelInvokerBean.class.getDeclaredMethod("buz", String.class), false);
|
||||
ConfigurableListableBeanFactory bf = mock(ConfigurableListableBeanFactory.class);
|
||||
willAnswer(i -> i.getArgument(0)).given(bf).resolveEmbeddedValue(anyString());
|
||||
helper.setBeanFactory(bf);
|
||||
try {
|
||||
helper.process(message);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), equalTo(
|
||||
"UseSpelInvoker.compilerMode: Object of class [java.lang.Object] "
|
||||
+ "must be an instance of class java.lang.String"));
|
||||
}
|
||||
|
||||
// Check other CTORs
|
||||
helper = new MessagingMethodInvokerHelper<>(bean, "bar", false);
|
||||
helper.process(message);
|
||||
assertEquals(SpelCompilerMode.IMMEDIATE,
|
||||
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
|
||||
|
||||
helper = new MessagingMethodInvokerHelper<>(bean, ServiceActivator.class, false);
|
||||
helper.process(message);
|
||||
assertEquals(SpelCompilerMode.MIXED,
|
||||
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
|
||||
}
|
||||
|
||||
private DirectFieldAccessor compileImmediate(MethodInvokingMessageProcessor processor) {
|
||||
// Update the parser configuration compiler mode
|
||||
@@ -863,9 +930,9 @@ public class MethodInvokingMessageProcessorTests {
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static final class CheckedException extends Exception {
|
||||
private static final class CheckedException extends Exception {
|
||||
|
||||
public CheckedException(String string) {
|
||||
CheckedException(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
@@ -1040,6 +1107,48 @@ public class MethodInvokingMessageProcessorTests {
|
||||
|
||||
}
|
||||
|
||||
private static class UseSpelInvokerBean {
|
||||
|
||||
UseSpelInvokerBean() {
|
||||
super();
|
||||
}
|
||||
|
||||
@UseSpelInvoker
|
||||
public void foo(String foo) {
|
||||
// empty
|
||||
}
|
||||
|
||||
@UseSpelInvoker("IMMEDIATE")
|
||||
public void bar(String bar) {
|
||||
// empty
|
||||
}
|
||||
|
||||
@ServiceActivator
|
||||
@UseSpelInvoker("mixed")
|
||||
public void baz(String baz) {
|
||||
// empty
|
||||
}
|
||||
|
||||
@UseSpelInvoker("OfF")
|
||||
public void qux(String qux) {
|
||||
// empty
|
||||
}
|
||||
|
||||
@UseSpelInvoker("JUNK")
|
||||
public void fiz(String fiz) {
|
||||
// empty
|
||||
}
|
||||
|
||||
@UseSpelInvoker("#{new Object()}")
|
||||
public void buz(String buz) {
|
||||
// empty
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Public for SpEL access.
|
||||
*/
|
||||
public static class DotBean {
|
||||
|
||||
private final String foo = "bar";
|
||||
|
||||
@@ -262,6 +262,7 @@ Also see <<annotations>> for more information about Messaging Annotations.
|
||||
=== Programming Considerations
|
||||
|
||||
It is generally recommended that you use plain old java objects (POJOs) whenever possible and only expose the framework in your code when absolutely necessary.
|
||||
See <<pojo-invocation>> for more information.
|
||||
|
||||
If you do expose the framework to your classes, there are some considerations that need to be taken into account, especially during application startup; some of these are listed here.
|
||||
|
||||
@@ -387,3 +388,69 @@ The sending thread returns immediately; the reply is sent asynchronously; uses '
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
----
|
||||
|
||||
[[pojo-invocation]]
|
||||
=== POJO Method invocation
|
||||
|
||||
As discussed in <<programming-considerations>>, it is generally recommended to use a POJO programming style.
|
||||
For example,
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@ServiceActivator
|
||||
public String myService(String payload) { ... }
|
||||
----
|
||||
|
||||
In this case, the framework will extract a String payload, invoke your method, and wrap the result in a message to send to the next component in the flow (the original headers will be copied to the new message).
|
||||
In fact, if you are using XML configuration, you don't even need the `@ServiceActivator` annotation:
|
||||
|
||||
[source, xml]
|
||||
----
|
||||
<int:service-activator ... ref="myPojo" method="myService" />
|
||||
----
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public String myService(String payload) { ... }
|
||||
----
|
||||
|
||||
You can omit the `method` attribute as long as there is no ambiguity in the public methods on the class.
|
||||
|
||||
Some further observations:
|
||||
|
||||
You can obtain header information in your POJO methods:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@ServiceActivator
|
||||
public String myService(@Payload String payload, @Header("foo") String fooHeader) { ... }
|
||||
----
|
||||
|
||||
You can dereference properties on the message:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@ServiceActivator
|
||||
public String myService(@Payload("payload.foo") String foo, @Header("bar.baz") String barbaz) { ... }
|
||||
----
|
||||
|
||||
Because many any varied POJO method invocations are available, versions prior to _5.0_ used SpEL to invoke the POJO methods.
|
||||
SpEL (even interpreted) is usually "fast enough" for these operations, when compared to the actual work usually done in the methods.
|
||||
However, starting with _version 5.0_, the `org.springframework.messaging.handler.invocation.InvocableHandlerMethod` is used by default, when possible.
|
||||
This technique is usually faster to execute than interpreted SpEL and is consistent with other Spring messaging projects.
|
||||
The `InvocableHandlerMethod` is similar to the technique used to invoke controller methods in Spring MVC.
|
||||
There are certain methods that are still always invoked using SpEL; examples include annotated parameters with dereferenced properties as discussed above.
|
||||
This is because SpEL has the capability to navigate a property path.
|
||||
|
||||
There may be some other corner cases that we haven't considered that also won't work with `InvocableHandlerMethod` s.
|
||||
For this reason, we automatically fall-back to using SpEL in those cases.
|
||||
|
||||
If you wish, you can also set up your POJO method such that it always uses SpEL, with the `UseSpelInvoker` annotation:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@UseSpelInvoker(compilerMode = "IMMEDIATE")
|
||||
public void bar(String bar) { ... }
|
||||
----
|
||||
|
||||
If the `compilerMode` property is omitted, the `spring.expression.compiler.mode` system property will determine the compiler mode - see http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/expressions.html#expressions-spel-compilation[SpEL compilation] for more information about compiled SpEL.
|
||||
|
||||
@@ -42,7 +42,10 @@ See <<tx-handle-message-advice>> for more information.
|
||||
The `SmartLifecycleRoleController` now provides methods to obtain status of endpoints in roles.
|
||||
See <<endpoint-roles>> for more information.
|
||||
|
||||
When targeting POJO objects as message handlers, one of the service methods can now be marked with the `@Default` annotation to provide a fallback mechanism for non-matched conditions.
|
||||
POJO methods are now invoked using an `InvocableHandlerMethod` by default, but can be configured to use SpEL as before.
|
||||
See <<pojo-invocation>> for more information.
|
||||
|
||||
When targeting POJO methods as message handlers, one of the service methods can now be marked with the `@Default` annotation to provide a fallback mechanism for non-matched conditions.
|
||||
See <<service-activator-namespace>> for more information.
|
||||
|
||||
==== JMS Changes
|
||||
|
||||
Reference in New Issue
Block a user