HandlerMethodArgumentResolver is now asynchronous
This commit is contained in:
parent
6b73993a38
commit
0989c8b3c2
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.reactive.web.dispatch.method;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.server.ReactiveServerHttpRequest;
|
||||
|
||||
|
@ -27,6 +29,12 @@ public interface HandlerMethodArgumentResolver {
|
|||
|
||||
boolean supportsParameter(MethodParameter parameter);
|
||||
|
||||
Object resolveArgument(MethodParameter parameter, ReactiveServerHttpRequest request);
|
||||
/**
|
||||
* The returned Publisher must produce a single value. As Reactive Streams
|
||||
* does not allow publishing null values, if the value may be {@code null}
|
||||
* use {@link java.util.Optional#ofNullable(Object)} to wrap it.
|
||||
*/
|
||||
Publisher<Object> resolveArgument(MethodParameter parameter, ReactiveServerHttpRequest request);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -19,8 +19,14 @@ package org.springframework.reactive.web.dispatch.method;
|
|||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.Publishers;
|
||||
import reactor.fn.tuple.Tuple;
|
||||
import reactor.rx.Streams;
|
||||
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
|
@ -32,9 +38,6 @@ import org.springframework.web.method.HandlerMethod;
|
|||
|
||||
|
||||
/**
|
||||
* 90% overlap with the existing one in spring-web except for the different
|
||||
* HandlerMethodArgumentResolver contract.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class InvocableHandlerMethod extends HandlerMethod {
|
||||
|
@ -55,53 +58,74 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
}
|
||||
|
||||
|
||||
public Object invokeForRequest(ReactiveServerHttpRequest request, Object... providedArgs) throws Exception {
|
||||
Object[] args = getMethodArgumentValues(request, providedArgs);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Invoking [" + getBeanType().getSimpleName() + "." +
|
||||
getMethod().getName() + "] method with arguments " + Arrays.asList(args));
|
||||
}
|
||||
Object returnValue = doInvoke(args);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
|
||||
}
|
||||
return returnValue;
|
||||
public Publisher<Object> invokeForRequest(ReactiveServerHttpRequest request,
|
||||
Object... providedArgs) {
|
||||
|
||||
List<Publisher<Object>> argPublishers = getMethodArguments(request, providedArgs);
|
||||
|
||||
Publisher<Object[]> argValues = (!argPublishers.isEmpty() ?
|
||||
Streams.zip(argPublishers, this::unwrapOptionalArgValues) : Publishers.just(new Object[0]));
|
||||
|
||||
return Publishers.map(argValues, args -> {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Invoking [" + getBeanType().getSimpleName() + "." +
|
||||
getMethod().getName() + "] method with arguments " +
|
||||
Collections.singletonList(argPublishers));
|
||||
}
|
||||
Object returnValue = null;
|
||||
try {
|
||||
returnValue = doInvoke(args);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// TODO: how to best handle error inside map? (also wrapping hides original ex)
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
return returnValue;
|
||||
});
|
||||
}
|
||||
|
||||
private Object[] getMethodArgumentValues(ReactiveServerHttpRequest request, Object... providedArgs) throws Exception {
|
||||
private List<Publisher<Object>> getMethodArguments(ReactiveServerHttpRequest request,
|
||||
Object... providedArgs) {
|
||||
|
||||
MethodParameter[] parameters = getMethodParameters();
|
||||
Object[] args = new Object[parameters.length];
|
||||
List<Publisher<Object>> valuePublishers = new ArrayList<>(parameters.length);
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
MethodParameter parameter = parameters[i];
|
||||
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
|
||||
GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
|
||||
args[i] = resolveProvidedArgument(parameter, providedArgs);
|
||||
if (args[i] != null) {
|
||||
Object value = resolveProvidedArgument(parameter, providedArgs);
|
||||
if (value != null) {
|
||||
valuePublishers.add(Publishers.just(value));
|
||||
continue;
|
||||
}
|
||||
boolean resolved = false;
|
||||
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
|
||||
if (resolver.supportsParameter(parameter)) {
|
||||
try {
|
||||
args[i] = resolver.resolveArgument(parameter, request);
|
||||
valuePublishers.add(resolver.resolveArgument(parameter, request));
|
||||
resolved = true;
|
||||
break;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
|
||||
}
|
||||
throw ex;
|
||||
String msg = buildArgErrorMessage("Error resolving argument", i);
|
||||
valuePublishers.add(Publishers.error(new IllegalStateException(msg, ex)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (args[i] == null) {
|
||||
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
|
||||
throw new IllegalStateException(msg);
|
||||
if (!resolved) {
|
||||
String msg = buildArgErrorMessage("No suitable resolver for argument", i);
|
||||
valuePublishers.add(Publishers.error(new IllegalStateException(msg)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
return valuePublishers;
|
||||
}
|
||||
|
||||
private String getArgumentResolutionErrorMessage(String message, int index) {
|
||||
private String buildArgErrorMessage(String message, int index) {
|
||||
MethodParameter param = getMethodParameters()[index];
|
||||
message += " [" + index + "] [type=" + param.getParameterType().getName() + "]";
|
||||
return getDetailedErrorMessage(message);
|
||||
|
@ -125,6 +149,27 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
|||
return null;
|
||||
}
|
||||
|
||||
private void unwrapOptionalArgValues(Object[] args) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i] instanceof Optional) {
|
||||
Optional optional = (Optional) args[i];
|
||||
args[i] = optional.isPresent() ? optional.get() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object[] unwrapOptionalArgValues(Tuple tuple) {
|
||||
Object[] args = new Object[tuple.size()];
|
||||
for (int i = 0; i < tuple.size(); i++) {
|
||||
args[i] = tuple.get(i);
|
||||
if (args[i] instanceof Optional) {
|
||||
Optional optional = (Optional) args[i];
|
||||
args[i] = optional.isPresent() ? optional.get() : null;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
protected Object doInvoke(Object... args) throws Exception {
|
||||
ReflectionUtils.makeAccessible(getBridgedMethod());
|
||||
try {
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.Publishers;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
|
@ -66,9 +67,7 @@ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolve
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object resolveArgument(MethodParameter parameter, ReactiveServerHttpRequest request) {
|
||||
|
||||
public Publisher<Object> resolveArgument(MethodParameter parameter, ReactiveServerHttpRequest request) {
|
||||
MediaType mediaType = resolveMediaType(request);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(parameter);
|
||||
List<Object> hints = new ArrayList<>();
|
||||
|
@ -85,12 +84,10 @@ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolve
|
|||
}
|
||||
elementStream = deserializer.decode(inputStream, elementType, mediaType, hints.toArray());
|
||||
}
|
||||
if (conversionService.canConvert(Publisher.class, type.getRawClass())) {
|
||||
return conversionService.convert(elementStream, type.getRawClass());
|
||||
}
|
||||
else {
|
||||
return elementStream;
|
||||
if (this.conversionService.canConvert(Publisher.class, type.getRawClass())) {
|
||||
return Publishers.just(this.conversionService.convert(elementStream, type.getRawClass()));
|
||||
}
|
||||
return Publishers.map(elementStream, element -> element);
|
||||
}
|
||||
|
||||
private MediaType resolveMediaType(ReactiveServerHttpRequest request) {
|
||||
|
|
|
@ -79,14 +79,8 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Initializin
|
|||
InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod((HandlerMethod) handler);
|
||||
handlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
|
||||
|
||||
try {
|
||||
Object result = handlerMethod.invokeForRequest(request);
|
||||
return Publishers.just(new HandlerResult(handlerMethod, result));
|
||||
}
|
||||
catch (Exception e) {
|
||||
// TODO: remove throws declaration from InvocableHandlerMethod
|
||||
return Publishers.error(e);
|
||||
}
|
||||
Publisher<Object> resultPublisher = handlerMethod.invokeForRequest(request);
|
||||
return Publishers.map(resultPublisher, result -> new HandlerResult(handlerMethod, result));
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,11 @@
|
|||
package org.springframework.reactive.web.dispatch.method.annotation;
|
||||
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.Publishers;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.server.ReactiveServerHttpRequest;
|
||||
import org.springframework.reactive.web.dispatch.method.HandlerMethodArgumentResolver;
|
||||
|
@ -40,11 +45,12 @@ public class RequestParamArgumentResolver implements HandlerMethodArgumentResolv
|
|||
|
||||
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter param, ReactiveServerHttpRequest request) {
|
||||
public Publisher<Object> resolveArgument(MethodParameter param, ReactiveServerHttpRequest request) {
|
||||
RequestParam annotation = param.getParameterAnnotation(RequestParam.class);
|
||||
String name = (annotation.value().length() != 0 ? annotation.value() : param.getParameterName());
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUri(request.getURI()).build();
|
||||
return uriComponents.getQueryParams().getFirst(name);
|
||||
String value = uriComponents.getQueryParams().getFirst(name);
|
||||
return Publishers.just(Optional.ofNullable(value));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -180,6 +180,11 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
|
|||
capitalizeCollection("http://localhost:" + port + "/stream-capitalize");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void personCapitalize() throws Exception {
|
||||
capitalizePojo("http://localhost:" + port + "/person-capitalize");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completableFutureCapitalize() throws Exception {
|
||||
capitalizePojo("http://localhost:" + port + "/completable-future-capitalize");
|
||||
|
@ -376,6 +381,13 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
|
|||
});
|
||||
}
|
||||
|
||||
@RequestMapping("/person-capitalize")
|
||||
@ResponseBody
|
||||
public Person personCapitalize(@RequestBody Person person) {
|
||||
person.setName(person.getName().toUpperCase());
|
||||
return person;
|
||||
}
|
||||
|
||||
@RequestMapping("/completable-future-capitalize")
|
||||
@ResponseBody
|
||||
public CompletableFuture<Person> completableFutureCapitalize(@RequestBody CompletableFuture<Person> personFuture) {
|
||||
|
|
Loading…
Reference in New Issue