diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/StatusAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/StatusAssertionTests.java index d092d723b30..5ae4128589a 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/StatusAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/StatusAssertionTests.java @@ -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(){ } } + } diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java index 5be84c00774..33a9b291bbf 100644 --- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java @@ -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. * *
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. + *
Also supports merged 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 getMethodAnnotation(Class annotationType) {
- return AnnotationUtils.findAnnotation(this.method, annotationType);
+ return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType);
}
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
index 006b0784c0d..f3cdd873a29 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
@@ -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.
*
* 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;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java
index 75ff89c72d7..7c2568b2490 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java
@@ -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);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java
index 7bf45276602..b9b9f788e05 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java
@@ -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.
*
* 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);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java
index 075457cd2ce..7103d09df71 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java
@@ -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();
}
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java
index 4df11749174..d615b3a2ee2 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java
@@ -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 */
-public class ResponseStatusExceptionResolverTests {
+import static org.junit.Assert.*;
- private ResponseStatusExceptionResolver exceptionResolver;
+/**
+ * Integration tests for {@link ResponseStatusExceptionResolver}.
+ *
+ * @author Arjen Poutsma
+ * @author Sam Brannen
+ */
+public class ResponseStatusExceptionResolverTests {
- private MockHttpServletRequest request;
+ private final ResponseStatusExceptionResolver exceptionResolver = new ResponseStatusExceptionResolver();
- private MockHttpServletResponse response;
+ private final MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
- @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 {
+ }
+
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java
index 1f8c7c6523e..157efb024f5 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java
@@ -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) {
}