Browse Source

Add native support for @SubscribeMapping and @MessageExceptionHandler

Closes gh-30002
pull/30010/head
Sébastien Deleuze 3 years ago
parent
commit
626a7fc52a
  1. 3
      spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageExceptionHandler.java
  2. 20
      spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java
  3. 4
      spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SubscribeMapping.java
  4. 56
      spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessorTests.java

3
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.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.aot.hint.annotation.Reflective;
/** /**
* Annotation for handling exceptions thrown from message-handling methods within a * Annotation for handling exceptions thrown from message-handling methods within a
* specific handler class. * specific handler class.
@ -32,6 +34,7 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Reflective(MessageMappingReflectiveProcessor.class)
public @interface MessageExceptionHandler { public @interface MessageExceptionHandler {
/** /**

20
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.lang.Nullable;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.messaging.support.MessageHeaderAccessor;
/** /**
* {@link ReflectiveProcessor} implementation for {@link MessageMapping} * {@link ReflectiveProcessor} implementation for types annotated
* annotated types. In addition to registering reflection hints for invoking * 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: * the annotated method, this implementation handles:
* *
* <ul> * <ul>
* <li>Return types</li> * <li>Return types</li>
* <li>Parameters identified as potential payloads</li> * <li>Parameters identified as potential payloads</li>
* <li>{@link Message} parameters</li> * <li>{@link Message} parameters</li>
* <li>Exception classes specified via {@link MessageExceptionHandler @MessageExceptionHandler}</li>
* </ul> * </ul>
* *
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @since 6.0 * @since 6.0
*/ */
class MessageMappingReflectiveProcessor implements ReflectiveProcessor { public class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
@ -58,6 +63,9 @@ class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
} }
else if (element instanceof Method method) { else if (element instanceof Method method) {
registerMethodHints(hints, 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) { protected boolean couldBePayload(MethodParameter methodParameter) {
return !methodParameter.hasParameterAnnotation(DestinationVariable.class) && return !methodParameter.hasParameterAnnotation(DestinationVariable.class) &&
!methodParameter.hasParameterAnnotation(Header.class) && !methodParameter.hasParameterAnnotation(Header.class) &&

4
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.RetentionPolicy;
import java.lang.annotation.Target; 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 * Annotation for mapping subscription messages onto specific handler methods based
* on the destination of a subscription. Supported with STOMP over WebSocket only * on the destination of a subscription. Supported with STOMP over WebSocket only
@ -54,6 +57,7 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Reflective(MessageMappingReflectiveProcessor.class)
public @interface SubscribeMapping { public @interface SubscribeMapping {
/** /**

56
spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessorTests.java

@ -16,16 +16,19 @@
package org.springframework.messaging.handler.annotation; package org.springframework.messaging.handler.annotation;
import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.security.Principal; import java.security.Principal;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory; 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.TypeReference;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.messaging.support.MessageHeaderAccessor;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -39,13 +42,13 @@ public class MessageMappingReflectiveProcessorTests {
private final MessageMappingReflectiveProcessor processor = new MessageMappingReflectiveProcessor(); private final MessageMappingReflectiveProcessor processor = new MessageMappingReflectiveProcessor();
private final ReflectionHints hints = new ReflectionHints(); private final RuntimeHints hints = new RuntimeHints();
@Test @Test
void registerReflectiveHintsForMethodWithReturnValue() throws NoSuchMethodException { void registerReflectiveHintsForMethodWithReturnValue() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("returnValue"); Method method = SampleController.class.getDeclaredMethod("returnValue");
processor.registerReflectionHints(hints, method); processor.registerReflectionHints(hints.reflection(), method);
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder( assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)), typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> { typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(OutgoingMessage.class)); assertThat(typeHint.getType()).isEqualTo(TypeReference.of(OutgoingMessage.class));
@ -62,8 +65,8 @@ public class MessageMappingReflectiveProcessorTests {
@Test @Test
void registerReflectiveHintsForMethodWithExplicitPayload() throws NoSuchMethodException { void registerReflectiveHintsForMethodWithExplicitPayload() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("explicitPayload", IncomingMessage.class); Method method = SampleController.class.getDeclaredMethod("explicitPayload", IncomingMessage.class);
processor.registerReflectionHints(hints, method); processor.registerReflectionHints(hints.reflection(), method);
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder( assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)), typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> { typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class)); assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class));
@ -80,8 +83,8 @@ public class MessageMappingReflectiveProcessorTests {
@Test @Test
void registerReflectiveHintsForMethodWithImplicitPayload() throws NoSuchMethodException { void registerReflectiveHintsForMethodWithImplicitPayload() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("implicitPayload", IncomingMessage.class); Method method = SampleController.class.getDeclaredMethod("implicitPayload", IncomingMessage.class);
processor.registerReflectionHints(hints, method); processor.registerReflectionHints(hints.reflection(), method);
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder( assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)), 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(IncomingMessage.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class))); typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
@ -90,8 +93,8 @@ public class MessageMappingReflectiveProcessorTests {
@Test @Test
void registerReflectiveHintsForMethodWithMessage() throws NoSuchMethodException { void registerReflectiveHintsForMethodWithMessage() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("message", Message.class); Method method = SampleController.class.getDeclaredMethod("message", Message.class);
processor.registerReflectionHints(hints, method); processor.registerReflectionHints(hints.reflection(), method);
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder( assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)), 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(IncomingMessage.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class))); typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
@ -102,8 +105,8 @@ public class MessageMappingReflectiveProcessorTests {
Method method = SampleController.class.getDeclaredMethod("implicitPayloadWithIgnoredAnnotations", Method method = SampleController.class.getDeclaredMethod("implicitPayloadWithIgnoredAnnotations",
IncomingMessage.class, Ignored.class, Ignored.class, Ignored.class, MessageHeaders.class, IncomingMessage.class, Ignored.class, Ignored.class, Ignored.class, MessageHeaders.class,
MessageHeaderAccessor.class, Principal.class); MessageHeaderAccessor.class, Principal.class);
processor.registerReflectionHints(hints, method); processor.registerReflectionHints(hints.reflection(), method);
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder( assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)), 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(IncomingMessage.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class))); typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
@ -111,11 +114,26 @@ public class MessageMappingReflectiveProcessorTests {
@Test @Test
void registerReflectiveHintsForClass() { void registerReflectiveHintsForClass() {
processor.registerReflectionHints(hints, SampleAnnotatedController.class); processor.registerReflectionHints(hints.reflection(), SampleAnnotatedController.class);
assertThat(hints.typeHints()).singleElement().satisfies( assertThat(hints.reflection().typeHints()).singleElement().satisfies(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleAnnotatedController.class))); 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 { static class SampleController {
@ -145,6 +163,16 @@ public class MessageMappingReflectiveProcessorTests {
MessageHeaderAccessor messageHeaderAccessor, MessageHeaderAccessor messageHeaderAccessor,
Principal principal) { Principal principal) {
} }
@SubscribeMapping("/foo")
public String handleSubscribe() {
return "bar";
}
@MessageExceptionHandler(IOException.class)
public void handleIOException() {
}
} }
@MessageMapping @MessageMapping

Loading…
Cancel
Save