diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/UseSpelInvoker.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/UseSpelInvoker.java
new file mode 100644
index 0000000000..345c6cc79b
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/UseSpelInvoker.java
@@ -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.
+ *
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.
+ *
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).
+ *
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 "";
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java
index 51e1f6c134..5e103202f1 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java
@@ -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 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 SPEL_COMPILERS = new HashMap<>();
+
private static final TypeDescriptor messageTypeDescriptor = TypeDescriptor.valueOf(Message.class);
@SuppressWarnings("unused")
@@ -129,6 +149,11 @@ public class MessagingMethodInvokerHelper 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 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 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 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 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 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 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 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 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 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 extends AbstractExpressionEvaluator
Map, HandlerMethod> candidateMethods) {
if (AopUtils.isAopProxy(targetObject)) {
final AtomicReference targetMethod = new AtomicReference<>();
+ final AtomicReference> targetClass = new AtomicReference<>();
Class>[] interfaces = ((Advised) targetObject).getProxiedInterfaces();
for (Class> clazz : interfaces) {
ReflectionUtils.doWithMethods(clazz, method1 -> {
@@ -693,6 +755,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
}
else {
targetMethod.set(method1);
+ targetClass.set(clazz);
}
}, method12 -> method12.getName().equals(methodName));
}
@@ -702,6 +765,7 @@ public class MessagingMethodInvokerHelper 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 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 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 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 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 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 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) {
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/ReleaseStrategyFactoryBeanTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/ReleaseStrategyFactoryBeanTests.java
index 263787c9ed..a41a7ab351 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/ReleaseStrategyFactoryBeanTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/ReleaseStrategyFactoryBeanTests.java
@@ -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)"));
}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java
index 3b2651b06d..3882da5921 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java
@@ -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 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
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java
index c9058168dc..bcc0e62d84 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java
@@ -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";
diff --git a/src/reference/asciidoc/overview.adoc b/src/reference/asciidoc/overview.adoc
index 010e9224b7..5340ddfd21 100644
--- a/src/reference/asciidoc/overview.adoc
+++ b/src/reference/asciidoc/overview.adoc
@@ -262,6 +262,7 @@ Also see <> 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 <> 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 '
----
+
+[[pojo-invocation]]
+=== POJO Method invocation
+
+As discussed in <>, 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]
+----
+
+----
+
+[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.
diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc
index a08edec72a..08bdbdc32a 100644
--- a/src/reference/asciidoc/whats-new.adoc
+++ b/src/reference/asciidoc/whats-new.adoc
@@ -42,7 +42,10 @@ See <> for more information.
The `SmartLifecycleRoleController` now provides methods to obtain status of endpoints in roles.
See <> 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 <> 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 <> for more information.
==== JMS Changes