Browse Source

Fix issue with DeferredResult on @RestController

Before this change, async result handling on controller methods failed
to observe type-level annotations annotations. The issue was never
noticed until we started supporting type-level @ResponseBody and the
@RestController meta annotation.

Issue: SPR-10905
pull/387/head
Rossen Stoyanchev 13 years ago
parent
commit
cf7889e226
  1. 27
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java
  2. 93
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java

27
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java

@ -174,9 +174,10 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
} }
/** /**
* Return a ServletInvocableHandlerMethod that will process the value returned * Create a ServletInvocableHandlerMethod that will return the given value from an
* from an async operation essentially either applying return value handling or * async operation instead of invoking the controller method again. The async result
* raising an exception if the end result is an Exception. * value is then either processed as if the controller method returned it or an
* exception is raised if the async result value itself is an Exception.
*/ */
ServletInvocableHandlerMethod wrapConcurrentResult(final Object result) { ServletInvocableHandlerMethod wrapConcurrentResult(final Object result) {
@ -197,9 +198,12 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
/** /**
* A ServletInvocableHandlerMethod sub-class that invokes a given * A sub-class of {@link HandlerMethod} that invokes the given {@link Callable}
* {@link Callable} and "inherits" the annotations of the containing class * instead of the target controller method. This is useful for resuming processing
* instance, useful for invoking a Callable returned from a HandlerMethod. * with the result of an async operation. The goal is to process the value returned
* from the Callable as if it was returned by the target controller method, i.e.
* taking into consideration both method and type-level controller annotations (e.g.
* {@code @ResponseBody}, {@code @ResponseStatus}, etc).
*/ */
private class CallableHandlerMethod extends ServletInvocableHandlerMethod { private class CallableHandlerMethod extends ServletInvocableHandlerMethod {
@ -208,6 +212,17 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
this.setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers); this.setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);
} }
/**
* Bridge to type-level annotations of the target controller method.
*/
@Override
public Class<?> getBeanType() {
return ServletInvocableHandlerMethod.this.getBeanType();
}
/**
* Bridge to method-level annotations of the target controller method.
*/
@Override @Override
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) { public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType); return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType);

93
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethodTests.java

@ -15,12 +15,9 @@
*/ */
package org.springframework.web.servlet.mvc.method.annotation; package org.springframework.web.servlet.mvc.method.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -28,13 +25,18 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver; import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite; import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@ -42,6 +44,8 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandlerCom
import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.servlet.view.RedirectView;
import static org.junit.Assert.*;
/** /**
* Test fixture with {@link ServletInvocableHandlerMethod}. * Test fixture with {@link ServletInvocableHandlerMethod}.
* *
@ -73,7 +77,7 @@ public class ServletInvocableHandlerMethodTests {
@Test @Test
public void invokeAndHandle_VoidWithResponseStatus() throws Exception { public void invokeAndHandle_VoidWithResponseStatus() throws Exception {
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatus"); ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "responseStatus");
handlerMethod.invokeAndHandle(webRequest, mavContainer); handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertTrue("Null return value + @ResponseStatus should result in 'request handled'", assertTrue("Null return value + @ResponseStatus should result in 'request handled'",
@ -85,7 +89,7 @@ public class ServletInvocableHandlerMethodTests {
public void invokeAndHandle_VoidWithHttpServletResponseArgument() throws Exception { public void invokeAndHandle_VoidWithHttpServletResponseArgument() throws Exception {
argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver()); argumentResolvers.addResolver(new ServletResponseMethodArgumentResolver());
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("httpServletResponse", HttpServletResponse.class); ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "httpServletResponse", HttpServletResponse.class);
handlerMethod.invokeAndHandle(webRequest, mavContainer); handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertTrue("Null return value + HttpServletResponse arg should result in 'request handled'", assertTrue("Null return value + HttpServletResponse arg should result in 'request handled'",
@ -98,7 +102,7 @@ public class ServletInvocableHandlerMethodTests {
int lastModifiedTimestamp = 1000 * 1000; int lastModifiedTimestamp = 1000 * 1000;
webRequest.checkNotModified(lastModifiedTimestamp); webRequest.checkNotModified(lastModifiedTimestamp);
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("notModified"); ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "notModified");
handlerMethod.invokeAndHandle(webRequest, mavContainer); handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertTrue("Null return value + 'not modified' request should result in 'request handled'", assertTrue("Null return value + 'not modified' request should result in 'request handled'",
@ -109,7 +113,7 @@ public class ServletInvocableHandlerMethodTests {
@Test @Test
public void invokeAndHandle_NotVoidWithResponseStatusAndReason() throws Exception { public void invokeAndHandle_NotVoidWithResponseStatusAndReason() throws Exception {
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("responseStatusWithReason"); ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "responseStatusWithReason");
handlerMethod.invokeAndHandle(webRequest, mavContainer); handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertTrue("When a phrase is used, the response should not be used any more", mavContainer.isRequestHandled()); assertTrue("When a phrase is used, the response should not be used any more", mavContainer.isRequestHandled());
@ -121,7 +125,7 @@ public class ServletInvocableHandlerMethodTests {
public void invokeAndHandle_Exception() throws Exception { public void invokeAndHandle_Exception() throws Exception {
returnValueHandlers.addHandler(new ExceptionRaisingReturnValueHandler()); returnValueHandlers.addHandler(new ExceptionRaisingReturnValueHandler());
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("handle"); ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "handle");
handlerMethod.invokeAndHandle(webRequest, mavContainer); handlerMethod.invokeAndHandle(webRequest, mavContainer);
fail("Expected exception"); fail("Expected exception");
} }
@ -133,7 +137,7 @@ public class ServletInvocableHandlerMethodTests {
returnValueHandlers.addHandler(new ViewNameMethodReturnValueHandler()); returnValueHandlers.addHandler(new ViewNameMethodReturnValueHandler());
// Invoke without a request parameter (String return value) // Invoke without a request parameter (String return value)
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod("dynamicReturnValue", String.class); ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new Handler(), "dynamicReturnValue", String.class);
handlerMethod.invokeAndHandle(webRequest, mavContainer); handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertNotNull(mavContainer.getView()); assertNotNull(mavContainer.getView());
@ -146,11 +150,45 @@ public class ServletInvocableHandlerMethodTests {
assertEquals("view", mavContainer.getViewName()); assertEquals("view", mavContainer.getViewName());
} }
@Test
public void wrapConcurrentResult_MethodLevelResponseBody() throws Exception {
wrapConcurrentResult_ResponseBody(new MethodLevelResponseBodyHandler());
}
@Test
public void wrapConcurrentResult_TypeLevelResponseBody() throws Exception {
wrapConcurrentResult_ResponseBody(new TypeLevelResponseBodyHandler());
}
private void wrapConcurrentResult_ResponseBody(Object handler) throws Exception {
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new StringHttpMessageConverter());
returnValueHandlers.addHandler(new RequestResponseBodyMethodProcessor(converters));
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(handler, "handle");
handlerMethod = handlerMethod.wrapConcurrentResult("bar");
handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertEquals("bar", response.getContentAsString());
}
@Test
public void wrapConcurrentResult_ResponseEntity() throws Exception {
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new StringHttpMessageConverter());
returnValueHandlers.addHandler(new HttpEntityMethodProcessor(converters));
ServletInvocableHandlerMethod handlerMethod = getHandlerMethod(new ResponseEntityHandler(), "handle");
handlerMethod = handlerMethod.wrapConcurrentResult(new ResponseEntity<>("bar", HttpStatus.OK));
handlerMethod.invokeAndHandle(webRequest, mavContainer);
assertEquals("bar", response.getContentAsString());
}
private ServletInvocableHandlerMethod getHandlerMethod(String methodName, Class<?>... argTypes)
throws NoSuchMethodException { private ServletInvocableHandlerMethod getHandlerMethod(Object controller,
Method method = Handler.class.getDeclaredMethod(methodName, argTypes); String methodName, Class<?>... argTypes) throws NoSuchMethodException {
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new Handler(), method);
Method method = controller.getClass().getDeclaredMethod(methodName, argTypes);
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(controller, method);
handlerMethod.setHandlerMethodArgumentResolvers(argumentResolvers); handlerMethod.setHandlerMethodArgumentResolvers(argumentResolvers);
handlerMethod.setHandlerMethodReturnValueHandlers(returnValueHandlers); handlerMethod.setHandlerMethodReturnValueHandlers(returnValueHandlers);
return handlerMethod; return handlerMethod;
@ -183,6 +221,31 @@ public class ServletInvocableHandlerMethodTests {
} }
} }
private static class MethodLevelResponseBodyHandler {
@ResponseBody
public DeferredResult<String> handle() {
return new DeferredResult<>();
}
}
@ResponseBody
private static class TypeLevelResponseBodyHandler {
@SuppressWarnings("unused")
public DeferredResult<String> handle() {
return new DeferredResult<String>();
}
}
private static class ResponseEntityHandler {
public DeferredResult<ResponseEntity<String>> handle() {
return new DeferredResult<>();
}
}
private static class ExceptionRaisingReturnValueHandler implements HandlerMethodReturnValueHandler { private static class ExceptionRaisingReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override @Override

Loading…
Cancel
Save