Support attribute overrides with @ResponseStatus
This commit introduces support for attribute overrides for @ResponseStatus when @ResponseStatus is used as a meta-annotation on a custom composed annotation. Specifically, this commit migrates all code that looks up @ResponseStatus from using AnnotationUtils.findAnnotation() to using AnnotatedElementUtils.findMergedAnnotation(). Issue: SPR-13441
This commit is contained in:
parent
4a49ce9694
commit
e2bfbdcfd1
|
@ -16,8 +16,12 @@
|
|||
|
||||
package org.springframework.test.web.servlet.samples.standalone.resultmatchers;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
@ -26,6 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
|||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.springframework.http.HttpStatus.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
|
||||
|
@ -34,6 +39,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
|
|||
* Examples of expectations on the status and the status reason found in the response.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class StatusAssertionTests {
|
||||
|
||||
|
@ -42,12 +48,14 @@ public class StatusAssertionTests {
|
|||
@Test
|
||||
public void testStatusInt() throws Exception {
|
||||
this.mockMvc.perform(get("/created")).andExpect(status().is(201));
|
||||
this.mockMvc.perform(get("/createdWithComposedAnnotation")).andExpect(status().is(201));
|
||||
this.mockMvc.perform(get("/badRequest")).andExpect(status().is(400));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpStatus() throws Exception {
|
||||
this.mockMvc.perform(get("/created")).andExpect(status().isCreated());
|
||||
this.mockMvc.perform(get("/createdWithComposedAnnotation")).andExpect(status().isCreated());
|
||||
this.mockMvc.perform(get("/badRequest")).andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
|
@ -66,27 +74,43 @@ public class StatusAssertionTests {
|
|||
|
||||
@Test
|
||||
public void testReasonMatcher() throws Exception {
|
||||
this.mockMvc.perform(get("/badRequest"))
|
||||
.andExpect(status().reason(endsWith("token")));
|
||||
this.mockMvc.perform(get("/badRequest")).andExpect(status().reason(endsWith("token")));
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping
|
||||
@ResponseStatus
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Get {
|
||||
|
||||
@AliasFor(annotation = RequestMapping.class, attribute = "path")
|
||||
String[] path() default {};
|
||||
|
||||
@AliasFor(annotation = ResponseStatus.class, attribute = "code")
|
||||
HttpStatus status() default INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
@Controller
|
||||
private static class StatusController {
|
||||
|
||||
@RequestMapping("/created")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
@ResponseStatus(CREATED)
|
||||
public @ResponseBody void created(){
|
||||
}
|
||||
|
||||
@Get(path = "/createdWithComposedAnnotation", status = CREATED)
|
||||
public @ResponseBody void createdWithComposedAnnotation() {
|
||||
}
|
||||
|
||||
@RequestMapping("/badRequest")
|
||||
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Expired token")
|
||||
@ResponseStatus(code = BAD_REQUEST, reason = "Expired token")
|
||||
public @ResponseBody void badRequest(){
|
||||
}
|
||||
|
||||
@RequestMapping("/notImplemented")
|
||||
@ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
|
||||
@ResponseStatus(NOT_IMPLEMENTED)
|
||||
public @ResponseBody void notImplemented(){
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.apache.commons.logging.LogFactory;
|
|||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.SynthesizingMethodParameter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
@ -33,15 +33,17 @@ import org.springframework.util.ClassUtils;
|
|||
/**
|
||||
* Encapsulates information about a handler method consisting of a
|
||||
* {@linkplain #getMethod() method} and a {@linkplain #getBean() bean}.
|
||||
* Provides convenient access to method parameters, method return value, method annotations.
|
||||
* Provides convenient access to method parameters, the method return value,
|
||||
* method annotations, etc.
|
||||
*
|
||||
* <p>The class may be created with a bean instance or with a bean name (e.g. lazy-init bean,
|
||||
* prototype bean). Use {@link #createWithResolvedBean()} to obtain a {@link HandlerMethod}
|
||||
* prototype bean). Use {@link #createWithResolvedBean()} to obtain a {@code HandlerMethod}
|
||||
* instance with a bean instance resolved through the associated {@link BeanFactory}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
public class HandlerMethod {
|
||||
|
@ -98,7 +100,7 @@ public class HandlerMethod {
|
|||
/**
|
||||
* Create an instance from a bean name, a method, and a {@code BeanFactory}.
|
||||
* The method {@link #createWithResolvedBean()} may be used later to
|
||||
* re-create the {@code HandlerMethod} with an initialized the bean.
|
||||
* re-create the {@code HandlerMethod} with an initialized bean.
|
||||
*/
|
||||
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
|
||||
Assert.hasText(beanName, "Bean name is required");
|
||||
|
@ -222,11 +224,14 @@ public class HandlerMethod {
|
|||
/**
|
||||
* Returns a single annotation on the underlying method traversing its super methods
|
||||
* if no annotation can be found on the given method itself.
|
||||
* <p>Also supports <em>merged</em> composed annotations with attribute
|
||||
* overrides as of Spring Framework 4.2.2.
|
||||
* @param annotationType the type of annotation to introspect the method for.
|
||||
* @return the annotation, or {@code null} if none found
|
||||
* @see AnnotatedElementUtils#findMergedAnnotation
|
||||
*/
|
||||
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
|
||||
return AnnotationUtils.findAnnotation(this.method, annotationType);
|
||||
return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -56,6 +56,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
|||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
@ -119,7 +120,7 @@ import org.springframework.web.util.WebUtils;
|
|||
|
||||
/**
|
||||
* Implementation of the {@link org.springframework.web.servlet.HandlerAdapter} interface
|
||||
* that maps handler methods based on HTTP paths, HTTP methods and request parameters
|
||||
* that maps handler methods based on HTTP paths, HTTP methods, and request parameters
|
||||
* expressed through the {@link RequestMapping} annotation.
|
||||
*
|
||||
* <p>Supports request parameter binding through the {@link RequestParam} annotation.
|
||||
|
@ -133,6 +134,7 @@ import org.springframework.web.util.WebUtils;
|
|||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Arjen Poutsma
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @see #setPathMatcher
|
||||
* @see #setMethodNameResolver
|
||||
|
@ -911,19 +913,19 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
|
|||
public ModelAndView getModelAndView(Method handlerMethod, Class<?> handlerType, Object returnValue,
|
||||
ExtendedModelMap implicitModel, ServletWebRequest webRequest) throws Exception {
|
||||
|
||||
ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class);
|
||||
if (responseStatusAnn != null) {
|
||||
HttpStatus responseStatus = responseStatusAnn.code();
|
||||
String reason = responseStatusAnn.reason();
|
||||
ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(handlerMethod, ResponseStatus.class);
|
||||
if (responseStatus != null) {
|
||||
HttpStatus statusCode = responseStatus.code();
|
||||
String reason = responseStatus.reason();
|
||||
if (!StringUtils.hasText(reason)) {
|
||||
webRequest.getResponse().setStatus(responseStatus.value());
|
||||
webRequest.getResponse().setStatus(statusCode.value());
|
||||
}
|
||||
else {
|
||||
webRequest.getResponse().sendError(responseStatus.value(), reason);
|
||||
webRequest.getResponse().sendError(statusCode.value(), reason);
|
||||
}
|
||||
|
||||
// to be picked up by the RedirectView
|
||||
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, responseStatus);
|
||||
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, statusCode);
|
||||
|
||||
this.responseArgumentUsed = true;
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import javax.xml.transform.Source;
|
|||
import org.springframework.core.ExceptionDepthComparator;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.SynthesizingMethodParameter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
|
@ -377,15 +378,15 @@ public class AnnotationMethodHandlerExceptionResolver extends AbstractHandlerExc
|
|||
private ModelAndView getModelAndView(Method handlerMethod, Object returnValue, ServletWebRequest webRequest)
|
||||
throws Exception {
|
||||
|
||||
ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class);
|
||||
if (responseStatusAnn != null) {
|
||||
HttpStatus responseStatus = responseStatusAnn.code();
|
||||
String reason = responseStatusAnn.reason();
|
||||
ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(handlerMethod, ResponseStatus.class);
|
||||
if (responseStatus != null) {
|
||||
HttpStatus statusCode = responseStatus.code();
|
||||
String reason = responseStatus.reason();
|
||||
if (!StringUtils.hasText(reason)) {
|
||||
webRequest.getResponse().setStatus(responseStatus.value());
|
||||
webRequest.getResponse().setStatus(statusCode.value());
|
||||
}
|
||||
else {
|
||||
webRequest.getResponse().sendError(responseStatus.value(), reason);
|
||||
webRequest.getResponse().sendError(statusCode.value(), reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
@ -38,11 +38,14 @@ import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
|
|||
* and the MVC Java config and the MVC namespace.
|
||||
*
|
||||
* <p>As of 4.2 this resolver also looks recursively for {@code @ResponseStatus}
|
||||
* present on cause exceptions.
|
||||
* present on cause exceptions, and as of 4.2.2 this resolver supports
|
||||
* attribute overrides for {@code @ResponseStatus} in custom composed annotations.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
* @see AnnotatedElementUtils#findMergedAnnotation
|
||||
*/
|
||||
public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware {
|
||||
|
||||
|
@ -59,7 +62,7 @@ public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionRes
|
|||
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler, Exception ex) {
|
||||
|
||||
ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
|
||||
ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
|
||||
if (responseStatus != null) {
|
||||
try {
|
||||
return resolveResponseStatus(responseStatus, request, response, handler, ex);
|
||||
|
|
|
@ -20,19 +20,20 @@ import java.io.FileNotFoundException;
|
|||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.Writer;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.net.BindException;
|
||||
import java.net.SocketException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
@ -43,25 +44,18 @@ import static org.junit.Assert.*;
|
|||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
@Deprecated
|
||||
public class AnnotationMethodHandlerExceptionResolverTests {
|
||||
|
||||
private AnnotationMethodHandlerExceptionResolver exceptionResolver;
|
||||
private final AnnotationMethodHandlerExceptionResolver exceptionResolver = new AnnotationMethodHandlerExceptionResolver();
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
private final MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
private final MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
exceptionResolver = new AnnotationMethodHandlerExceptionResolver();
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
request.setMethod("GET");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleWithIOException() {
|
||||
IOException ex = new IOException();
|
||||
|
@ -103,6 +97,16 @@ public class AnnotationMethodHandlerExceptionResolverTests {
|
|||
assertEquals("Invalid status code returned", 406, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleWithNumberFormatExceptionAndComposedResponseStatusAnnotation() {
|
||||
NumberFormatException ex = new NumberFormatException();
|
||||
SimpleController controller = new SimpleController();
|
||||
ModelAndView mav = exceptionResolver.resolveException(request, response, controller, ex);
|
||||
assertNotNull("No ModelAndView returned", mav);
|
||||
assertEquals("Invalid view name returned", "X:NumberFormatException", mav.getViewName());
|
||||
assertEquals("Invalid status code returned", 400, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inherited() {
|
||||
IOException ex = new IOException();
|
||||
|
@ -155,6 +159,13 @@ public class AnnotationMethodHandlerExceptionResolverTests {
|
|||
assertNull(mav);
|
||||
}
|
||||
|
||||
@ResponseStatus
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ComposedResponseStatus {
|
||||
|
||||
@AliasFor(annotation = ResponseStatus.class, attribute = "code")
|
||||
HttpStatus responseStatus() default HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
@Controller
|
||||
private static class SimpleController {
|
||||
|
@ -162,18 +173,24 @@ public class AnnotationMethodHandlerExceptionResolverTests {
|
|||
@ExceptionHandler(IOException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public String handleIOException(IOException ex, HttpServletRequest request) {
|
||||
return "X:" + ClassUtils.getShortName(ex.getClass());
|
||||
return "X:" + ex.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@ExceptionHandler(SocketException.class)
|
||||
@ResponseStatus(code = HttpStatus.NOT_ACCEPTABLE, reason = "This is simply unacceptable!")
|
||||
public String handleSocketException(Exception ex, HttpServletResponse response) {
|
||||
return "Y:" + ClassUtils.getShortName(ex.getClass());
|
||||
return "Y:" + ex.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public String handleIllegalArgumentException(Exception ex) {
|
||||
return ClassUtils.getShortName(ex.getClass());
|
||||
return ex.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@ExceptionHandler(NumberFormatException.class)
|
||||
@ComposedResponseStatus(responseStatus = HttpStatus.BAD_REQUEST)
|
||||
public String handleNumberFormatException(NumberFormatException ex) {
|
||||
return "X:" + ex.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,12 +211,12 @@ public class AnnotationMethodHandlerExceptionResolverTests {
|
|||
@ExceptionHandler({BindException.class, IllegalArgumentException.class})
|
||||
public String handle1(Exception ex, HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException {
|
||||
return ClassUtils.getShortName(ex.getClass());
|
||||
return ex.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@ExceptionHandler
|
||||
public String handle2(IllegalArgumentException ex) {
|
||||
return ClassUtils.getShortName(ex.getClass());
|
||||
return ex.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,7 +226,7 @@ public class AnnotationMethodHandlerExceptionResolverTests {
|
|||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public void handle(Exception ex, Writer writer) throws IOException {
|
||||
writer.write(ClassUtils.getShortName(ex.getClass()));
|
||||
writer.write(ex.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,7 +237,7 @@ public class AnnotationMethodHandlerExceptionResolverTests {
|
|||
@ExceptionHandler(Exception.class)
|
||||
@ResponseBody
|
||||
public String handle(Exception ex) {
|
||||
return ClassUtils.getShortName(ex.getClass());
|
||||
return ex.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,16 +16,16 @@
|
|||
|
||||
package org.springframework.web.servlet.mvc.annotation;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.context.support.StaticMessageSource;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
|
@ -33,22 +33,21 @@ import org.springframework.tests.sample.beans.ITestBean;
|
|||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/** @author Arjen Poutsma */
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ResponseStatusExceptionResolver}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class ResponseStatusExceptionResolverTests {
|
||||
|
||||
private ResponseStatusExceptionResolver exceptionResolver;
|
||||
private final ResponseStatusExceptionResolver exceptionResolver = new ResponseStatusExceptionResolver();
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
private final MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
exceptionResolver = new ResponseStatusExceptionResolver();
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
request.setMethod("GET");
|
||||
}
|
||||
private final MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
@Test
|
||||
public void statusCode() {
|
||||
|
@ -60,6 +59,16 @@ public class ResponseStatusExceptionResolverTests {
|
|||
assertTrue("Response has not been committed", response.isCommitted());
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statusCodeAndReason() {
|
||||
StatusCodeAndReasonException ex = new StatusCodeAndReasonException();
|
||||
|
@ -109,6 +118,7 @@ public class ResponseStatusExceptionResolverTests {
|
|||
assertEquals("Invalid status code", 410, response.getStatus());
|
||||
}
|
||||
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@SuppressWarnings("serial")
|
||||
private static class StatusCodeException extends Exception {
|
||||
|
@ -124,4 +134,17 @@ public class ResponseStatusExceptionResolverTests {
|
|||
private static class StatusCodeAndReasonMessageException extends Exception {
|
||||
}
|
||||
|
||||
@ResponseStatus
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ComposedResponseStatus {
|
||||
|
||||
@AliasFor(annotation = ResponseStatus.class, attribute = "code")
|
||||
HttpStatus responseStatus() default HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
@ComposedResponseStatus(responseStatus = HttpStatus.BAD_REQUEST)
|
||||
@SuppressWarnings("serial")
|
||||
private static class StatusCodeFromComposedResponseStatusException extends Exception {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,9 +16,8 @@
|
|||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -29,6 +28,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
@ -49,6 +49,9 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandlerCom
|
|||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Test fixture with {@link ServletInvocableHandlerMethod}.
|
||||
*
|
||||
|
@ -80,6 +83,16 @@ public class ServletInvocableHandlerMethodTests {
|
|||
assertEquals(HttpStatus.BAD_REQUEST.value(), this.response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeAndHandle_VoidWithComposedResponseStatus() throws Exception {
|
||||
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "composedResponseStatus");
|
||||
handlerMethod.invokeAndHandle(this.webRequest, this.mavContainer);
|
||||
|
||||
assertTrue("Null return value + @ComposedResponseStatus should result in 'request handled'",
|
||||
this.mavContainer.isRequestHandled());
|
||||
assertEquals(HttpStatus.BAD_REQUEST.value(), this.response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeAndHandle_VoidWithHttpServletResponseArgument() throws Exception {
|
||||
this.argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver());
|
||||
|
@ -260,6 +273,13 @@ public class ServletInvocableHandlerMethodTests {
|
|||
return handlerMethod;
|
||||
}
|
||||
|
||||
@ResponseStatus
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ComposedResponseStatus {
|
||||
|
||||
@AliasFor(annotation = ResponseStatus.class, attribute = "code")
|
||||
HttpStatus responseStatus() default HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class Handler {
|
||||
|
@ -277,6 +297,10 @@ public class ServletInvocableHandlerMethodTests {
|
|||
return "foo";
|
||||
}
|
||||
|
||||
@ComposedResponseStatus(responseStatus = HttpStatus.BAD_REQUEST)
|
||||
public void composedResponseStatus() {
|
||||
}
|
||||
|
||||
public void httpServletResponse(HttpServletResponse response) {
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue