Skip to content

Commit 4cd9e2e

Browse files
OlgaMaciaszekrstoyanchev
authored andcommitted
Support @RSocketExchange for annotated responders
See gh-30936
1 parent 376223c commit 4cd9e2e

File tree

3 files changed

+88
-19
lines changed

3 files changed

+88
-19
lines changed

framework-docs/modules/ROOT/pages/rsocket.adoc

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,10 @@ The `spring-messaging` module contains the following:
139139

140140
* xref:rsocket.adoc#rsocket-requester[RSocketRequester] -- fluent API to make requests through an `io.rsocket.RSocket`
141141
with data and metadata encoding/decoding.
142-
* xref:rsocket.adoc#rsocket-annot-responders[Annotated Responders] -- `@MessageMapping` annotated handler methods for
143-
responding.
142+
* xref:rsocket.adoc#rsocket-interface[RSocket Interfaces] -- `@RSocketExchange` annotated
143+
interfaces for making requests.
144+
* xref:rsocket.adoc#rsocket-annot-responders[Annotated Responders] -- `@MessageMapping`
145+
and `@RSocketExchange` annotated handler methods for responding.
144146

145147
The `spring-web` module contains `Encoder` and `Decoder` implementations such as Jackson
146148
CBOR/JSON, and Protobuf that RSocket applications will likely need. It also contains the
@@ -862,6 +864,59 @@ interaction type(s):
862864
|===
863865

864866

867+
[[rsocket-annot-rsocketexchange]]
868+
=== @RSocketExchange
869+
870+
While `@MessageMapping` is only supported for responding, `@RSocketExchange`
871+
can be used both to create an annotated responder
872+
and xref:rsocket.adoc#rsocket-interface[an RSocket Interface] that allows
873+
making requests.
874+
875+
`@RSocketExchange` can be used as follows to create responder methods:
876+
877+
[tabs]
878+
======
879+
Java::
880+
+
881+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
882+
----
883+
@Controller
884+
public class RadarsController {
885+
886+
@RSocketExchange("locate.radars.within")
887+
public Flux<AirportLocation> radars(MapRequest request) {
888+
// ...
889+
}
890+
}
891+
----
892+
893+
Kotlin::
894+
+
895+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
896+
----
897+
@Controller
898+
class RadarsController {
899+
900+
@RSocketExchange("locate.radars.within")
901+
fun radars(request: MapRequest): Flow<AirportLocation> {
902+
// ...
903+
}
904+
}
905+
----
906+
======
907+
908+
`@RSocketExhange` supports a very similar method signature to `@MessageMapping`,
909+
however, since it needs to be suitable both for requester and responder use,
910+
there are slight differences. Notably, while `@MessageMapping` accepts
911+
a `String` array as its `value` parameter, only a single `String` can be passed
912+
as the `value` of `@RSocketExchange`.
913+
914+
When it comes to possible return values and the way we establish supported
915+
RSocket interaction types, it works in the same way as with `@MessageMapping`.
916+
917+
Similarly to `@MessageMapping`, `@RSocketExchange` can also be used at class
918+
level to specify a common prefix for all the method routes within the class.
919+
865920

866921
[[rsocket-annot-connectmapping]]
867922
=== @ConnectMapping
@@ -1026,6 +1081,9 @@ Two, create a proxy that will perform the declared RSocket exchanges:
10261081
RepositoryService service = factory.createClient(RadarService.class);
10271082
----
10281083

1084+
NOTE: Apart from RSocket interface services, `@RSocketExchange` can also
1085+
be used to create xref:rsocket.adoc#rsocket-annot-rsocketexchange[annotated responders].
1086+
10291087

10301088
[[rsocket-interface-method-parameters]]
10311089
=== Method Parameters

spring-messaging/src/main/java/org/springframework/messaging/rsocket/annotation/support/RSocketMessageHandler.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.springframework.messaging.rsocket.RSocketRequester;
5353
import org.springframework.messaging.rsocket.RSocketStrategies;
5454
import org.springframework.messaging.rsocket.annotation.ConnectMapping;
55+
import org.springframework.messaging.rsocket.service.RSocketExchange;
5556
import org.springframework.util.Assert;
5657
import org.springframework.util.MimeType;
5758
import org.springframework.util.MimeTypeUtils;
@@ -60,8 +61,9 @@
6061

6162
/**
6263
* Extension of {@link MessageMappingMessageHandler} for handling RSocket
63-
* requests with {@link ConnectMapping @ConnectMapping} and
64-
* {@link MessageMapping @MessageMapping} methods.
64+
* requests with {@link ConnectMapping @ConnectMapping},
65+
* {@link MessageMapping @MessageMapping}
66+
* and {@link RSocketExchange @RSocketExchange} methods.
6567
*
6668
* <p>For server scenarios this class can be declared as a bean in Spring
6769
* configuration and that would detect {@code @MessageMapping} methods in
@@ -77,13 +79,14 @@
7779
* {@link org.springframework.messaging.rsocket.RSocketRequester.Builder#rsocketConnector
7880
* RSocketRequester.Builder}.
7981
*
80-
* <p>For {@code @MessageMapping} methods, this class automatically determines
81-
* the RSocket interaction type based on the input and output cardinality of the
82-
* method. See the
82+
* <p>For {@code @MessageMapping} and {@code @RSocketExchange} methods,
83+
* this class automatically determines the RSocket interaction type
84+
* based on the input and output cardinality of the method. See the
8385
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#rsocket-annot-responders">
8486
* "Annotated Responders"</a> section of the Spring Framework reference for more details.
8587
*
8688
* @author Rossen Stoyanchev
89+
* @author Olga Maciaszek-Sharma
8790
* @since 5.2
8891
*/
8992
public class RSocketMessageHandler extends MessageMappingMessageHandler {
@@ -322,6 +325,15 @@ protected CompositeMessageCondition getCondition(AnnotatedElement element) {
322325
RSocketFrameTypeMessageCondition.CONNECT_CONDITION,
323326
new DestinationPatternsMessageCondition(patterns, obtainRouteMatcher()));
324327
}
328+
RSocketExchange ann3 = AnnotatedElementUtils.findMergedAnnotation(element, RSocketExchange.class);
329+
if (ann3 != null && StringUtils.hasText(ann3.value())) {
330+
String[] destinations = new String[]{ann3.value()};
331+
return new CompositeMessageCondition(
332+
RSocketFrameTypeMessageCondition.EMPTY_CONDITION,
333+
new DestinationPatternsMessageCondition(processDestinations(destinations),
334+
obtainRouteMatcher())
335+
);
336+
}
325337
return null;
326338
}
327339

@@ -402,7 +414,8 @@ protected void handleNoMatch(@Nullable RouteMatcher.Route destination, Message<?
402414
* connection. Such a method can also start requests to the client but that
403415
* must be done decoupled from handling and from the current thread.
404416
* <p>Subsequent requests on the connection can be handled with
405-
* {@link MessageMapping MessageMapping} methods.
417+
* {@link MessageMapping MessageMapping}
418+
* and {@link RSocketExchange RSocketExchange} methods.
406419
*/
407420
public SocketAcceptor responder() {
408421
return (setupPayload, sendingRSocket) -> {

spring-messaging/src/test/java/org/springframework/messaging/rsocket/service/RSocketServiceIntegrationTests.java

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,7 +33,6 @@
3333
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3434
import org.springframework.context.annotation.Bean;
3535
import org.springframework.context.annotation.Configuration;
36-
import org.springframework.messaging.handler.annotation.MessageMapping;
3736
import org.springframework.messaging.rsocket.RSocketRequester;
3837
import org.springframework.messaging.rsocket.RSocketStrategies;
3938
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
@@ -45,6 +44,7 @@
4544
* Integration tests with RSocket Service client.
4645
*
4746
* @author Rossen Stoyanchev
47+
* @author Olga Maciaszek-Sharma
4848
*/
4949
class RSocketServiceIntegrationTests {
5050

@@ -57,7 +57,7 @@ class RSocketServiceIntegrationTests {
5757

5858
@BeforeAll
5959
@SuppressWarnings("ConstantConditions")
60-
static void setupOnce() throws Exception {
60+
static void setupOnce() {
6161

6262
MimeType metadataMimeType = MimeTypeUtils.parseMimeType(
6363
WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString());
@@ -112,28 +112,26 @@ void echoStream() {
112112
}
113113

114114

115-
@Controller
115+
@RSocketExchange("echo")
116116
interface Service {
117117

118-
@RSocketExchange("echo-async")
118+
@RSocketExchange("async")
119119
Mono<String> echoAsync(String payload);
120120

121-
@RSocketExchange("echo-stream")
121+
@RSocketExchange("stream")
122122
Flux<String> echoStream(String payload);
123123

124124
}
125125

126126

127127
@Controller
128-
static class ServerController {
128+
static class ServerController implements Service {
129129

130-
@MessageMapping("echo-async")
131-
Mono<String> echoAsync(String payload) {
130+
public Mono<String> echoAsync(String payload) {
132131
return Mono.delay(Duration.ofMillis(10)).map(aLong -> payload + " async");
133132
}
134133

135-
@MessageMapping("echo-stream")
136-
Flux<String> echoStream(String payload) {
134+
public Flux<String> echoStream(String payload) {
137135
return Flux.interval(Duration.ofMillis(10)).map(aLong -> payload + " " + aLong);
138136
}
139137

0 commit comments

Comments
 (0)