Spring MVC supports ResponseStatusException
The ResponseStatusException is now also supported in Spring MVC through the ResponseStatusExceptionResolver. Issue: SPR-14895
This commit is contained in:
parent
729551f375
commit
93cfc791a7
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.web.servlet.mvc.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
|
@ -25,6 +26,7 @@ import org.springframework.context.i18n.LocaleContextHolder;
|
|||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
|
||||
|
||||
|
@ -41,6 +43,8 @@ import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
|
|||
* present on cause exceptions, and as of 4.2.2 this resolver supports
|
||||
* attribute overrides for {@code @ResponseStatus} in custom composed annotations.
|
||||
*
|
||||
* <p>As of 5.0 this resolver also supports {@link ResponseStatusException}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
|
@ -62,43 +66,80 @@ public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionRes
|
|||
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler, Exception ex) {
|
||||
|
||||
ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
|
||||
if (responseStatus != null) {
|
||||
try {
|
||||
return resolveResponseStatus(responseStatus, request, response, handler, ex);
|
||||
try {
|
||||
if (ex instanceof ResponseStatusException) {
|
||||
return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
|
||||
}
|
||||
catch (Exception resolveEx) {
|
||||
logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx);
|
||||
|
||||
ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
|
||||
if (status != null) {
|
||||
return resolveResponseStatus(status, request, response, handler, ex);
|
||||
}
|
||||
|
||||
if (ex.getCause() instanceof Exception) {
|
||||
ex = (Exception) ex.getCause();
|
||||
return doResolveException(request, response, handler, ex);
|
||||
}
|
||||
}
|
||||
else if (ex.getCause() instanceof Exception) {
|
||||
ex = (Exception) ex.getCause();
|
||||
return doResolveException(request, response, handler, ex);
|
||||
catch (Exception resolveEx) {
|
||||
logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method that handles {@link ResponseStatus @ResponseStatus} annotation.
|
||||
* <p>The default implementation sends a response error using
|
||||
* {@link HttpServletResponse#sendError(int)} or
|
||||
* {@link HttpServletResponse#sendError(int, String)} if the annotation has a
|
||||
* {@linkplain ResponseStatus#reason() reason} and then returns an empty ModelAndView.
|
||||
* @param responseStatus the annotation
|
||||
* Template method that handles the {@link ResponseStatus @ResponseStatus} annotation.
|
||||
* <p>The default implementation delegates to {@link #applyStatusAndReason}
|
||||
* with the status code and reason from the annotation.
|
||||
* @param responseStatus the {@code @ResponseStatus} annotation
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @param handler the executed handler, or {@code null} if none chosen at the
|
||||
* time of the exception (for example, if multipart resolution failed)
|
||||
* @param ex the exception that got thrown during handler execution or the
|
||||
* exception that has the ResponseStatus annotation if found on the cause.
|
||||
* @return a corresponding ModelAndView to forward to, or {@code null}
|
||||
* for default processing
|
||||
* time of the exception, e.g. if multipart resolution failed
|
||||
* @param ex the exception
|
||||
* @return an empty ModelAndView, i.e. exception resolved
|
||||
*/
|
||||
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
|
||||
HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus,
|
||||
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
|
||||
throws Exception {
|
||||
|
||||
int statusCode = responseStatus.code().value();
|
||||
String reason = responseStatus.reason();
|
||||
return applyStatusAndReason(statusCode, reason, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method that handles an {@link ResponseStatusException}.
|
||||
* <p>The default implementation delegates to {@link #applyStatusAndReason}
|
||||
* with the status code and reason from the exception.
|
||||
* @param ex the exception
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @param handler the executed handler, or {@code null} if none chosen at the
|
||||
* time of the exception, e.g. if multipart resolution failed
|
||||
* @return an empty ModelAndView, i.e. exception resolved
|
||||
* @since 5.0
|
||||
*/
|
||||
protected ModelAndView resolveResponseStatusException(ResponseStatusException ex,
|
||||
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
|
||||
int statusCode = ex.getStatus().value();
|
||||
String reason = ex.getReason();
|
||||
applyStatusAndReason(statusCode, reason, response);
|
||||
return new ModelAndView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the resolved status code and reason to the response.
|
||||
* <p>The default implementation sends a response error using
|
||||
* {@link HttpServletResponse#sendError(int)} or
|
||||
* {@link HttpServletResponse#sendError(int, String)} if there is a reason
|
||||
* and then returns an empty ModelAndView.
|
||||
* @since 5.0
|
||||
*/
|
||||
protected ModelAndView applyStatusAndReason(int statusCode, String reason, HttpServletResponse response)
|
||||
throws IOException {
|
||||
|
||||
if (this.messageSource != null) {
|
||||
reason = this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale());
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.springframework.mock.web.test.MockHttpServletRequest;
|
|||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.tests.sample.beans.ITestBean;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
@ -41,6 +42,7 @@ import static org.junit.Assert.*;
|
|||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Sam Brannen
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class ResponseStatusExceptionResolverTests {
|
||||
|
||||
|
@ -50,40 +52,32 @@ public class ResponseStatusExceptionResolverTests {
|
|||
|
||||
private final MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
exceptionResolver.setWarnLogCategory(exceptionResolver.getClass().getName());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void statusCode() {
|
||||
StatusCodeException ex = new StatusCodeException();
|
||||
ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex);
|
||||
assertNotNull("No ModelAndView returned", mav);
|
||||
assertTrue("No Empty ModelAndView returned", mav.isEmpty());
|
||||
assertEquals("Invalid status code", 400, response.getStatus());
|
||||
assertTrue("Response has not been committed", response.isCommitted());
|
||||
assertResolved(mav, 400, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statusCodeFromComposedResponseStatus() {
|
||||
StatusCodeFromComposedResponseStatusException ex = new StatusCodeFromComposedResponseStatusException();
|
||||
ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex);
|
||||
assertNotNull("No ModelAndView returned", mav);
|
||||
assertTrue("No Empty ModelAndView returned", mav.isEmpty());
|
||||
assertEquals("Invalid status code", 400, response.getStatus());
|
||||
assertTrue("Response has not been committed", response.isCommitted());
|
||||
assertResolved(mav, 400, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statusCodeAndReason() {
|
||||
StatusCodeAndReasonException ex = new StatusCodeAndReasonException();
|
||||
ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex);
|
||||
assertNotNull("No ModelAndView returned", mav);
|
||||
assertTrue("No Empty ModelAndView returned", mav.isEmpty());
|
||||
assertEquals("Invalid status code", 410, response.getStatus());
|
||||
assertEquals("Invalid status reason", "You suck!", response.getErrorMessage());
|
||||
assertTrue("Response has not been committed", response.isCommitted());
|
||||
assertResolved(mav, 410, "You suck!");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -112,16 +106,29 @@ public class ResponseStatusExceptionResolverTests {
|
|||
assertNull("ModelAndView returned", mav);
|
||||
}
|
||||
|
||||
// SPR-12903
|
||||
|
||||
@Test
|
||||
@Test // SPR-12903
|
||||
public void nestedException() throws Exception {
|
||||
Exception cause = new StatusCodeAndReasonMessageException();
|
||||
TypeMismatchException ex = new TypeMismatchException("value", ITestBean.class, cause);
|
||||
ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex);
|
||||
assertNotNull("No ModelAndView returned", mav);
|
||||
assertTrue("No Empty ModelAndView returned", mav.isEmpty());
|
||||
assertEquals("Invalid status code", 410, response.getStatus());
|
||||
assertResolved(mav, 410, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseStatusException() throws Exception {
|
||||
ResponseStatusException ex = new ResponseStatusException(HttpStatus.BAD_REQUEST, "The reason");
|
||||
ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex);
|
||||
assertResolved(mav, 400, null);
|
||||
}
|
||||
|
||||
|
||||
private void assertResolved(ModelAndView mav, int status, String reason) {
|
||||
assertTrue("No Empty ModelAndView returned", mav != null && mav.isEmpty());
|
||||
assertEquals(status, response.getStatus());
|
||||
if (reason != null) {
|
||||
assertEquals(reason, response.getErrorMessage());
|
||||
}
|
||||
assertTrue(response.isCommitted());
|
||||
}
|
||||
|
||||
|
||||
|
@ -142,6 +149,7 @@ public class ResponseStatusExceptionResolverTests {
|
|||
|
||||
@ResponseStatus
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@SuppressWarnings("unused")
|
||||
@interface ComposedResponseStatus {
|
||||
|
||||
@AliasFor(annotation = ResponseStatus.class, attribute = "code")
|
||||
|
|
Loading…
Reference in New Issue