Skip to content

Commit 20dfedd

Browse files
committed
Auto-configure RSocketRequester.Builder
This commit auto-configures a prototype `RSocketRequester.Builder` bean for building requester instances. This builder is pre-configured with auto-detected `RSocketStrategies` (same as the server side). Closes gh-16280
1 parent 6544d19 commit 20dfedd

File tree

5 files changed

+187
-13
lines changed

5 files changed

+187
-13
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.rsocket;
18+
19+
import io.rsocket.RSocketFactory;
20+
import io.rsocket.transport.netty.server.TcpServerTransport;
21+
import reactor.netty.http.server.HttpServer;
22+
23+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
24+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
26+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.context.annotation.Scope;
30+
import org.springframework.messaging.rsocket.RSocketRequester;
31+
import org.springframework.messaging.rsocket.RSocketStrategies;
32+
33+
/**
34+
* {@link EnableAutoConfiguration Auto-configuration} for
35+
* {@link org.springframework.messaging.rsocket.RSocketRequester}. This auto-configuration
36+
* creates {@link org.springframework.messaging.rsocket.RSocketRequester.Builder}
37+
* prototype beans, as the builders are stateful and should not be reused to build
38+
* requester instances with different configurations.
39+
*
40+
* @author Brian Clozel
41+
* @since 2.2.0
42+
*/
43+
@Configuration(proxyBeanMethods = false)
44+
@ConditionalOnClass({ RSocketRequester.class, RSocketFactory.class, HttpServer.class,
45+
TcpServerTransport.class })
46+
@AutoConfigureAfter(RSocketStrategiesAutoConfiguration.class)
47+
public class RSocketRequesterAutoConfiguration {
48+
49+
@Bean
50+
@Scope("prototype")
51+
@ConditionalOnMissingBean
52+
public RSocketRequester.Builder rsocketRequesterBuilder(
53+
RSocketStrategies strategies) {
54+
return RSocketRequester.builder().rsocketStrategies(strategies);
55+
}
56+
57+
}

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
100100
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
101101
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
102102
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
103+
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
103104
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
104105
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
105106
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.rsocket;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.boot.autoconfigure.AutoConfigurations;
22+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.messaging.rsocket.RSocketRequester;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.mockito.Mockito.mock;
29+
30+
/**
31+
* Tests for {@link RSocketRequesterAutoConfiguration}
32+
*
33+
* @author Brian Clozel
34+
*/
35+
public class RSocketRequesterAutoConfigurationTests {
36+
37+
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
38+
.withConfiguration(
39+
AutoConfigurations.of(RSocketStrategiesAutoConfiguration.class,
40+
RSocketRequesterAutoConfiguration.class));
41+
42+
@Test
43+
public void shouldCreateBuilder() {
44+
this.contextRunner.run((context) -> assertThat(context)
45+
.hasSingleBean(RSocketRequester.Builder.class));
46+
}
47+
48+
@Test
49+
public void shouldGetPrototypeScopedBean() {
50+
this.contextRunner.run((context) -> {
51+
RSocketRequester.Builder first = context
52+
.getBean(RSocketRequester.Builder.class);
53+
RSocketRequester.Builder second = context
54+
.getBean(RSocketRequester.Builder.class);
55+
assertThat(first).isNotEqualTo(second);
56+
});
57+
}
58+
59+
@Test
60+
public void shouldNotCreateBuilderIfAlreadyPresent() {
61+
this.contextRunner.withUserConfiguration(CustomRSocketRequesterBuilder.class)
62+
.run((context) -> {
63+
RSocketRequester.Builder builder = context
64+
.getBean(RSocketRequester.Builder.class);
65+
assertThat(builder).isInstanceOf(MyRSocketRequesterBuilder.class);
66+
});
67+
}
68+
69+
@Configuration(proxyBeanMethods = false)
70+
static class CustomRSocketRequesterBuilder {
71+
72+
@Bean
73+
public MyRSocketRequesterBuilder myRSocketRequesterBuilder() {
74+
return mock(MyRSocketRequesterBuilder.class);
75+
}
76+
77+
}
78+
79+
interface MyRSocketRequesterBuilder extends RSocketRequester.Builder {
80+
81+
}
82+
83+
}

spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3528,6 +3528,42 @@ about customization possibilities.
35283528
Developers can create `RSocketStrategiesCustomizer` beans to add other strategies,
35293529
assuming there are `Encoder` and `Decoder` implementations available.
35303530

3531+
[[boot-features-rsocket-requester]]
3532+
=== Calling RSocket Services with `RSocketRequester`
3533+
3534+
Once the `RSocket` channel is established between server and client, any party can send or
3535+
receive requests to the other.
3536+
3537+
As a server, you can get injected an `RSocketRequester` instance on any handler method of
3538+
an RSocket `@Controller`. As a client, you need to configure and establish an RSocket
3539+
connection first. Spring Boot auto-configures an `RSocketRequester.Builder` for such cases
3540+
with the expected codecs.
3541+
3542+
The `RSocketRequester.Builder` instance is a prototype bean, meaning each injection point
3543+
will provide you with a new instance - this is done on purpose since this builder is stateful
3544+
and you shouldn't create requesters with different setups using the same instance.
3545+
3546+
The following code shows a typical example:
3547+
3548+
[source,java,indent=0]
3549+
----
3550+
@Service
3551+
public class MyService {
3552+
3553+
private final RSocketRequester rsocketRequester;
3554+
3555+
public MyService(RSocketRequester.Builder rsocketRequesterBuilder) {
3556+
this.rsocketRequester = rsocketRequesterBuilder
3557+
.connectTcp("example.org", 9090).block();
3558+
}
3559+
3560+
public Mono<User> someRSocketCall(String name) {
3561+
return this.requester.route("user").data(payload)
3562+
.retrieveMono(User.class);
3563+
}
3564+
3565+
}
3566+
----
35313567

35323568

35333569
[[boot-features-security]]

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/netty/NettyRSocketServerFactoryTests.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import io.rsocket.RSocket;
2828
import io.rsocket.RSocketFactory;
2929
import io.rsocket.SocketAcceptor;
30-
import io.rsocket.transport.netty.client.TcpClientTransport;
3130
import io.rsocket.transport.netty.client.WebsocketClientTransport;
3231
import io.rsocket.util.DefaultPayload;
3332
import org.assertj.core.api.Assertions;
@@ -45,7 +44,6 @@
4544
import org.springframework.core.io.buffer.NettyDataBufferFactory;
4645
import org.springframework.messaging.rsocket.RSocketRequester;
4746
import org.springframework.messaging.rsocket.RSocketStrategies;
48-
import org.springframework.util.MimeTypeUtils;
4947
import org.springframework.util.SocketUtils;
5048

5149
import static org.assertj.core.api.Assertions.assertThat;
@@ -96,7 +94,7 @@ public void specificPort() {
9694
factory.setPort(specificPort);
9795
this.rSocketServer = factory.create(new EchoRequestResponseAcceptor());
9896
this.rSocketServer.start();
99-
this.requester = getRSocketRequester(createRSocketTcpClient());
97+
this.requester = createRSocketTcpClient();
10098
String payload = "test payload";
10199
String response = this.requester.route("test").data(payload)
102100
.retrieveMono(String.class).block(TIMEOUT);
@@ -111,7 +109,7 @@ public void websocketTransport() {
111109
factory.setTransport(RSocketServer.TRANSPORT.WEBSOCKET);
112110
this.rSocketServer = factory.create(new EchoRequestResponseAcceptor());
113111
this.rSocketServer.start();
114-
this.requester = getRSocketRequester(createRSocketWebSocketClient());
112+
this.requester = createRSocketWebSocketClient();
115113
String payload = "test payload";
116114
String response = this.requester.route("test").data(payload)
117115
.retrieveMono(String.class).block(TIMEOUT);
@@ -136,29 +134,28 @@ public void serverCustomizers() {
136134
}
137135
}
138136

139-
private RSocket createRSocketTcpClient() {
137+
private RSocketRequester createRSocketTcpClient() {
140138
Assertions.assertThat(this.rSocketServer).isNotNull();
141139
InetSocketAddress address = this.rSocketServer.address();
142-
return RSocketFactory.connect().dataMimeType(MimeTypeUtils.TEXT_PLAIN_VALUE)
143-
.transport(TcpClientTransport.create(address)).start().block();
140+
return createRSocketRequesterBuilder()
141+
.connectTcp(address.getHostString(), address.getPort()).block();
144142
}
145143

146-
private RSocket createRSocketWebSocketClient() {
144+
private RSocketRequester createRSocketWebSocketClient() {
147145
Assertions.assertThat(this.rSocketServer).isNotNull();
148146
InetSocketAddress address = this.rSocketServer.address();
149-
return RSocketFactory.connect().dataMimeType(MimeTypeUtils.TEXT_PLAIN_VALUE)
150-
.transport(WebsocketClientTransport.create(address)).start().block();
147+
return createRSocketRequesterBuilder()
148+
.connect(WebsocketClientTransport.create(address)).block();
151149
}
152150

153-
private RSocketRequester getRSocketRequester(RSocket rSocketClient) {
151+
private RSocketRequester.Builder createRSocketRequesterBuilder() {
154152
RSocketStrategies strategies = RSocketStrategies.builder()
155153
.decoder(StringDecoder.allMimeTypes())
156154
.encoder(CharSequenceEncoder.allMimeTypes())
157155
.dataBufferFactory(
158156
new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT))
159157
.build();
160-
return RSocketRequester.create(rSocketClient, MimeTypeUtils.TEXT_PLAIN,
161-
strategies);
158+
return RSocketRequester.builder().rsocketStrategies(strategies);
162159
}
163160

164161
static class EchoRequestResponseAcceptor implements SocketAcceptor {

0 commit comments

Comments
 (0)