Browse Source

Handle ResponseStatusException thrown by MVC functional endpoints

Prior to this commit, exceptions thrown by MVC functional handlers would
not be considered by `ExceptionHandlerExceptionResolver`. This means
that common exceptions would not be handled consistently between
annotated and functional handlers. This is true, for example, for all
`ProblemDetails`-related exception handling.

While MVC functional and annotation models are separate concepts,
WebFlux has a different error handling model that processes all
exceptions in a central place.

This commit ensures that `ExceptionHandlerExceptionResolver` considers
exceptions thrown by handlers of type `HandlerFunction<?>` and processes
them accordingly.

Closes gh-32689
pull/32977/head
Brian Clozel 2 years ago
parent
commit
52af43d6d2
  1. 12
      spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java
  2. 17
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java

12
spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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.
@ -22,6 +22,7 @@ import jakarta.servlet.http.HttpServletResponse; @@ -22,6 +22,7 @@ import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.function.HandlerFunction;
/**
* Abstract base class for
@ -34,9 +35,9 @@ import org.springframework.web.servlet.ModelAndView; @@ -34,9 +35,9 @@ import org.springframework.web.servlet.ModelAndView;
public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver {
/**
* Checks if the handler is a {@link HandlerMethod} and then delegates to the
* base class implementation of {@code #shouldApplyTo(HttpServletRequest, Object)}
* passing the bean of the {@code HandlerMethod}. Otherwise returns {@code false}.
* Checks if the handler is a {@link HandlerMethod} or a {@link HandlerFunction}
* and then delegates to the base class implementation of {@code #shouldApplyTo(HttpServletRequest, Object)}
* passing the bean of the {@code HandlerMethod}. Otherwise, returns {@code false}.
*/
@Override
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
@ -47,6 +48,9 @@ public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHan @@ -47,6 +48,9 @@ public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHan
handler = handlerMethod.getBean();
return super.shouldApplyTo(request, handler);
}
else if (handler instanceof HandlerFunction<?> handlerFunction) {
return super.shouldApplyTo(request, handlerFunction);
}
else if (hasGlobalExceptionHandlers() && hasHandlerMappings()) {
return super.shouldApplyTo(request, handler);
}

17
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java

@ -60,6 +60,8 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler; @@ -60,6 +60,8 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.function.HandlerFunction;
import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
@ -82,6 +84,8 @@ import static org.mockito.Mockito.mock; @@ -82,6 +84,8 @@ import static org.mockito.Mockito.mock;
@SuppressWarnings("unused")
class ExceptionHandlerExceptionResolverTests {
//TODO
private static int DEFAULT_RESOLVER_COUNT;
private static int DEFAULT_HANDLER_COUNT;
@ -255,6 +259,19 @@ class ExceptionHandlerExceptionResolverTests { @@ -255,6 +259,19 @@ class ExceptionHandlerExceptionResolverTests {
assertExceptionHandledAsBody(mav, "AnotherTestExceptionResolver: IllegalAccessException");
}
@Test
void resolveExceptionGlobalHandlerForHandlerFunction() throws Exception {
loadConfiguration(MyConfig.class);
IllegalAccessException ex = new IllegalAccessException();
HandlerFunction<ServerResponse> handlerFunction = req -> {
throw new IllegalAccessException();
};
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerFunction, ex);
assertExceptionHandledAsBody(mav, "AnotherTestExceptionResolver: IllegalAccessException");
}
@Test
void resolveExceptionGlobalHandlerOrdered() throws Exception {
loadConfiguration(MyConfig.class);

Loading…
Cancel
Save