Skip to content

Commit b10262c

Browse files
committed
Merge pull request #45360 from dmitrysulman
* pr/47287: Polish "Register controller advices to RSocket messaging" Register controller advices to RSocket messaging Closes gh-45360
2 parents 7613024 + 485180d commit b10262c

File tree

3 files changed

+124
-1
lines changed

3 files changed

+124
-1
lines changed

documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/rsocket.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ Spring Boot will auto-configure the Spring Messaging infrastructure for RSocket.
6969

7070
This means that Spring Boot will create a javadoc:org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler[] bean that will handle RSocket requests to your application.
7171

72+
TIP: You can use {url-spring-framework-docs}/web/webmvc/mvc-controller/ann-advice.html[`@ControllerAdvice`] to handle exceptions.
73+
7274

7375

7476
[[messaging.rsocket.requester]]

module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.java

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,29 @@
1717
package org.springframework.boot.rsocket.autoconfigure;
1818

1919
import io.rsocket.transport.netty.server.TcpServerTransport;
20+
import org.jspecify.annotations.Nullable;
2021

2122
import org.springframework.beans.factory.ObjectProvider;
2223
import org.springframework.boot.autoconfigure.AutoConfiguration;
2324
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2425
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2526
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
27+
import org.springframework.context.ApplicationContext;
2628
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.messaging.handler.MessagingAdviceBean;
2731
import org.springframework.messaging.rsocket.RSocketRequester;
2832
import org.springframework.messaging.rsocket.RSocketStrategies;
2933
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
34+
import org.springframework.web.method.ControllerAdviceBean;
3035

3136
/**
3237
* {@link EnableAutoConfiguration Auto-configuration} for Spring RSocket support in Spring
3338
* Messaging.
3439
*
3540
* @author Brian Clozel
41+
* @author Dmitry Sulman
42+
* @author Stephane Nicoll
3643
* @since 4.0.0
3744
*/
3845
@AutoConfiguration(after = RSocketStrategiesAutoConfiguration.class)
@@ -42,11 +49,71 @@ public final class RSocketMessagingAutoConfiguration {
4249
@Bean
4350
@ConditionalOnMissingBean
4451
RSocketMessageHandler messageHandler(RSocketStrategies rSocketStrategies,
45-
ObjectProvider<RSocketMessageHandlerCustomizer> customizers) {
52+
ObjectProvider<RSocketMessageHandlerCustomizer> customizers, ApplicationContext context) {
4653
RSocketMessageHandler messageHandler = new RSocketMessageHandler();
4754
messageHandler.setRSocketStrategies(rSocketStrategies);
4855
customizers.orderedStream().forEach((customizer) -> customizer.customize(messageHandler));
56+
4957
return messageHandler;
5058
}
5159

60+
@Configuration(proxyBeanMethods = false)
61+
@ConditionalOnClass(ControllerAdviceBean.class)
62+
static class MessagingAdviceConfiguration {
63+
64+
@Bean
65+
MessagingAdviceRSocketMessageHandlerCustomizer messagingAdviceRSocketMessageHandlerCustomizer(
66+
ApplicationContext applicationContext) {
67+
return new MessagingAdviceRSocketMessageHandlerCustomizer(applicationContext);
68+
}
69+
70+
}
71+
72+
static final class MessagingAdviceRSocketMessageHandlerCustomizer implements RSocketMessageHandlerCustomizer {
73+
74+
private final ApplicationContext applicationContext;
75+
76+
MessagingAdviceRSocketMessageHandlerCustomizer(ApplicationContext applicationContext) {
77+
this.applicationContext = applicationContext;
78+
}
79+
80+
@Override
81+
public void customize(RSocketMessageHandler messageHandler) {
82+
ControllerAdviceBean.findAnnotatedBeans(this.applicationContext)
83+
.forEach((controllerAdviceBean) -> messageHandler
84+
.registerMessagingAdvice(new ControllerAdviceBeanWrapper(controllerAdviceBean)));
85+
}
86+
87+
}
88+
89+
private static final class ControllerAdviceBeanWrapper implements MessagingAdviceBean {
90+
91+
private final ControllerAdviceBean adviceBean;
92+
93+
private ControllerAdviceBeanWrapper(ControllerAdviceBean adviceBean) {
94+
this.adviceBean = adviceBean;
95+
}
96+
97+
@Override
98+
public @Nullable Class<?> getBeanType() {
99+
return this.adviceBean.getBeanType();
100+
}
101+
102+
@Override
103+
public Object resolveBean() {
104+
return this.adviceBean.resolveBean();
105+
}
106+
107+
@Override
108+
public boolean isApplicableToBeanType(Class<?> beanType) {
109+
return this.adviceBean.isApplicableToBeanType(beanType);
110+
}
111+
112+
@Override
113+
public int getOrder() {
114+
return this.adviceBean.getOrder();
115+
}
116+
117+
}
118+
52119
}

module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfigurationTests.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,30 @@
1616

1717
package org.springframework.boot.rsocket.autoconfigure;
1818

19+
import io.rsocket.frame.FrameType;
1920
import org.junit.jupiter.api.Test;
21+
import reactor.core.publisher.Mono;
22+
import reactor.test.StepVerifier;
2023

2124
import org.springframework.boot.autoconfigure.AutoConfigurations;
2225
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2326
import org.springframework.context.annotation.Bean;
2427
import org.springframework.context.annotation.Configuration;
2528
import org.springframework.core.codec.CharSequenceEncoder;
2629
import org.springframework.core.codec.StringDecoder;
30+
import org.springframework.messaging.Message;
31+
import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
32+
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
33+
import org.springframework.messaging.handler.annotation.MessageMapping;
2734
import org.springframework.messaging.rsocket.RSocketStrategies;
35+
import org.springframework.messaging.rsocket.annotation.support.RSocketFrameTypeMessageCondition;
2836
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
37+
import org.springframework.messaging.support.MessageBuilder;
38+
import org.springframework.messaging.support.MessageHeaderAccessor;
39+
import org.springframework.stereotype.Controller;
2940
import org.springframework.util.MimeType;
41+
import org.springframework.util.RouteMatcher;
42+
import org.springframework.web.bind.annotation.ControllerAdvice;
3043

3144
import static org.assertj.core.api.Assertions.assertThat;
3245

@@ -72,6 +85,21 @@ void shouldApplyMessageHandlerCustomizers() {
7285
});
7386
}
7487

88+
@Test
89+
void shouldRegisterControllerAdvice() {
90+
this.contextRunner.withBean(TestControllerAdvice.class).withBean(TestController.class).run((context) -> {
91+
RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class);
92+
MessageHeaderAccessor headers = new MessageHeaderAccessor();
93+
RouteMatcher.Route route = handler.getRouteMatcher().parseRoute("exception");
94+
headers.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, route);
95+
headers.setHeader(RSocketFrameTypeMessageCondition.FRAME_TYPE_HEADER, FrameType.REQUEST_FNF);
96+
Message<?> message = MessageBuilder.createMessage(Mono.empty(), headers.getMessageHeaders());
97+
98+
StepVerifier.create(handler.handleMessage(message)).expectComplete().verify();
99+
assertThat(context.getBean(TestControllerAdvice.class).isExceptionHandled()).isTrue();
100+
});
101+
}
102+
75103
@Configuration(proxyBeanMethods = false)
76104
static class BaseConfiguration {
77105

@@ -111,4 +139,30 @@ RSocketMessageHandlerCustomizer customizer() {
111139

112140
}
113141

142+
@Controller
143+
static final class TestController {
144+
145+
@MessageMapping("exception")
146+
void handleWithSimulatedException() {
147+
throw new IllegalStateException("simulated exception");
148+
}
149+
150+
}
151+
152+
@ControllerAdvice
153+
static final class TestControllerAdvice {
154+
155+
boolean exceptionHandled;
156+
157+
boolean isExceptionHandled() {
158+
return this.exceptionHandled;
159+
}
160+
161+
@MessageExceptionHandler
162+
void handleException(IllegalStateException ex) {
163+
this.exceptionHandled = true;
164+
}
165+
166+
}
167+
114168
}

0 commit comments

Comments
 (0)