diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/AbstractNamedValueMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/AbstractNamedValueMethodArgumentResolver.java
new file mode 100644
index 0000000000..9184e41f36
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/AbstractNamedValueMethodArgumentResolver.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2002-2019 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.messaging.handler.annotation.support.reactive;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+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.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.lang.Nullable;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.handler.annotation.ValueConstants;
+import org.springframework.messaging.handler.invocation.reactive.SyncHandlerMethodArgumentResolver;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Abstract base class to resolve method arguments from a named value, e.g.
+ * message headers or destination variables. Named values could have one or more
+ * of a name, a required flag, and a default value.
+ *
+ *
Subclasses only need to define specific steps such as how to obtain named
+ * value details from a method parameter, how to resolve to argument values, or
+ * how to handle missing values.
+ *
+ *
A default value string can contain ${...} placeholders and Spring
+ * Expression Language {@code #{...}} expressions which will be resolved if a
+ * {@link ConfigurableBeanFactory} is supplied to the class constructor.
+ *
+ *
A {@link ConversionService} is used to to convert resolved String argument
+ * value to the expected target method parameter type.
+ *
+ * @author Rossen Stoyanchev
+ * @since 5.2
+ */
+public abstract class AbstractNamedValueMethodArgumentResolver implements SyncHandlerMethodArgumentResolver {
+
+ private final ConversionService conversionService;
+
+ @Nullable
+ private final ConfigurableBeanFactory configurableBeanFactory;
+
+ @Nullable
+ private final BeanExpressionContext expressionContext;
+
+ private final Map namedValueInfoCache = new ConcurrentHashMap<>(256);
+
+
+ /**
+ * Constructor with a {@link ConversionService} and a {@link BeanFactory}.
+ * @param conversionService conversion service for converting String values
+ * to the target method parameter type
+ * @param beanFactory a bean factory for resolving {@code ${...}}
+ * placeholders and {@code #{...}} SpEL expressions in default values
+ */
+ protected AbstractNamedValueMethodArgumentResolver(ConversionService conversionService,
+ @Nullable ConfigurableBeanFactory beanFactory) {
+
+ this.conversionService = conversionService;
+ this.configurableBeanFactory = beanFactory;
+ this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, null) : null);
+ }
+
+
+ @Override
+ public Object resolveArgumentValue(MethodParameter parameter, Message> message) {
+
+ NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
+ MethodParameter nestedParameter = parameter.nestedIfOptional();
+
+ Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
+ if (resolvedName == null) {
+ throw new IllegalArgumentException(
+ "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
+ }
+
+ Object arg = resolveArgumentInternal(nestedParameter, message, resolvedName.toString());
+ if (arg == null) {
+ if (namedValueInfo.defaultValue != null) {
+ arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
+ }
+ else if (namedValueInfo.required && !nestedParameter.isOptional()) {
+ handleMissingValue(namedValueInfo.name, nestedParameter, message);
+ }
+ arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
+ }
+ else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
+ arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
+ }
+
+ if (parameter != nestedParameter || !ClassUtils.isAssignableValue(parameter.getParameterType(), arg)) {
+ arg = this.conversionService.convert(arg, TypeDescriptor.forObject(arg), new TypeDescriptor(parameter));
+ }
+
+ return arg;
+ }
+
+ /**
+ * Obtain the named value for the given method parameter.
+ */
+ private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
+ NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
+ if (namedValueInfo == null) {
+ namedValueInfo = createNamedValueInfo(parameter);
+ namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
+ this.namedValueInfoCache.put(parameter, namedValueInfo);
+ }
+ return namedValueInfo;
+ }
+
+ /**
+ * Create the {@link NamedValueInfo} object for the given method parameter.
+ * Implementations typically retrieve the method annotation by means of
+ * {@link MethodParameter#getParameterAnnotation(Class)}.
+ * @param parameter the method parameter
+ * @return the named value information
+ */
+ protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
+
+ /**
+ * Fall back on the parameter name from the class file if necessary and
+ * replace {@link ValueConstants#DEFAULT_NONE} with null.
+ */
+ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
+ String name = info.name;
+ if (info.name.isEmpty()) {
+ name = parameter.getParameterName();
+ if (name == null) {
+ Class> type = parameter.getParameterType();
+ throw new IllegalArgumentException(
+ "Name for argument of type [" + type.getName() + "] not specified, " +
+ "and parameter name information not found in class file either.");
+ }
+ }
+ return new NamedValueInfo(name, info.required,
+ ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
+ }
+
+ /**
+ * Resolve the given annotation-specified value,
+ * potentially containing placeholders and expressions.
+ */
+ @Nullable
+ private Object resolveEmbeddedValuesAndExpressions(String value) {
+ if (this.configurableBeanFactory == null || this.expressionContext == null) {
+ return value;
+ }
+ String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
+ BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
+ if (exprResolver == null) {
+ return value;
+ }
+ return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
+ }
+
+ /**
+ * Resolves the given parameter type and value name into an argument value.
+ * @param parameter the method parameter to resolve to an argument value
+ * @param message the current request
+ * @param name the name of the value being resolved
+ * @return the resolved argument. May be {@code null}
+ */
+ @Nullable
+ protected abstract Object resolveArgumentInternal(MethodParameter parameter, Message> message, String name);
+
+ /**
+ * Invoked when a value is required, but {@link #resolveArgumentInternal}
+ * returned {@code null} and there is no default value. Sub-classes can
+ * throw an appropriate exception for this case.
+ * @param name the name for the value
+ * @param parameter the target method parameter
+ * @param message the message being processed
+ */
+ protected abstract void handleMissingValue(String name, MethodParameter parameter, Message> message);
+
+ /**
+ * One last chance to handle a possible null value.
+ * Specifically for booleans method parameters, use {@link Boolean#FALSE}.
+ * Also raise an ISE for primitive types.
+ */
+ @Nullable
+ private Object handleNullValue(String name, @Nullable Object value, Class> paramType) {
+ if (value == null) {
+ if (Boolean.TYPE.equals(paramType)) {
+ return Boolean.FALSE;
+ }
+ else if (paramType.isPrimitive()) {
+ throw new IllegalStateException("Optional " + paramType + " parameter '" + name +
+ "' is present but cannot be translated into a null value due to being " +
+ "declared as a primitive type. Consider declaring it as object wrapper " +
+ "for the corresponding primitive type.");
+ }
+ }
+ return value;
+ }
+
+
+ /**
+ * Represents a named value declaration.
+ */
+ protected static class NamedValueInfo {
+
+ private final String name;
+
+ private final boolean required;
+
+ @Nullable
+ private final String defaultValue;
+
+ protected NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
+ this.name = name;
+ this.required = required;
+ this.defaultValue = defaultValue;
+ }
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/DestinationVariableMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/DestinationVariableMethodArgumentResolver.java
new file mode 100644
index 0000000000..b8c413072d
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/DestinationVariableMethodArgumentResolver.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2019 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.messaging.handler.annotation.support.reactive;
+
+import java.util.Map;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.lang.Nullable;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHandlingException;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.handler.annotation.DestinationVariable;
+import org.springframework.messaging.handler.annotation.ValueConstants;
+import org.springframework.util.Assert;
+
+/**
+ * Resolve for {@link DestinationVariable @DestinationVariable} method parameters.
+ *
+ * @author Rossen Stoyanchev
+ * @since 5.2
+ */
+public class DestinationVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+ /** The name of the header used to for template variables. */
+ public static final String DESTINATION_TEMPLATE_VARIABLES_HEADER =
+ DestinationVariableMethodArgumentResolver.class.getSimpleName() + ".templateVariables";
+
+
+ public DestinationVariableMethodArgumentResolver(ConversionService conversionService) {
+ super(conversionService, null);
+ }
+
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(DestinationVariable.class);
+ }
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ DestinationVariable annot = parameter.getParameterAnnotation(DestinationVariable.class);
+ Assert.state(annot != null, "No DestinationVariable annotation");
+ return new DestinationVariableNamedValueInfo(annot);
+ }
+
+ @Override
+ @Nullable
+ @SuppressWarnings("unchecked")
+ protected Object resolveArgumentInternal(MethodParameter parameter, Message> message, String name) {
+ MessageHeaders headers = message.getHeaders();
+ Map vars = (Map) headers.get(DESTINATION_TEMPLATE_VARIABLES_HEADER);
+ return vars != null ? vars.get(name) : null;
+ }
+
+ @Override
+ protected void handleMissingValue(String name, MethodParameter parameter, Message> message) {
+ throw new MessageHandlingException(message, "Missing path template variable '" + name + "' " +
+ "for method parameter type [" + parameter.getParameterType() + "]");
+ }
+
+
+ private static final class DestinationVariableNamedValueInfo extends NamedValueInfo {
+
+ private DestinationVariableNamedValueInfo(DestinationVariable annotation) {
+ super(annotation.value(), true, ValueConstants.DEFAULT_NONE);
+ }
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/HeaderMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/HeaderMethodArgumentResolver.java
new file mode 100644
index 0000000000..a50d38e377
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/HeaderMethodArgumentResolver.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2002-2019 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.messaging.handler.annotation.support.reactive;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.lang.Nullable;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHandlingException;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.messaging.support.NativeMessageHeaderAccessor;
+import org.springframework.util.Assert;
+
+/**
+ * Resolver for {@link Header @Header} arguments. Headers are resolved from
+ * either the top-level header map or the nested
+ * {@link NativeMessageHeaderAccessor native} header map.
+ *
+ * @author Rossen Stoyanchev
+ * @since 5.2
+ *
+ * @see HeadersMethodArgumentResolver
+ * @see NativeMessageHeaderAccessor
+ */
+public class HeaderMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
+
+ private static final Log logger = LogFactory.getLog(HeaderMethodArgumentResolver.class);
+
+
+ public HeaderMethodArgumentResolver(
+ ConversionService conversionService, @Nullable ConfigurableBeanFactory beanFactory) {
+
+ super(conversionService, beanFactory);
+ }
+
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.hasParameterAnnotation(Header.class);
+ }
+
+ @Override
+ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
+ Header annot = parameter.getParameterAnnotation(Header.class);
+ Assert.state(annot != null, "No Header annotation");
+ return new HeaderNamedValueInfo(annot);
+ }
+
+ @Override
+ @Nullable
+ protected Object resolveArgumentInternal(MethodParameter parameter, Message> message, String name) {
+
+ Object headerValue = message.getHeaders().get(name);
+ Object nativeHeaderValue = getNativeHeaderValue(message, name);
+
+ if (headerValue != null && nativeHeaderValue != null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("A value was found for '" + name + "', in both the top level header map " +
+ "and also in the nested map for native headers. Using the value from top level map. " +
+ "Use 'nativeHeader.myHeader' to resolve the native header.");
+ }
+ }
+
+ return (headerValue != null ? headerValue : nativeHeaderValue);
+ }
+
+ @Nullable
+ private Object getNativeHeaderValue(Message> message, String name) {
+ Map> nativeHeaders = getNativeHeaders(message);
+ if (name.startsWith("nativeHeaders.")) {
+ name = name.substring("nativeHeaders.".length());
+ }
+ if (nativeHeaders == null || !nativeHeaders.containsKey(name)) {
+ return null;
+ }
+ List> nativeHeaderValues = nativeHeaders.get(name);
+ return (nativeHeaderValues.size() == 1 ? nativeHeaderValues.get(0) : nativeHeaderValues);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private Map> getNativeHeaders(Message> message) {
+ return (Map>) message.getHeaders().get(NativeMessageHeaderAccessor.NATIVE_HEADERS);
+ }
+
+ @Override
+ protected void handleMissingValue(String headerName, MethodParameter parameter, Message> message) {
+ throw new MessageHandlingException(message, "Missing header '" + headerName +
+ "' for method parameter type [" + parameter.getParameterType() + "]");
+ }
+
+
+ private static final class HeaderNamedValueInfo extends NamedValueInfo {
+
+ private HeaderNamedValueInfo(Header annotation) {
+ super(annotation.name(), annotation.required(), annotation.defaultValue());
+ }
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/HeadersMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/HeadersMethodArgumentResolver.java
new file mode 100644
index 0000000000..e2aa858540
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/HeadersMethodArgumentResolver.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2019 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.messaging.handler.annotation.support.reactive;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.lang.Nullable;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.handler.annotation.Headers;
+import org.springframework.messaging.handler.invocation.reactive.SyncHandlerMethodArgumentResolver;
+import org.springframework.messaging.support.MessageHeaderAccessor;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Argument resolver for headers. Resolves the following method parameters:
+ *
+ *
{@link Headers @Headers} {@link Map}
+ *
{@link MessageHeaders}
+ *
{@link MessageHeaderAccessor}
+ *
+ *
+ * @author Rossen Stoyanchev
+ * @since 5.2
+ */
+public class HeadersMethodArgumentResolver implements SyncHandlerMethodArgumentResolver {
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ Class> paramType = parameter.getParameterType();
+ return ((parameter.hasParameterAnnotation(Headers.class) && Map.class.isAssignableFrom(paramType)) ||
+ MessageHeaders.class == paramType || MessageHeaderAccessor.class.isAssignableFrom(paramType));
+ }
+
+ @Override
+ @Nullable
+ public Object resolveArgumentValue(MethodParameter parameter, Message> message) {
+ Class> paramType = parameter.getParameterType();
+ if (Map.class.isAssignableFrom(paramType)) {
+ return message.getHeaders();
+ }
+ else if (MessageHeaderAccessor.class == paramType) {
+ MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class);
+ return accessor != null ? accessor : new MessageHeaderAccessor(message);
+ }
+ else if (MessageHeaderAccessor.class.isAssignableFrom(paramType)) {
+ MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class);
+ if (accessor != null && paramType.isAssignableFrom(accessor.getClass())) {
+ return accessor;
+ }
+ else {
+ Method method = ReflectionUtils.findMethod(paramType, "wrap", Message.class);
+ if (method == null) {
+ throw new IllegalStateException(
+ "Cannot create accessor of type " + paramType + " for message " + message);
+ }
+ return ReflectionUtils.invokeMethod(method, null, message);
+ }
+ }
+ else {
+ throw new IllegalStateException("Unexpected parameter of type " + paramType +
+ " in method " + parameter.getMethod() + ". ");
+ }
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/MessageMappingMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/MessageMappingMessageHandler.java
index 4d8d8ba9d6..25463e70f3 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/MessageMappingMessageHandler.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/reactive/MessageMappingMessageHandler.java
@@ -24,9 +24,14 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.codec.Decoder;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.CompositeMessageCondition;
@@ -73,6 +78,8 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandlerBy default {@link DefaultFormattingConversionService} is used.
+ * @param conversionService the conversion service to use
+ */
+ public void setConversionService(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ /**
+ * Return the configured ConversionService.
+ */
+ public ConversionService getConversionService() {
+ return this.conversionService;
+ }
+
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
@@ -159,6 +183,15 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler initArgumentResolvers() {
List resolvers = new ArrayList<>();
+ ApplicationContext context = getApplicationContext();
+ ConfigurableBeanFactory beanFactory = (context instanceof ConfigurableApplicationContext ?
+ ((ConfigurableApplicationContext) context).getBeanFactory() : null);
+
+ // Annotation-based resolvers
+ resolvers.add(new HeaderMethodArgumentResolver(this.conversionService, beanFactory));
+ resolvers.add(new HeadersMethodArgumentResolver());
+ resolvers.add(new DestinationVariableMethodArgumentResolver(this.conversionService));
+
// Custom resolvers
resolvers.addAll(getArgumentResolverConfigurer().getCustomResolvers());
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/SyncHandlerMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/SyncHandlerMethodArgumentResolver.java
new file mode 100644
index 0000000000..01727c43de
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/reactive/SyncHandlerMethodArgumentResolver.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2019 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.messaging.handler.invocation.reactive;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.lang.Nullable;
+import org.springframework.messaging.Message;
+
+/**
+ * An extension of {@link HandlerMethodArgumentResolver} for implementations
+ * that are synchronous in nature and do not block to resolve values.
+ *
+ * @author Rossen Stoyanchev
+ * @since 5.2
+ */
+public interface SyncHandlerMethodArgumentResolver extends HandlerMethodArgumentResolver {
+
+ /**
+ * {@inheritDoc}
+ *
By default this simply delegates to {@link #resolveArgumentValue} for
+ * synchronous resolution.
+ */
+ @Override
+ default Mono