@MessageExceptionHandler supports error signal
Before this change if a controller method returned a Publisher whose first signal was an error, the error signal would not be delegated to a @MessageExceptionHandler method as expected. To make this work for now we use a package private local copy of the ChannelSendOperator from spring-web. See gh-21987
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,7 @@ import java.util.function.Consumer;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
@@ -63,7 +64,7 @@ public class MethodMessageHandlerTests {
|
||||
|
||||
assertEquals(5, mappings.keySet().size());
|
||||
assertThat(mappings.keySet(), Matchers.containsInAnyOrder(
|
||||
"/handleMessage", "/handleMessageWithArgument", "/handleMessageAndThrow",
|
||||
"/handleMessage", "/handleMessageWithArgument", "/handleMessageWithError",
|
||||
"/handleMessageMatch1", "/handleMessageMatch2"));
|
||||
}
|
||||
|
||||
@@ -80,7 +81,7 @@ public class MethodMessageHandlerTests {
|
||||
|
||||
handler.handleMessage(message).block(Duration.ofSeconds(5));
|
||||
|
||||
StepVerifier.create((Mono<Object>) handler.getLastReturnValue())
|
||||
StepVerifier.create((Publisher<Object>) handler.getLastReturnValue())
|
||||
.expectNext("handleMessageMatch1")
|
||||
.verifyComplete();
|
||||
}
|
||||
@@ -100,7 +101,7 @@ public class MethodMessageHandlerTests {
|
||||
|
||||
handler.handleMessage(message).block(Duration.ofSeconds(5));
|
||||
|
||||
StepVerifier.create((Mono<Object>) handler.getLastReturnValue())
|
||||
StepVerifier.create((Publisher<Object>) handler.getLastReturnValue())
|
||||
.expectNext("handleMessageWithArgument,payload=foo")
|
||||
.verifyComplete();
|
||||
}
|
||||
@@ -111,11 +112,11 @@ public class MethodMessageHandlerTests {
|
||||
TestMethodMessageHandler handler = initMethodMessageHandler(TestController.class);
|
||||
|
||||
Message<?> message = new GenericMessage<>("body", Collections.singletonMap(
|
||||
DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, "/handleMessageAndThrow"));
|
||||
DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, "/handleMessageWithError"));
|
||||
|
||||
handler.handleMessage(message).block(Duration.ofSeconds(5));
|
||||
|
||||
StepVerifier.create((Mono<Object>) handler.getLastReturnValue())
|
||||
StepVerifier.create((Publisher<Object>) handler.getLastReturnValue())
|
||||
.expectNext("handleIllegalStateException,ex=rejected")
|
||||
.verifyComplete();
|
||||
}
|
||||
@@ -153,7 +154,7 @@ public class MethodMessageHandlerTests {
|
||||
return delay("handleMessageWithArgument,payload=" + payload);
|
||||
}
|
||||
|
||||
public Mono<Void> handleMessageAndThrow() {
|
||||
public Mono<String> handleMessageWithError() {
|
||||
return Mono.delay(Duration.ofMillis(10))
|
||||
.flatMap(aLong -> Mono.error(new IllegalStateException("rejected")));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.messaging.handler.invocation.reactive;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
@@ -43,7 +44,14 @@ public class TestReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Mono<Void> handleReturnValue(@Nullable Object value, MethodParameter returnType, Message<?> message) {
|
||||
return value instanceof Publisher ?
|
||||
new ChannelSendOperator((Publisher) value, this::saveValue) :
|
||||
saveValue(value);
|
||||
}
|
||||
|
||||
private Mono<Void> saveValue(@Nullable Object value) {
|
||||
this.lastReturnValue = value;
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ 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.MessageDeliveryException;
|
||||
import org.springframework.messaging.ReactiveMessageChannel;
|
||||
import org.springframework.messaging.ReactiveSubscribableChannel;
|
||||
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.support.DefaultReactiveMessageChannel;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -170,6 +170,26 @@ public class RSocketClientToServerIntegrationTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWithThrownException() {
|
||||
|
||||
Mono<String> result = requester.route("thrown-exception").data("a").retrieveMono(String.class);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNext("Invalid input error handled")
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWithErrorSignal() {
|
||||
|
||||
Mono<String> result = requester.route("error-signal").data("a").retrieveMono(String.class);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNext("Invalid input error handled")
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noMatchingRoute() {
|
||||
Mono<String> result = requester.route("invalid").data("anything").retrieveMono(String.class);
|
||||
@@ -208,6 +228,20 @@ public class RSocketClientToServerIntegrationTests {
|
||||
return payloads.delayElements(Duration.ofMillis(10)).map(payload -> payload + " async");
|
||||
}
|
||||
|
||||
@MessageMapping("thrown-exception")
|
||||
Mono<String> handleAndThrow(String payload) {
|
||||
throw new IllegalArgumentException("Invalid input error");
|
||||
}
|
||||
|
||||
@MessageMapping("error-signal")
|
||||
Mono<String> handleAndReturnError(String payload) {
|
||||
return Mono.error(new IllegalArgumentException("Invalid input error"));
|
||||
}
|
||||
|
||||
@MessageExceptionHandler
|
||||
Mono<String> handleException(IllegalArgumentException ex) {
|
||||
return Mono.delay(Duration.ofMillis(10)).map(aLong -> ex.getMessage() + " handled");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user