diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/DispatcherHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/DispatcherHandler.java index 8b5c676d95b..d5fbe851690 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/DispatcherHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/DispatcherHandler.java @@ -117,35 +117,29 @@ public class DispatcherHandler implements HttpHandler, ApplicationContextAware { logger.debug("Processing " + request.getMethod() + " request for [" + request.getURI() + "]"); } return Flux.fromIterable(this.handlerMappings) - .concatMap(m -> m.getHandler(request)) + .concatMap(mapping -> mapping.getHandler(request)) .next() - .then(handler -> getHandlerAdapter(handler).handle(request, response, handler)) - .then(result -> { - Mono mono = (result.hasError() ? Mono.error(result.getError()) : - handleResult(request, response, result)); - if (result.hasExceptionMapper()) { - return mono.otherwise(ex -> result.getExceptionMapper().apply(ex) - .then(exResult -> handleResult(request, response, exResult))); - } - return mono; - }) + .then(handler -> invokeHandler(request, response, handler)) + .then(result -> handleResult(request, response, result)) .otherwise(ex -> Mono.error(this.errorMapper.apply(ex))); } - protected HandlerAdapter getHandlerAdapter(Object handler) { + private Mono invokeHandler(ServerHttpRequest request, ServerHttpResponse response, Object handler) { for (HandlerAdapter handlerAdapter : this.handlerAdapters) { if (handlerAdapter.supports(handler)) { - return handlerAdapter; + return handlerAdapter.handle(request, response, handler); } } - throw new IllegalStateException("No HandlerAdapter: " + handler); + return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler)); } - protected Mono handleResult(ServerHttpRequest request, ServerHttpResponse response, HandlerResult result) { - return getResultHandler(result).handleResult(request, response, result); + private Mono handleResult(ServerHttpRequest request, ServerHttpResponse response, HandlerResult result) { + return getResultHandler(result).handleResult(request, response, result) + .otherwise(ex -> result.applyExceptionHandler(ex).then(exceptionResult -> + getResultHandler(result).handleResult(request, response, exceptionResult))); } - protected HandlerResultHandler getResultHandler(HandlerResult handlerResult) { + private HandlerResultHandler getResultHandler(HandlerResult handlerResult) { for (HandlerResultHandler resultHandler : resultHandlers) { if (resultHandler.supports(handlerResult)) { return resultHandler; diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerAdapter.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerAdapter.java index b66676d5378..12623033804 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerAdapter.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerAdapter.java @@ -16,17 +16,16 @@ package org.springframework.web.reactive; -import org.reactivestreams.Publisher; +import java.util.function.Function; + import reactor.Mono; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; /** - * Interface that must be implemented for each handler type to handle an HTTP request. - * This interface is used to allow the {@link DispatcherHandler} to be indefinitely - * extensible. The {@code DispatcherHandler} accesses all installed handlers through - * this interface, meaning that it does not contain code specific to any handler type. + * Contract that decouples the {@link DispatcherHandler} from the details of + * invoking a handler and makes it possible to support any handler type. * * @author Rossen Stoyanchev * @author Sebastien Deleuze @@ -34,26 +33,32 @@ import org.springframework.http.server.reactive.ServerHttpResponse; public interface HandlerAdapter { /** - * Given a handler instance, return whether or not this {@code HandlerAdapter} - * can support it. Typical HandlerAdapters will base the decision on the handler - * type. HandlerAdapters will usually only support one handler type each. - *

A typical implementation: - *

{@code - * return (handler instanceof MyHandler); - * } + * Whether this {@code HandlerAdapter} supports the given {@code handler}. + * * @param handler handler object to check - * @return whether or not this object can use the given handler + * @return whether or not the handler is supported */ boolean supports(Object handler); /** - * Use the given handler to handle this request. - * @param request current HTTP request - * @param response current HTTP response - * @param handler handler to use. This object must have previously been passed - * to the {@code supports} method of this interface, which must have - * returned {@code true}. - * @return A {@link Mono} that emits a single {@link HandlerResult} element + * Handle the request with the given handler. + * + *

Implementations are encouraged to handle exceptions resulting from the + * invocation of a handler in order and if necessary to return an alternate + * result that represents an error response. + * + *

Furthermore since an async {@code HandlerResult} may produce an error + * later during result handling implementations are also encouraged to + * {@link HandlerResult#setExceptionHandler(Function) set an exception + * handler} on the {@code HandlerResult} so that may also be applied later + * after result handling. + * + * @param request current request + * @param response current response + * @param handler the selected handler which must have been previously + * checked via {@link #supports(Object)} + * @return {@link Mono} that emits a single {@code HandlerResult} or none if + * the request has been fully handled and doesn't require further handling. */ Mono handle(ServerHttpRequest request, ServerHttpResponse response, Object handler); diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerResult.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerResult.java index ff84c5caa57..1e5660838a6 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerResult.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerResult.java @@ -36,9 +36,7 @@ public class HandlerResult { private final ResolvableType resultType; - private final Throwable error; - - private Function> exceptionMapper; + private Function> exceptionHandler; public HandlerResult(Object handler, Object result, ResolvableType resultType) { @@ -47,16 +45,6 @@ public class HandlerResult { this.handler = handler; this.result = result; this.resultType = resultType; - this.error = null; - } - - public HandlerResult(Object handler, Throwable error) { - Assert.notNull(handler, "'handler' is required"); - Assert.notNull(error, "'error' is required"); - this.handler = handler; - this.result = null; - this.resultType = null; - this.error = error; } @@ -72,38 +60,25 @@ public class HandlerResult { return this.resultType; } - public Throwable getError() { - return this.error; - } - - /** - * Whether handler invocation produced a result or failed with an error. - *

If {@code true} the {@link #getError()} returns the error while - * {@link #getResult()} and {@link #getResultType()} return {@code null} - * and vice versa. - * @return whether this instance contains a result or an error. - */ - public boolean hasError() { - return (this.error != null); - } - /** - * Configure a function for selecting an alternate {@code HandlerResult} in - * case of an {@link #hasError() error result} or in case of an async result - * that results in an error. - * @param function the exception resolving function + * For an async result, failures may occur later during result handling. + * Use this property to configure an exception handler to be invoked if + * result handling fails. + * + * @param function a function to map the the error to an alternative result. + * @return the current instance */ - public HandlerResult setExceptionMapper(Function> function) { - this.exceptionMapper = function; + public HandlerResult setExceptionHandler(Function> function) { + this.exceptionHandler = function; return this; } - public Function> getExceptionMapper() { - return this.exceptionMapper; + public boolean hasExceptionHandler() { + return (this.exceptionHandler != null); } - public boolean hasExceptionMapper() { - return (this.exceptionMapper != null); + public Mono applyExceptionHandler(Throwable ex) { + return (hasExceptionHandler() ? this.exceptionHandler.apply(ex) : Mono.error(ex)); } } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/RequestMappingHandlerAdapter.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/RequestMappingHandlerAdapter.java index a6e67927a4d..b9ebe323ead 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/RequestMappingHandlerAdapter.java @@ -109,17 +109,15 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Initializin Object handler) { HandlerMethod handlerMethod = (HandlerMethod) handler; - InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod); invocable.setHandlerMethodArgumentResolvers(this.argumentResolvers); return invocable.invokeForRequest(request) - .otherwise(ex -> Mono.just(new HandlerResult(handler, ex))) - .map(result -> result.setExceptionMapper( - ex -> mapException(ex, handlerMethod, request, response))); + .map(result -> result.setExceptionHandler(ex -> handleException(ex, handlerMethod, request, response))) + .otherwise(ex -> handleException(ex, handlerMethod, request, response)); } - private Mono mapException(Throwable ex, HandlerMethod handlerMethod, + private Mono handleException(Throwable ex, HandlerMethod handlerMethod, ServerHttpRequest request, ServerHttpResponse response) { if (ex instanceof Exception) {