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:
Gary Russell
2017-03-01 18:09:48 -05:00
committed by Artem Bilan
parent 56708b448c
commit a50d866f81
7 changed files with 427 additions and 54 deletions

View File

@@ -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 "";
}

View File

@@ -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) {

View File

@@ -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)"));
}

View File

@@ -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

View File

@@ -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";

View File

@@ -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.

View File

@@ -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