From 94fce6f6890f8f281c014ce19ae6c4a720e8ef91 Mon Sep 17 00:00:00 2001 From: Dmitry Sulman Date: Sat, 20 Sep 2025 20:14:45 +0300 Subject: [PATCH 1/2] Register controller advices to RSocket messaging See gh-45360 Signed-off-by: Dmitry Sulman --- .../RSocketMessagingAutoConfiguration.java | 40 +++++++++++++- ...SocketMessagingAutoConfigurationTests.java | 55 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.java b/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.java index b62408a7634..400d7404d42 100644 --- a/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.java +++ b/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.java @@ -17,22 +17,27 @@ package org.springframework.boot.rsocket.autoconfigure; import io.rsocket.transport.netty.server.TcpServerTransport; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.messaging.handler.MessagingAdviceBean; import org.springframework.messaging.rsocket.RSocketRequester; import org.springframework.messaging.rsocket.RSocketStrategies; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; +import org.springframework.web.method.ControllerAdviceBean; /** * {@link EnableAutoConfiguration Auto-configuration} for Spring RSocket support in Spring * Messaging. * * @author Brian Clozel + * @author Dmitry Sulman * @since 4.0.0 */ @AutoConfiguration(after = RSocketStrategiesAutoConfiguration.class) @@ -42,11 +47,44 @@ public final class RSocketMessagingAutoConfiguration { @Bean @ConditionalOnMissingBean RSocketMessageHandler messageHandler(RSocketStrategies rSocketStrategies, - ObjectProvider customizers) { + ObjectProvider customizers, ApplicationContext context) { RSocketMessageHandler messageHandler = new RSocketMessageHandler(); messageHandler.setRSocketStrategies(rSocketStrategies); customizers.orderedStream().forEach((customizer) -> customizer.customize(messageHandler)); + ControllerAdviceBean.findAnnotatedBeans(context) + .forEach((controllerAdviceBean) -> messageHandler + .registerMessagingAdvice(new ControllerAdviceBeanWrapper(controllerAdviceBean))); return messageHandler; } + private static final class ControllerAdviceBeanWrapper implements MessagingAdviceBean { + + private final ControllerAdviceBean adviceBean; + + private ControllerAdviceBeanWrapper(ControllerAdviceBean adviceBean) { + this.adviceBean = adviceBean; + } + + @Override + public @Nullable Class getBeanType() { + return this.adviceBean.getBeanType(); + } + + @Override + public Object resolveBean() { + return this.adviceBean.resolveBean(); + } + + @Override + public boolean isApplicableToBeanType(Class beanType) { + return this.adviceBean.isApplicableToBeanType(beanType); + } + + @Override + public int getOrder() { + return this.adviceBean.getOrder(); + } + + } + } diff --git a/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfigurationTests.java b/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfigurationTests.java index 5a04bc07c7f..18610f7244e 100644 --- a/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfigurationTests.java +++ b/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfigurationTests.java @@ -16,7 +16,10 @@ package org.springframework.boot.rsocket.autoconfigure; +import io.rsocket.frame.FrameType; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -24,9 +27,19 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.core.codec.StringDecoder; +import org.springframework.messaging.Message; +import org.springframework.messaging.handler.DestinationPatternsMessageCondition; +import org.springframework.messaging.handler.annotation.MessageExceptionHandler; +import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.rsocket.RSocketStrategies; +import org.springframework.messaging.rsocket.annotation.support.RSocketFrameTypeMessageCondition; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.messaging.support.MessageHeaderAccessor; +import org.springframework.stereotype.Controller; import org.springframework.util.MimeType; +import org.springframework.util.RouteMatcher; +import org.springframework.web.bind.annotation.ControllerAdvice; import static org.assertj.core.api.Assertions.assertThat; @@ -72,6 +85,22 @@ class RSocketMessagingAutoConfigurationTests { }); } + @Test + void shouldRegisterControllerAdvice() { + this.contextRunner.withBean(TestControllerAdvice.class).withBean(TestController.class).run((context) -> { + RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class); + TestControllerAdvice controllerAdvice = context.getBean(TestControllerAdvice.class); + + MessageHeaderAccessor headers = new MessageHeaderAccessor(); + RouteMatcher.Route route = handler.getRouteMatcher().parseRoute("exception"); + headers.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, route); + headers.setHeader(RSocketFrameTypeMessageCondition.FRAME_TYPE_HEADER, FrameType.REQUEST_FNF); + Message message = MessageBuilder.createMessage(Mono.empty(), headers.getMessageHeaders()); + StepVerifier.create(handler.handleMessage(message)).expectComplete().verify(); + assertThat(controllerAdvice.isExceptionHandled()).isTrue(); + }); + } + @Configuration(proxyBeanMethods = false) static class BaseConfiguration { @@ -111,4 +140,30 @@ class RSocketMessagingAutoConfigurationTests { } + @Controller + static final class TestController { + + @MessageMapping("exception") + void handleWithSimulatedException() { + throw new IllegalStateException("simulated exception"); + } + + } + + @ControllerAdvice + static final class TestControllerAdvice { + + boolean exceptionHandled; + + boolean isExceptionHandled() { + return this.exceptionHandled; + } + + @MessageExceptionHandler + void handleException(IllegalStateException ex) { + this.exceptionHandled = true; + } + + } + } From 485180df4f6c059c09dae443f0799c0d1238aced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Mon, 6 Oct 2025 11:24:51 +0200 Subject: [PATCH 2/2] Polish "Register controller advices to RSocket messaging" See gh-45360 --- .../reference/pages/messaging/rsocket.adoc | 2 ++ .../RSocketMessagingAutoConfiguration.java | 35 +++++++++++++++++-- ...SocketMessagingAutoConfigurationTests.java | 5 ++- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/rsocket.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/rsocket.adoc index 01d739d6212..41d944b3ef1 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/rsocket.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/rsocket.adoc @@ -69,6 +69,8 @@ Spring Boot will auto-configure the Spring Messaging infrastructure for RSocket. 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. +TIP: You can use {url-spring-framework-docs}/web/webmvc/mvc-controller/ann-advice.html[`@ControllerAdvice`] to handle exceptions. + [[messaging.rsocket.requester]] diff --git a/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.java b/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.java index 400d7404d42..6613c2142db 100644 --- a/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.java +++ b/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.java @@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.messaging.handler.MessagingAdviceBean; import org.springframework.messaging.rsocket.RSocketRequester; import org.springframework.messaging.rsocket.RSocketStrategies; @@ -38,6 +39,7 @@ import org.springframework.web.method.ControllerAdviceBean; * * @author Brian Clozel * @author Dmitry Sulman + * @author Stephane Nicoll * @since 4.0.0 */ @AutoConfiguration(after = RSocketStrategiesAutoConfiguration.class) @@ -51,12 +53,39 @@ public final class RSocketMessagingAutoConfiguration { RSocketMessageHandler messageHandler = new RSocketMessageHandler(); messageHandler.setRSocketStrategies(rSocketStrategies); customizers.orderedStream().forEach((customizer) -> customizer.customize(messageHandler)); - ControllerAdviceBean.findAnnotatedBeans(context) - .forEach((controllerAdviceBean) -> messageHandler - .registerMessagingAdvice(new ControllerAdviceBeanWrapper(controllerAdviceBean))); + return messageHandler; } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(ControllerAdviceBean.class) + static class MessagingAdviceConfiguration { + + @Bean + MessagingAdviceRSocketMessageHandlerCustomizer messagingAdviceRSocketMessageHandlerCustomizer( + ApplicationContext applicationContext) { + return new MessagingAdviceRSocketMessageHandlerCustomizer(applicationContext); + } + + } + + static final class MessagingAdviceRSocketMessageHandlerCustomizer implements RSocketMessageHandlerCustomizer { + + private final ApplicationContext applicationContext; + + MessagingAdviceRSocketMessageHandlerCustomizer(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public void customize(RSocketMessageHandler messageHandler) { + ControllerAdviceBean.findAnnotatedBeans(this.applicationContext) + .forEach((controllerAdviceBean) -> messageHandler + .registerMessagingAdvice(new ControllerAdviceBeanWrapper(controllerAdviceBean))); + } + + } + private static final class ControllerAdviceBeanWrapper implements MessagingAdviceBean { private final ControllerAdviceBean adviceBean; diff --git a/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfigurationTests.java b/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfigurationTests.java index 18610f7244e..5576701deb4 100644 --- a/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfigurationTests.java +++ b/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfigurationTests.java @@ -89,15 +89,14 @@ class RSocketMessagingAutoConfigurationTests { void shouldRegisterControllerAdvice() { this.contextRunner.withBean(TestControllerAdvice.class).withBean(TestController.class).run((context) -> { RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class); - TestControllerAdvice controllerAdvice = context.getBean(TestControllerAdvice.class); - MessageHeaderAccessor headers = new MessageHeaderAccessor(); RouteMatcher.Route route = handler.getRouteMatcher().parseRoute("exception"); headers.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, route); headers.setHeader(RSocketFrameTypeMessageCondition.FRAME_TYPE_HEADER, FrameType.REQUEST_FNF); Message message = MessageBuilder.createMessage(Mono.empty(), headers.getMessageHeaders()); + StepVerifier.create(handler.handleMessage(message)).expectComplete().verify(); - assertThat(controllerAdvice.isExceptionHandled()).isTrue(); + assertThat(context.getBean(TestControllerAdvice.class).isExceptionHandled()).isTrue(); }); }