diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageExceptionHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageExceptionHandler.java
index 81a1c403e8..42588c0975 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageExceptionHandler.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageExceptionHandler.java
@@ -22,6 +22,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import org.springframework.aot.hint.annotation.Reflective;
+
/**
* Annotation for handling exceptions thrown from message-handling methods within a
* specific handler class.
@@ -32,6 +34,7 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
+@Reflective(MessageMappingReflectiveProcessor.class)
public @interface MessageExceptionHandler {
/**
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java
index cdfed1ff7c..825921fd9a 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java
@@ -30,23 +30,28 @@ import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.messaging.support.MessageHeaderAccessor;
/**
- * {@link ReflectiveProcessor} implementation for {@link MessageMapping}
- * annotated types. In addition to registering reflection hints for invoking
+ * {@link ReflectiveProcessor} implementation for types annotated
+ * with {@link MessageMapping @MessageMapping},
+ * {@link SubscribeMapping @SubscribeMapping}
+ * and {@link MessageExceptionHandler @MessageExceptionHandler}.
+ * In addition to registering reflection hints for invoking
* the annotated method, this implementation handles:
*
*
* - Return types
* - Parameters identified as potential payloads
* - {@link Message} parameters
+ * - Exception classes specified via {@link MessageExceptionHandler @MessageExceptionHandler}
*
*
* @author Sebastien Deleuze
* @since 6.0
*/
-class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
+public class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
@@ -58,6 +63,9 @@ class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
}
else if (element instanceof Method method) {
registerMethodHints(hints, method);
+ if (element.isAnnotationPresent(MessageExceptionHandler.class)) {
+ registerMessageExceptionHandlerHints(hints, element.getAnnotation(MessageExceptionHandler.class));
+ }
}
}
@@ -84,6 +92,12 @@ class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
}
}
+ protected void registerMessageExceptionHandlerHints(ReflectionHints hints, MessageExceptionHandler annotation) {
+ for (Class> exceptionClass : annotation.value()) {
+ hints.registerType(exceptionClass);
+ }
+ }
+
protected boolean couldBePayload(MethodParameter methodParameter) {
return !methodParameter.hasParameterAnnotation(DestinationVariable.class) &&
!methodParameter.hasParameterAnnotation(Header.class) &&
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SubscribeMapping.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SubscribeMapping.java
index 1714115f24..bfa891a1f2 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SubscribeMapping.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SubscribeMapping.java
@@ -22,6 +22,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import org.springframework.aot.hint.annotation.Reflective;
+import org.springframework.messaging.handler.annotation.MessageMappingReflectiveProcessor;
+
/**
* Annotation for mapping subscription messages onto specific handler methods based
* on the destination of a subscription. Supported with STOMP over WebSocket only
@@ -54,6 +57,7 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
+@Reflective(MessageMappingReflectiveProcessor.class)
public @interface SubscribeMapping {
/**
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessorTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessorTests.java
index fbb6d72d66..bc3d435c70 100644
--- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessorTests.java
+++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessorTests.java
@@ -16,16 +16,19 @@
package org.springframework.messaging.handler.annotation;
+import java.io.IOException;
import java.lang.reflect.Method;
import java.security.Principal;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
-import org.springframework.aot.hint.ReflectionHints;
+import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
+import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.messaging.support.MessageHeaderAccessor;
import static org.assertj.core.api.Assertions.assertThat;
@@ -39,13 +42,13 @@ public class MessageMappingReflectiveProcessorTests {
private final MessageMappingReflectiveProcessor processor = new MessageMappingReflectiveProcessor();
- private final ReflectionHints hints = new ReflectionHints();
+ private final RuntimeHints hints = new RuntimeHints();
@Test
void registerReflectiveHintsForMethodWithReturnValue() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("returnValue");
- processor.registerReflectionHints(hints, method);
- assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
+ processor.registerReflectionHints(hints.reflection(), method);
+ assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(OutgoingMessage.class));
@@ -62,8 +65,8 @@ public class MessageMappingReflectiveProcessorTests {
@Test
void registerReflectiveHintsForMethodWithExplicitPayload() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("explicitPayload", IncomingMessage.class);
- processor.registerReflectionHints(hints, method);
- assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
+ processor.registerReflectionHints(hints.reflection(), method);
+ assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class));
@@ -80,8 +83,8 @@ public class MessageMappingReflectiveProcessorTests {
@Test
void registerReflectiveHintsForMethodWithImplicitPayload() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("implicitPayload", IncomingMessage.class);
- processor.registerReflectionHints(hints, method);
- assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
+ processor.registerReflectionHints(hints.reflection(), method);
+ assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
@@ -90,8 +93,8 @@ public class MessageMappingReflectiveProcessorTests {
@Test
void registerReflectiveHintsForMethodWithMessage() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("message", Message.class);
- processor.registerReflectionHints(hints, method);
- assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
+ processor.registerReflectionHints(hints.reflection(), method);
+ assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
@@ -102,8 +105,8 @@ public class MessageMappingReflectiveProcessorTests {
Method method = SampleController.class.getDeclaredMethod("implicitPayloadWithIgnoredAnnotations",
IncomingMessage.class, Ignored.class, Ignored.class, Ignored.class, MessageHeaders.class,
MessageHeaderAccessor.class, Principal.class);
- processor.registerReflectionHints(hints, method);
- assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
+ processor.registerReflectionHints(hints.reflection(), method);
+ assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
@@ -111,11 +114,26 @@ public class MessageMappingReflectiveProcessorTests {
@Test
void registerReflectiveHintsForClass() {
- processor.registerReflectionHints(hints, SampleAnnotatedController.class);
- assertThat(hints.typeHints()).singleElement().satisfies(
+ processor.registerReflectionHints(hints.reflection(), SampleAnnotatedController.class);
+ assertThat(hints.reflection().typeHints()).singleElement().satisfies(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleAnnotatedController.class)));
}
+ @Test
+ void registerReflectiveHintsForMethodWithSubscribeMapping() throws NoSuchMethodException {
+ Method method = SampleController.class.getDeclaredMethod("handleSubscribe");
+ processor.registerReflectionHints(hints.reflection(), method);
+ assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleController.class, "handleSubscribe")).accepts(hints);
+ }
+
+ @Test
+ void registerReflectiveHintsForMethodWithMessageExceptionHandler() throws NoSuchMethodException {
+ Method method = SampleController.class.getDeclaredMethod("handleIOException");
+ processor.registerReflectionHints(hints.reflection(), method);
+ assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleController.class, "handleIOException")).accepts(hints);
+ assertThat(RuntimeHintsPredicates.reflection().onType(IOException.class)).accepts(hints);
+ }
+
static class SampleController {
@@ -145,6 +163,16 @@ public class MessageMappingReflectiveProcessorTests {
MessageHeaderAccessor messageHeaderAccessor,
Principal principal) {
}
+
+ @SubscribeMapping("/foo")
+ public String handleSubscribe() {
+ return "bar";
+ }
+
+ @MessageExceptionHandler(IOException.class)
+ public void handleIOException() {
+ }
+
}
@MessageMapping