Async return values refactoring in Spring MVC

Revise Javadoc on AsyncHandlerMethodReturnValueHandler to clarify its
main purpose is to prioritze custom async return value handlers ahead
of built-in ones. Also replace the interface from built-in handlers
which are prioritized already.

Remove DeferredResultAdapter and ResponseBodyEmitterAdapter --
introduced in 4.3 for custom async return value handling, since for
5.0 we will add built-in support for reactive types and the value of
these contracts becomes very marginal.

Issue: SPR-15365
This commit is contained in:
Rossen Stoyanchev
2017-04-02 20:30:24 -04:00
parent cfc89ebe16
commit ae1ed16cb8
10 changed files with 143 additions and 391 deletions

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.Method;
import java.util.concurrent.CompletableFuture;
import org.junit.Before;
@@ -36,8 +35,8 @@ import org.springframework.web.method.support.ModelAndViewContainer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.springframework.web.method.ResolvableMethod.on;
/**
* Unit tests for {@link DeferredResultMethodReturnValueHandler}.
@@ -68,129 +67,88 @@ public class DeferredResultReturnValueHandlerTests {
@Test
public void supportsReturnType() throws Exception {
assertTrue(this.handler.supportsReturnType(returnType("handleDeferredResult")));
assertTrue(this.handler.supportsReturnType(returnType("handleListenableFuture")));
assertTrue(this.handler.supportsReturnType(returnType("handleCompletableFuture")));
assertFalse(this.handler.supportsReturnType(returnType("handleString")));
assertTrue(this.handler.supportsReturnType(
on(TestController.class).resolveReturnType(DeferredResult.class, String.class)));
assertTrue(this.handler.supportsReturnType(
on(TestController.class).resolveReturnType(ListenableFuture.class, String.class)));
assertTrue(this.handler.supportsReturnType(
on(TestController.class).resolveReturnType(CompletableFuture.class, String.class)));
}
@Test
public void doesNotSupportReturnType() throws Exception {
assertFalse(this.handler.supportsReturnType(on(TestController.class).resolveReturnType(String.class)));
}
@Test
public void deferredResult() throws Exception {
MethodParameter returnType = returnType("handleDeferredResult");
DeferredResult<String> deferredResult = new DeferredResult<>();
handleReturnValue(deferredResult, returnType);
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
deferredResult.setResult("foo");
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertEquals("foo", WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
}
@Test
public void deferredResultWitError() throws Exception {
MethodParameter returnType = returnType("handleDeferredResult");
DeferredResult<String> deferredResult = new DeferredResult<>();
handleReturnValue(deferredResult, returnType);
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
DeferredResult<String> result = new DeferredResult<>();
IllegalStateException ex = new IllegalStateException();
deferredResult.setErrorResult(ex);
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertSame(ex, WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
testHandle(result, DeferredResult.class, () -> result.setErrorResult(ex), ex);
}
@Test
public void listenableFuture() throws Exception {
MethodParameter returnType = returnType("handleListenableFuture");
SettableListenableFuture<String> future = new SettableListenableFuture<>();
handleReturnValue(future, returnType);
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
future.set("foo");
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertEquals("foo", WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
}
@Test
public void listenableFutureWithError() throws Exception {
MethodParameter returnType = returnType("handleListenableFuture");
SettableListenableFuture<String> future = new SettableListenableFuture<>();
handleReturnValue(future, returnType);
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
IllegalStateException ex = new IllegalStateException();
future.setException(ex);
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertSame(ex, WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
testHandle(future, ListenableFuture.class, () -> future.set("foo"), "foo");
}
@Test
public void completableFuture() throws Exception {
MethodParameter returnType = returnType("handleCompletableFuture");
SettableListenableFuture<String> future = new SettableListenableFuture<>();
handleReturnValue(future, returnType);
testHandle(future, CompletableFuture.class, () -> future.set("foo"), "foo");
}
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
@Test
public void deferredResultWitError() throws Exception {
DeferredResult<String> result = new DeferredResult<>();
testHandle(result, DeferredResult.class, () -> result.setResult("foo"), "foo");
}
future.set("foo");
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertEquals("foo", WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
@Test
public void listenableFutureWithError() throws Exception {
SettableListenableFuture<String> future = new SettableListenableFuture<>();
IllegalStateException ex = new IllegalStateException();
testHandle(future, ListenableFuture.class, () -> future.setException(ex), ex);
}
@Test
public void completableFutureWithError() throws Exception {
MethodParameter returnType = returnType("handleCompletableFuture");
CompletableFuture<String> future = new CompletableFuture<>();
handleReturnValue(future, returnType);
SettableListenableFuture<String> future = new SettableListenableFuture<>();
IllegalStateException ex = new IllegalStateException();
testHandle(future, CompletableFuture.class, () -> future.setException(ex), ex);
}
private void testHandle(Object returnValue, Class<?> asyncType,
Runnable setResultTask, Object expectedValue) throws Exception {
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
MethodParameter returnType = on(TestController.class).resolveReturnType(asyncType, String.class);
this.handler.handleReturnValue(returnValue, returnType, mavContainer, this.webRequest);
assertTrue(this.request.isAsyncStarted());
assertFalse(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
IllegalStateException ex = new IllegalStateException();
future.completeExceptionally(ex);
setResultTask.run();
assertTrue(WebAsyncUtils.getAsyncManager(this.webRequest).hasConcurrentResult());
assertSame(ex, WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
}
private void handleReturnValue(Object returnValue, MethodParameter returnType) throws Exception {
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
this.handler.handleReturnValue(returnValue, returnType, mavContainer, this.webRequest);
}
private MethodParameter returnType(String methodName) throws NoSuchMethodException {
Method method = TestController.class.getDeclaredMethod(methodName);
return new MethodParameter(method, -1);
assertEquals(expectedValue, WebAsyncUtils.getAsyncManager(this.webRequest).getConcurrentResult());
}
@SuppressWarnings("unused")
private static class TestController {
static class TestController {
private String handleString() {
return null;
}
String handleString() { return null; }
private DeferredResult<String> handleDeferredResult() {
return null;
}
DeferredResult<String> handleDeferredResult() { return null; }
private ListenableFuture<String> handleListenableFuture() {
return null;
}
ListenableFuture<String> handleListenableFuture() { return null; }
private CompletableFuture<String> handleCompletableFuture() {
return null;
}
CompletableFuture<String> handleCompletableFuture() { return null; }
}
}