Sync argument resolution and HandlerMethod invocation

HandlerMethodArgumentResolver is a non-blocking contract, however only
implementations that read the request body require blocking.

This commit introduces SyncMethodArgumentResolver as an extension of
the non-blocking contract that allows synchronous implementations to
use synchronous argument resolution.

There is also a SyncInvocableHandlerMethod extension that uses only
sync argument resolvers and allows a synchronous invocation.

Issue: SPR-14543
This commit is contained in:
Rossen Stoyanchev
2016-10-31 12:23:53 +02:00
parent c4ee876416
commit b55ab1119e
22 changed files with 367 additions and 104 deletions

View File

@@ -87,7 +87,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
* @return Publisher that produces a single HandlerResult or an error signal;
* never throws an exception
*/
public Mono<HandlerResult> invokeForRequest(ServerWebExchange exchange,
public Mono<HandlerResult> invoke(ServerWebExchange exchange,
BindingContext bindingContext, Object... providedArgs) {
return resolveArguments(exchange, bindingContext, providedArgs).then(args -> {

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.method;
import java.util.Optional;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.web.server.ServerWebExchange;
/**
* An extension of {@link HandlerMethodArgumentResolver} for implementations
* that are synchronous in nature and do not block to resolve values.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public interface SyncHandlerMethodArgumentResolver extends HandlerMethodArgumentResolver {
@Override
default Mono<Object> resolveArgument(MethodParameter parameter, BindingContext context,
ServerWebExchange exchange) {
Optional<Object> value = resolveArgumentValue(parameter, context, exchange);
return Mono.justOrEmpty(value);
}
/**
* Resolve the method argument value synchronously returning an optional value.
* @param parameter the method parameter
* @param bindingContext the binding context to use
* @param exchange the current exchange
* @return the resolved value if any
*/
Optional<Object> resolveArgumentValue(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange);
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.method;
import java.lang.reflect.Method;
import java.util.List;
import org.springframework.util.Assert;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.server.ServerWebExchange;
/**
* An extension of {@code InvocableHandlerMethod} for use with
* {@link SyncHandlerMethodArgumentResolver}s which in turn enables synchronous
* handler method invocation via {@link #invokeForHandlerResult}.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public class SyncInvocableHandlerMethod extends InvocableHandlerMethod {
public SyncInvocableHandlerMethod(HandlerMethod handlerMethod) {
super(handlerMethod);
}
public SyncInvocableHandlerMethod(Object bean, Method method) {
super(bean, method);
}
/**
* Overloaded variant of the same setter from the base class that ensures
* all resolvers are {@link SyncHandlerMethodArgumentResolver}.
*/
@Override
public void setHandlerMethodArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.forEach(resolver ->
Assert.isInstanceOf(SyncHandlerMethodArgumentResolver.class, resolver,
"Expected sync argument resolver: " + resolver.getClass().getName()));
super.setHandlerMethodArgumentResolvers(resolvers);
}
/**
* Delegate to the base class {@link #invoke} and also wait for the result.
* Since all argument resolvers are synchronous this won't actually block.
*/
public HandlerResult invokeForHandlerResult(ServerWebExchange exchange,
BindingContext bindingContext, Object... providedArgs) {
// This will not block
return super.invoke(exchange, bindingContext, providedArgs).block();
}
}

View File

@@ -53,7 +53,7 @@ import org.springframework.web.server.ServerWebInputException;
* @author Rossen Stoyanchev
* @since 5.0
*/
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
public abstract class AbstractNamedValueArgumentResolver implements HandlerMethodArgumentResolver {
private final ConfigurableBeanFactory configurableBeanFactory;
@@ -67,7 +67,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
* and #{...} SpEL expressions in default values, or {@code null} if default
* values are not expected to contain expressions
*/
public AbstractNamedValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
public AbstractNamedValueArgumentResolver(ConfigurableBeanFactory beanFactory) {
this.configurableBeanFactory = beanFactory;
this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, null) : null);
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.method.annotation;
import java.util.Optional;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.web.reactive.result.method.BindingContext;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
/**
* An extension of {@link AbstractNamedValueArgumentResolver} for named value
* resolvers that are synchronous and yet non-blocking. Sub-classes implement
* the synchronous {@link #resolveNamedValue} to which the asynchronous
* {@link #resolveName} delegates to by default.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public abstract class AbstractNamedValueSyncArgumentResolver extends AbstractNamedValueArgumentResolver
implements SyncHandlerMethodArgumentResolver {
public AbstractNamedValueSyncArgumentResolver(ConfigurableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
public Optional<Object> resolveArgumentValue(MethodParameter parameter,
BindingContext bindingContext, ServerWebExchange exchange) {
// This will not block
Object value = resolveArgument(parameter, bindingContext, exchange).block();
return Optional.ofNullable(value);
}
@Override
protected Mono<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
return Mono.justOrEmpty(resolveNamedValue(name, parameter, exchange));
}
/**
* An abstract method for synchronous resolution of method argument values
* that sub-classes must implement.
* @param name the name of the value being resolved
* @param parameter the method parameter to resolve to an argument value
* (pre-nested in case of a {@link java.util.Optional} declaration)
* @param exchange the current exchange
* @return the resolved argument value, if any
*/
protected abstract Optional<Object> resolveNamedValue(String name, MethodParameter parameter,
ServerWebExchange exchange);
}

View File

@@ -16,7 +16,7 @@
package org.springframework.web.reactive.result.method.annotation;
import reactor.core.publisher.Mono;
import java.util.Optional;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
@@ -35,7 +35,7 @@ import org.springframework.web.server.ServerWebInputException;
* @author Rossen Stoyanchev
* @since 5.0
*/
public class CookieValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
public class CookieValueMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver {
/**
@@ -60,16 +60,19 @@ public class CookieValueMethodArgumentResolver extends AbstractNamedValueMethodA
}
@Override
protected Mono<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter,
ServerWebExchange exchange) {
HttpCookie cookie = exchange.getRequest().getCookies().getFirst(name);
if (HttpCookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
return Mono.justOrEmpty(cookie);
Class<?> paramType = parameter.getNestedParameterType();
if (HttpCookie.class.isAssignableFrom(paramType)) {
return Optional.ofNullable(cookie);
}
else if (cookie != null) {
return Mono.justOrEmpty(cookie.getValue());
return Optional.ofNullable(cookie.getValue());
}
else {
return Mono.empty();
return Optional.empty();
}
}

View File

@@ -16,7 +16,7 @@
package org.springframework.web.reactive.result.method.annotation;
import reactor.core.publisher.Mono;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@@ -33,7 +33,7 @@ import org.springframework.web.server.ServerWebExchange;
* @author Rossen Stoyanchev
* @since 5.0
*/
public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver {
/**
@@ -58,9 +58,11 @@ public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMet
}
@Override
protected Mono<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter,
ServerWebExchange exchange) {
// No name to resolve
return Mono.empty();
return Optional.empty();
}
@Override

View File

@@ -15,12 +15,12 @@
*/
package org.springframework.web.reactive.result.method.annotation;
import reactor.core.publisher.Mono;
import java.util.Optional;
import org.springframework.core.MethodParameter;
import org.springframework.ui.Model;
import org.springframework.web.reactive.result.method.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -29,7 +29,7 @@ import org.springframework.web.server.ServerWebExchange;
* @author Rossen Stoyanchev
* @since 5.0
*/
public class ModelArgumentResolver implements HandlerMethodArgumentResolver {
public class ModelArgumentResolver implements SyncHandlerMethodArgumentResolver {
@Override
@@ -38,10 +38,10 @@ public class ModelArgumentResolver implements HandlerMethodArgumentResolver {
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
public Optional<Object> resolveArgumentValue(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
return Mono.just(bindingContext.getModel());
return Optional.of(bindingContext.getModel());
}
}

View File

@@ -20,14 +20,12 @@ import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.result.method.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -40,7 +38,7 @@ import org.springframework.web.server.ServerWebExchange;
* @since 5.0
* @see PathVariableMethodArgumentResolver
*/
public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
public class PathVariableMapMethodArgumentResolver implements SyncHandlerMethodArgumentResolver {
@Override
@@ -54,12 +52,12 @@ public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgum
* Return a Map with all URI template variables or an empty map.
*/
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
public Optional<Object> resolveArgumentValue(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
Optional<Object> value = exchange.getAttribute(name);
return (value.isPresent() ? Mono.just(value.get()) : Mono.just(Collections.emptyMap()));
Object value = exchange.getAttribute(name).orElse(Collections.emptyMap());
return Optional.of(value);
}
}

View File

@@ -19,8 +19,6 @@ package org.springframework.web.reactive.result.method.annotation;
import java.util.Map;
import java.util.Optional;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.converter.Converter;
@@ -51,7 +49,7 @@ import org.springframework.web.server.ServerWebExchange;
* @since 5.0
* @see PathVariableMapMethodArgumentResolver
*/
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
public class PathVariableMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver {
public PathVariableMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
@@ -79,14 +77,12 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
@Override
@SuppressWarnings("unchecked")
protected Mono<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter,
ServerWebExchange exchange) {
String attributeName = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
Optional<Object> optional = exchange.getAttribute(attributeName);
Object value = null;
if (optional.isPresent()) {
value = ((Map<String, String>) optional.get()).get(name);
}
return Mono.justOrEmpty(value);
return exchange.getAttribute(attributeName)
.map(value -> ((Map<String, String>) value).get(name));
}
@Override

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.web.reactive.result.method.annotation;
import reactor.core.publisher.Mono;
import java.util.Optional;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
@@ -31,7 +31,7 @@ import org.springframework.web.server.ServerWebInputException;
* @since 5.0
* @see SessionAttributeMethodArgumentResolver
*/
public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver {
public RequestAttributeMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
@@ -52,8 +52,10 @@ public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueMe
}
@Override
protected Mono<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange){
return Mono.justOrEmpty(exchange.getAttribute(name));
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter,
ServerWebExchange exchange) {
return exchange.getAttribute(name);
}
@Override

View File

@@ -17,15 +17,14 @@
package org.springframework.web.reactive.result.method.annotation;
import java.util.Map;
import reactor.core.publisher.Mono;
import java.util.Optional;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.reactive.result.method.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -41,7 +40,7 @@ import org.springframework.web.server.ServerWebExchange;
* @since 5.0
* @see RequestHeaderMethodArgumentResolver
*/
public class RequestHeaderMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
public class RequestHeaderMapMethodArgumentResolver implements SyncHandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
@@ -50,16 +49,17 @@ public class RequestHeaderMapMethodArgumentResolver implements HandlerMethodArgu
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
public Optional<Object> resolveArgumentValue(MethodParameter parameter, BindingContext context,
ServerWebExchange exchange) {
HttpHeaders headers = exchange.getRequest().getHeaders();
if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
return Mono.just(headers);
}
else {
return Mono.just(headers.toSingleValueMap());
}
Object value = (isMultiValueMap(parameter) ? headers : headers.toSingleValueMap());
return Optional.of(value);
}
private boolean isMultiValueMap(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return MultiValueMap.class.isAssignableFrom(paramType);
}
}

View File

@@ -18,8 +18,7 @@ package org.springframework.web.reactive.result.method.annotation;
import java.util.List;
import java.util.Map;
import reactor.core.publisher.Mono;
import java.util.Optional;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
@@ -44,8 +43,7 @@ import org.springframework.web.server.ServerWebInputException;
* @since 5.0
* @see RequestHeaderMapMethodArgumentResolver
*/
public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver {
/**
* @param beanFactory a bean factory to use for resolving ${...}
@@ -70,13 +68,15 @@ public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMetho
}
@Override
protected Mono<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter,
ServerWebExchange exchange) {
List<String> headerValues = exchange.getRequest().getHeaders().get(name);
Object result = null;
if (headerValues != null) {
result = (headerValues.size() == 1 ? headerValues.get(0) : headerValues);
}
return Mono.justOrEmpty(result);
return Optional.ofNullable(result);
}
@Override

View File

@@ -192,6 +192,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
resolvers.add(new HttpEntityArgumentResolver(getMessageReaders(), adapterRegistry));
resolvers.add(new ModelArgumentResolver());
resolvers.add(new ServerWebExchangeArgumentResolver());
resolvers.add(new WebSessionArgumentResolver());
// Custom resolvers
if (getCustomArgumentResolvers() != null) {
@@ -214,7 +215,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
invocable.setHandlerMethodArgumentResolvers(getArgumentResolvers());
BindingContext bindingContext = new BindingContext(getWebBindingInitializer());
return invocable.invokeForRequest(exchange, bindingContext)
return invocable.invoke(exchange, bindingContext)
.map(result -> result.setExceptionHandler(
ex -> handleException(ex, handlerMethod, bindingContext, exchange)))
.otherwise(ex -> handleException(
@@ -232,7 +233,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
}
invocable.setHandlerMethodArgumentResolvers(getArgumentResolvers());
bindingContext.getModel().clear();
return invocable.invokeForRequest(exchange, bindingContext, ex);
return invocable.invoke(exchange, bindingContext, ex);
}
catch (Throwable invocationEx) {
if (logger.isWarnEnabled()) {

View File

@@ -17,15 +17,14 @@
package org.springframework.web.reactive.result.method.annotation;
import java.util.Map;
import reactor.core.publisher.Mono;
import java.util.Optional;
import org.springframework.core.MethodParameter;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.reactive.result.method.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -43,7 +42,7 @@ import org.springframework.web.server.ServerWebExchange;
* @since 5.0
* @see RequestParamMethodArgumentResolver
*/
public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
public class RequestParamMapMethodArgumentResolver implements SyncHandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
@@ -57,16 +56,17 @@ public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgum
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
public Optional<Object> resolveArgumentValue(MethodParameter parameter, BindingContext context,
ServerWebExchange exchange) {
Class<?> paramType = parameter.getParameterType();
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
if (MultiValueMap.class.isAssignableFrom(paramType)) {
return Mono.just(queryParams);
}
else {
return Mono.just(queryParams.toSingleValueMap());
}
Object value = (isMultiValueMap(parameter) ? queryParams : queryParams.toSingleValueMap());
return Optional.of(value);
}
private boolean isMultiValueMap(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return MultiValueMap.class.isAssignableFrom(paramType);
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.web.reactive.result.method.annotation;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import reactor.core.publisher.Mono;
@@ -50,7 +51,7 @@ import org.springframework.web.server.ServerWebInputException;
* @since 5.0
* @see RequestParamMapMethodArgumentResolver
*/
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
public class RequestParamMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver {
private final boolean useDefaultResolution;
@@ -91,13 +92,15 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
}
@Override
protected Mono<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
protected Optional<Object> resolveNamedValue(String name, MethodParameter parameter,
ServerWebExchange exchange) {
List<String> paramValues = exchange.getRequest().getQueryParams().get(name);
Object result = null;
if (paramValues != null) {
result = (paramValues.size() == 1 ? paramValues.get(0) : paramValues);
}
return Mono.justOrEmpty(result);
return Optional.ofNullable(result);
}
@Override

View File

@@ -15,31 +15,32 @@
*/
package org.springframework.web.reactive.result.method.annotation;
import reactor.core.publisher.Mono;
import java.util.Optional;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.result.method.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
/**
* Resolves ServerWebExchange-related method argument values of the following types:
* <ul>
* <li>{@link ServerWebExchange}
* <li>{@link ServerHttpRequest}
* <li>{@link WebSession}
* <li>{@link HttpMethod}
* <li>{@link ServerHttpResponse}
* </ul>
*
* <p>For the {@code WebSession} see {@link WebSessionArgumentResolver}.
*
* @author Rossen Stoyanchev
* @since 5.0
* @see WebSessionArgumentResolver
*/
public class ServerWebExchangeArgumentResolver implements HandlerMethodArgumentResolver {
public class ServerWebExchangeArgumentResolver implements SyncHandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
@@ -47,35 +48,33 @@ public class ServerWebExchangeArgumentResolver implements HandlerMethodArgumentR
return (ServerWebExchange.class.isAssignableFrom(paramType) ||
ServerHttpRequest.class.isAssignableFrom(paramType) ||
ServerHttpResponse.class.isAssignableFrom(paramType) ||
WebSession.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType);
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
public Optional<Object> resolveArgumentValue(MethodParameter parameter, BindingContext context,
ServerWebExchange exchange) {
Class<?> paramType = parameter.getParameterType();
Object value;
if (ServerWebExchange.class.isAssignableFrom(paramType)) {
return Mono.just(exchange);
value = exchange;
}
else if (ServerHttpRequest.class.isAssignableFrom(paramType)) {
return Mono.just(exchange.getRequest());
value = exchange.getRequest();
}
else if (ServerHttpResponse.class.isAssignableFrom(paramType)) {
return Mono.just(exchange.getResponse());
}
else if (WebSession.class.isAssignableFrom(paramType)) {
return exchange.getSession().cast(Object.class);
value = exchange.getResponse();
}
else if (HttpMethod.class == paramType) {
return Mono.just(exchange.getRequest().getMethod());
value = exchange.getRequest().getMethod();
}
else {
// should never happen...
return Mono.error(new UnsupportedOperationException(
"Unknown parameter type: " + paramType + " in method: " + parameter.getMethod()));
throw new IllegalArgumentException(
"Unknown parameter type: " + paramType + " in method: " + parameter.getMethod());
}
return Optional.of(value);
}
}

View File

@@ -33,7 +33,7 @@ import org.springframework.web.server.ServerWebInputException;
* @since 5.0
* @see RequestAttributeMethodArgumentResolver
*/
public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueArgumentResolver {
public SessionAttributeMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
@@ -53,9 +53,13 @@ public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueMe
}
@Override
protected Mono<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange){
return exchange.getSession().map(session -> session.getAttribute(name))
.filter(Optional::isPresent).map(Optional::get);
protected Mono<Object> resolveName(String name, MethodParameter parameter,
ServerWebExchange exchange) {
return exchange.getSession()
.map(session -> session.getAttribute(name))
.filter(Optional::isPresent)
.map(Optional::get);
}
@Override

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.method.annotation;
import java.util.Optional;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.result.method.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
/**
* Resolves method argument value of type {@link WebSession}.
*
* @author Rossen Stoyanchev
* @since 5.0
* @see ServerWebExchangeArgumentResolver
*/
public class WebSessionArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (WebSession.class.isAssignableFrom(parameter.getParameterType()));
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext context,
ServerWebExchange exchange) {
Class<?> paramType = parameter.getParameterType();
if (WebSession.class.isAssignableFrom(paramType)) {
return exchange.getSession().cast(Object.class);
}
else {
// should never happen...
throw new IllegalArgumentException(
"Unknown parameter type: " + paramType + " in method: " + parameter.getMethod());
}
}
}

View File

@@ -64,7 +64,7 @@ public class InvocableHandlerMethodTests {
@Test
public void invokeMethodWithNoArguments() throws Exception {
InvocableHandlerMethod hm = handlerMethod("noArgs");
Mono<HandlerResult> mono = hm.invokeForRequest(this.exchange, new BindingContext());
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
assertHandlerResultValue(mono, "success");
}
@@ -73,7 +73,7 @@ public class InvocableHandlerMethodTests {
public void invokeMethodWithNoValue() throws Exception {
InvocableHandlerMethod hm = handlerMethod("singleArg");
addResolver(hm, Mono.empty());
Mono<HandlerResult> mono = hm.invokeForRequest(this.exchange, new BindingContext());
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
assertHandlerResultValue(mono, "success:null");
}
@@ -82,7 +82,7 @@ public class InvocableHandlerMethodTests {
public void invokeMethodWithValue() throws Exception {
InvocableHandlerMethod hm = handlerMethod("singleArg");
addResolver(hm, Mono.just("value1"));
Mono<HandlerResult> mono = hm.invokeForRequest(this.exchange, new BindingContext());
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
assertHandlerResultValue(mono, "success:value1");
}
@@ -90,7 +90,7 @@ public class InvocableHandlerMethodTests {
@Test
public void noMatchingResolver() throws Exception {
InvocableHandlerMethod hm = handlerMethod("singleArg");
Mono<HandlerResult> mono = hm.invokeForRequest(this.exchange, new BindingContext());
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
ScriptedSubscriber.create().expectNextCount(0)
.consumeErrorWith(error -> {
@@ -105,7 +105,7 @@ public class InvocableHandlerMethodTests {
public void resolverThrowsException() throws Exception {
InvocableHandlerMethod hm = handlerMethod("singleArg");
addResolver(hm, Mono.error(new UnsupportedMediaTypeStatusException("boo")));
Mono<HandlerResult> mono = hm.invokeForRequest(this.exchange, new BindingContext());
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
ScriptedSubscriber.create().expectNextCount(0)
.consumeErrorWith(error -> {
@@ -119,7 +119,7 @@ public class InvocableHandlerMethodTests {
public void illegalArgumentExceptionIsWrappedWithInvocationDetails() throws Exception {
InvocableHandlerMethod hm = handlerMethod("singleArg");
addResolver(hm, Mono.just(1));
Mono<HandlerResult> mono = hm.invokeForRequest(this.exchange, new BindingContext());
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
ScriptedSubscriber.create().expectNextCount(0)
.consumeErrorWith(error -> {
@@ -134,7 +134,7 @@ public class InvocableHandlerMethodTests {
@Test
public void invocationTargetExceptionIsUnwrapped() throws Exception {
InvocableHandlerMethod hm = handlerMethod("exceptionMethod");
Mono<HandlerResult> mono = hm.invokeForRequest(this.exchange, new BindingContext());
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
ScriptedSubscriber.create().expectNextCount(0)
.consumeErrorWith(error -> {

View File

@@ -380,7 +380,7 @@ public class RequestMappingInfoHandlerMappingTests {
BindingContext bindingContext = new BindingContext();
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
Mono<HandlerResult> mono = invocable.invokeForRequest(exchange, bindingContext);
Mono<HandlerResult> mono = invocable.invoke(exchange, bindingContext);
HandlerResult result = mono.block();
assertNotNull(result);

View File

@@ -69,7 +69,6 @@ public class ServerWebExchangeArgumentResolverTests {
assertTrue(this.resolver.supportsParameter(parameter(ServerWebExchange.class)));
assertTrue(this.resolver.supportsParameter(parameter(ServerHttpRequest.class)));
assertTrue(this.resolver.supportsParameter(parameter(ServerHttpResponse.class)));
assertTrue(this.resolver.supportsParameter(parameter(WebSession.class)));
assertTrue(this.resolver.supportsParameter(parameter(HttpMethod.class)));
assertFalse(this.resolver.supportsParameter(parameter(String.class)));
}
@@ -79,7 +78,6 @@ public class ServerWebExchangeArgumentResolverTests {
testResolveArgument(parameter(ServerWebExchange.class), this.exchange);
testResolveArgument(parameter(ServerHttpRequest.class), this.exchange.getRequest());
testResolveArgument(parameter(ServerHttpResponse.class), this.exchange.getResponse());
testResolveArgument(parameter(WebSession.class), this.exchange.getSession().block());
testResolveArgument(parameter(HttpMethod.class), HttpMethod.GET);
}