diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Payload.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Payload.java
index 311776ef2f..db69ece453 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Payload.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Payload.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -51,7 +51,11 @@ public @interface Payload {
*
This attribute may or may not be supported depending on whether the message being
* handled contains a non-primitive Object as its payload or is in serialized form and
* requires message conversion.
- *
When processing STOMP over WebSocket messages this attribute is not supported.
+ *
This attribute is not supported for:
+ *
+ *
STOMP over WebSocket messages
+ *
RSocket interface client
+ *
* @since 4.2
*/
@AliasFor("value")
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java
index e20a3dce5b..1762536eec 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java
@@ -102,6 +102,12 @@ final class DefaultRSocketRequester implements RSocketRequester {
return this.metadataMimeType;
}
+ @Override
+ public RSocketStrategies strategies() {
+ return this.strategies;
+ }
+
+
@Override
public RequestSpec route(String route, Object... vars) {
return new DefaultRequestSpec(route, vars);
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java
index 80b18ec170..84475176be 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java
@@ -83,6 +83,11 @@ public interface RSocketRequester extends Disposable {
*/
MimeType metadataMimeType();
+ /**
+ * Return the configured {@link RSocketStrategies}.
+ */
+ RSocketStrategies strategies();
+
/**
* Begin to specify a new request with the given route to a remote handler.
*
The route can be a template with placeholders, e.g.
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/DestinationVariableArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/DestinationVariableArgumentResolver.java
new file mode 100644
index 0000000000..6cc9cabc6e
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/DestinationVariableArgumentResolver.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2022 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
+ *
+ * https://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.rsocket.service;
+
+import java.util.Collection;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.lang.Nullable;
+import org.springframework.messaging.handler.annotation.DestinationVariable;
+
+/**
+ * {@link RSocketServiceArgumentResolver} for a
+ * {@link DestinationVariable @DestinationVariable} annotated argument.
+ *
+ *
The argument is treated as a single route variable, or in case of a
+ * Collection or an array, as multiple route variables.
+ *
+ * @author Rossen Stoyanchev
+ * @since 6.0
+ */
+public class DestinationVariableArgumentResolver implements RSocketServiceArgumentResolver {
+
+ @Override
+ public boolean resolve(
+ @Nullable Object argument, MethodParameter parameter, RSocketRequestValues.Builder requestValues) {
+
+ DestinationVariable annot = parameter.getParameterAnnotation(DestinationVariable.class);
+ if (annot == null) {
+ return false;
+ }
+
+ if (argument != null) {
+ if (argument instanceof Collection) {
+ ((Collection>) argument).forEach(requestValues::addRouteVariable);
+ return true;
+ }
+ else if (argument.getClass().isArray()) {
+ for (Object variable : (Object[]) argument) {
+ requestValues.addRouteVariable(variable);
+ }
+ return true;
+ }
+ else {
+ requestValues.addRouteVariable(argument);
+ }
+ }
+
+ return true;
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/MetadataArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/MetadataArgumentResolver.java
new file mode 100644
index 0000000000..3e89f95bed
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/MetadataArgumentResolver.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2022 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
+ *
+ * https://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.rsocket.service;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.MimeType;
+
+/**
+ * {@link RSocketServiceArgumentResolver} for metadata entries.
+ *
+ *
Supports a sequence of an {@link Object} parameter for the metadata value,
+ * followed by a {@link MimeType} parameter for the metadata mime type.
+ *
+ *
This should be ordered last to give other, more specific resolvers a
+ * chance to resolve the argument.
+ *
+ * @author Rossen Stoyanchev
+ * @since 6.0
+ */
+public class MetadataArgumentResolver implements RSocketServiceArgumentResolver {
+
+ @Override
+ public boolean resolve(
+ @Nullable Object argument, MethodParameter parameter, RSocketRequestValues.Builder requestValues) {
+
+ int index = parameter.getParameterIndex();
+ Class>[] paramTypes = parameter.getExecutable().getParameterTypes();
+
+ if (parameter.getParameterType().equals(MimeType.class)) {
+ Assert.notNull(argument, "MimeType parameter is required");
+ Assert.state(index > 0, "MimeType parameter should have preceding metadata object parameter");
+ requestValues.addMimeType((MimeType) argument);
+ return true;
+ }
+
+ if (paramTypes.length > (index + 1) && MimeType.class.equals(paramTypes[index + 1])) {
+ Assert.notNull(argument, "MimeType parameter is required");
+ requestValues.addMetadata(argument);
+ return true;
+ }
+
+ return false;
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/PayloadArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/PayloadArgumentResolver.java
new file mode 100644
index 0000000000..8511f675f2
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/PayloadArgumentResolver.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2022 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
+ *
+ * https://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.rsocket.service;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.core.ReactiveAdapter;
+import org.springframework.core.ReactiveAdapterRegistry;
+import org.springframework.lang.Nullable;
+import org.springframework.messaging.handler.annotation.Payload;
+import org.springframework.util.Assert;
+
+/**
+ * {@link RSocketServiceArgumentResolver} for {@link Payload @Payload}
+ * annotated arguments.
+ *
+ * @author Rossen Stoyanchev
+ * @since 6.0
+ */
+public class PayloadArgumentResolver implements RSocketServiceArgumentResolver {
+
+ private final ReactiveAdapterRegistry reactiveAdapterRegistry;
+
+ private final boolean useDefaultResolution;
+
+
+ public PayloadArgumentResolver(ReactiveAdapterRegistry reactiveAdapterRegistry, boolean useDefaultResolution) {
+ this.useDefaultResolution = useDefaultResolution;
+ Assert.notNull(reactiveAdapterRegistry, "ReactiveAdapterRegistry is required");
+ this.reactiveAdapterRegistry = reactiveAdapterRegistry;
+ }
+
+
+ @Override
+ public boolean resolve(
+ @Nullable Object argument, MethodParameter parameter, RSocketRequestValues.Builder requestValues) {
+
+ Payload annot = parameter.getParameterAnnotation(Payload.class);
+ if (annot == null && !this.useDefaultResolution) {
+ return false;
+ }
+
+ if (argument != null) {
+ ReactiveAdapter reactiveAdapter = this.reactiveAdapterRegistry.getAdapter(parameter.getParameterType());
+ if (reactiveAdapter == null) {
+ requestValues.setPayloadValue(argument);
+ }
+ else {
+ MethodParameter nestedParameter = parameter.nested();
+
+ String message = "Async type for @Payload should produce value(s)";
+ Assert.isTrue(nestedParameter.getNestedParameterType() != Void.class, message);
+ Assert.isTrue(!reactiveAdapter.isNoValue(), message);
+
+ requestValues.setPayload(
+ reactiveAdapter.toPublisher(argument),
+ ParameterizedTypeReference.forType(nestedParameter.getNestedGenericParameterType()));
+ }
+ }
+
+ return true;
+ }
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/RSocketExchange.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/RSocketExchange.java
new file mode 100644
index 0000000000..115d7bd294
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/RSocketExchange.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2022 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
+ *
+ * https://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.rsocket.service;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to declare a method on an RSocket service interface as an RSocket
+ * endpoint. The endpoint route is defined statically through the annotation
+ * attributes, and through the input method argument types.
+ *
+ *
Supported at the type level to express common attributes, to be inherited
+ * by all methods, such as a base route.
+ *
+ *
{@link Object} argument followed by {@link org.springframework.util.MimeType} argument
+ *
Add a metadata value
+ *
{@link MetadataArgumentResolver}
+ *
+ *
+ *
{@link org.springframework.util.MimeType} argument preceded by {@link Object} argument
+ *
Specify the mime type for the preceding metadata value
+ *
{@link MetadataArgumentResolver}
+ *
+ *
+ *
+ * @author Rossen Stoyanchev
+ * @since 6.0
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RSocketExchange {
+
+ /**
+ * Destination-based mapping expressed by this annotation. This is either
+ * {@link org.springframework.util.AntPathMatcher AntPathMatcher} or
+ * {@link org.springframework.web.util.pattern.PathPattern PathPattern}
+ * based pattern, depending on which is configured, matched to the route of
+ * the stream request.
+ */
+ String value() default "";
+
+}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/RSocketRequestValues.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/RSocketRequestValues.java
new file mode 100644
index 0000000000..4ea1e38d9e
--- /dev/null
+++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/service/RSocketRequestValues.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2002-2022 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
+ *
+ * https://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.rsocket.service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.reactivestreams.Publisher;
+
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.MimeType;
+import org.springframework.util.StringUtils;
+
+/**
+ * Container for RSocket request values extracted from an
+ * {@link RSocketExchange @RSocketExchange}-annotated
+ * method and argument values passed to it. This is then used to define a request
+ * via {@link org.springframework.messaging.rsocket.RSocketRequester}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 6.0
+ */
+public final class RSocketRequestValues {
+
+ @Nullable
+ private final String route;
+
+ private final Object[] routeVariables;
+
+ private final Map