Support Model as method argument and in HandlerResult

A model is created and passed to argument resolvers including a new
ModelArgumentResolver. The model is then exposed for result handling
as a field in HandlerResult.
This commit is contained in:
Rossen Stoyanchev
2016-02-02 14:30:05 -05:00
parent e92174c772
commit 14997eccf3
11 changed files with 129 additions and 40 deletions

View File

@@ -22,6 +22,8 @@ import java.util.function.Function;
import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
/**
@@ -37,6 +39,8 @@ public class HandlerResult {
private final ResolvableType returnValueType;
private final ModelMap model;
private Function<Throwable, Mono<HandlerResult>> exceptionHandler;
@@ -45,13 +49,16 @@ public class HandlerResult {
* @param handler the handler that handled the request
* @param returnValue the return value from the handler possibly {@code null}
* @param returnValueType the return value type
* @param model the model used for request handling
*/
public HandlerResult(Object handler, Object returnValue, ResolvableType returnValueType) {
public HandlerResult(Object handler, Object returnValue, ResolvableType returnValueType, ModelMap model) {
Assert.notNull(handler, "'handler' is required");
Assert.notNull(returnValueType, "'returnValueType' is required");
Assert.notNull(model, "'model' is required");
this.handler = handler;
this.returnValue = Optional.ofNullable(returnValue);
this.returnValueType = returnValueType;
this.model = new ExtendedModelMap();
}
@@ -76,6 +83,14 @@ public class HandlerResult {
return this.returnValueType;
}
/**
* Return the model used during request handling with attributes that may be
* used to render HTML templates with.
*/
public ModelMap getModel() {
return this.model;
}
/**
* Configure an exception handler that may be used to produce an alternative
* result when result handling fails. Especially for an async return value

View File

@@ -20,6 +20,8 @@ import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.ModelMap;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.HandlerAdapter;
import org.springframework.web.reactive.HandlerResult;
@@ -48,7 +50,8 @@ public class HttpHandlerHandlerAdapter implements HandlerAdapter {
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
WebHandler webHandler = (WebHandler) handler;
Mono<Void> completion = webHandler.handle(exchange);
return Mono.just(new HandlerResult(webHandler, completion, PUBLISHER_VOID));
ModelMap model = new ExtendedModelMap();
return Mono.just(new HandlerResult(webHandler, completion, PUBLISHER_VOID, model));
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.web.reactive.method;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.ui.ModelMap;
import org.springframework.web.server.ServerWebExchange;
@@ -34,7 +35,10 @@ public interface HandlerMethodArgumentResolver {
* The returned {@link Mono} may produce one or zero values if the argument
* does not resolve to any value, which will result in {@code null} passed
* as the argument value.
* @param parameter the method parameter
* @param model the implicit model for request handling
* @param exchange the current exchange
*/
Mono<Object> resolveArgument(MethodParameter parameter, ServerWebExchange exchange);
Mono<Object> resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange);
}

View File

@@ -32,6 +32,7 @@ import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ResolvableType;
import org.springframework.ui.ModelMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.method.HandlerMethod;
@@ -77,17 +78,20 @@ public class InvocableHandlerMethod extends HandlerMethod {
/**
* Invoke the method and return a Publisher for the return value.
* @param exchange the current exchange
* @param model the model for request handling
* @param providedArgs optional list of argument values to check by type
* (via {@code instanceof}) for resolving method arguments.
* @return Publisher that produces a single HandlerResult or an error signal;
* never throws an exception
*/
public Mono<HandlerResult> invokeForRequest(ServerWebExchange exchange, Object... providedArgs) {
return resolveArguments(exchange, providedArgs).then(args -> {
public Mono<HandlerResult> invokeForRequest(ServerWebExchange exchange, ModelMap model,
Object... providedArgs) {
return resolveArguments(exchange, model, providedArgs).then(args -> {
try {
Object value = doInvoke(args);
ResolvableType type = ResolvableType.forMethodParameter(getReturnType());
HandlerResult handlerResult = new HandlerResult(this, value, type);
HandlerResult handlerResult = new HandlerResult(this, value, type, model);
return Mono.just(handlerResult);
}
catch (InvocationTargetException ex) {
@@ -100,7 +104,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
});
}
private Mono<Object[]> resolveArguments(ServerWebExchange exchange, Object... providedArgs) {
private Mono<Object[]> resolveArguments(ServerWebExchange exchange, ModelMap model, Object... providedArgs) {
if (ObjectUtils.isEmpty(getMethodParameters())) {
return NO_ARGS;
}
@@ -121,7 +125,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
.findFirst()
.orElseThrow(() -> getArgError("No resolver for ", param, null));
try {
return resolver.resolveArgument(param, exchange)
return resolver.resolveArgument(param, model, exchange)
.defaultIfEmpty(NO_VALUE)
.otherwise(ex -> Mono.error(getArgError("Error resolving ", param, ex)))
.log("reactor.unresolved");

View File

@@ -0,0 +1,44 @@
/*
* 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.method.annotation;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.reactive.method.HandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
/**
* Resolver for the {@link Model} controller method argument.
*
* @author Rossen Stoyanchev
*/
public class ModelArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, ModelMap model, ServerWebExchange exchange) {
return Mono.just(model);
}
}

View File

@@ -28,6 +28,7 @@ import org.springframework.core.codec.Decoder;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.reactive.method.HandlerMethodArgumentResolver;
@@ -58,7 +59,9 @@ public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolve
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, ServerWebExchange exchange) {
public Mono<Object> resolveArgument(MethodParameter parameter, ModelMap model,
ServerWebExchange exchange) {
MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
if (mediaType == null) {
mediaType = MediaType.APPLICATION_OCTET_STREAM;

View File

@@ -38,6 +38,8 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.buffer.DataBufferAllocator;
import org.springframework.core.io.buffer.NettyDataBufferAllocator;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.ModelMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
@@ -64,7 +66,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Initializin
new NettyDataBufferAllocator(new UnpooledByteBufAllocator(false));
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
new ConcurrentHashMap<Class<?>, ExceptionHandlerMethodResolver>(64);
new ConcurrentHashMap<>(64);
/**
@@ -105,6 +107,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Initializin
this.argumentResolvers.add(new RequestParamArgumentResolver());
this.argumentResolvers.add(new RequestBodyArgumentResolver(decoders, this.conversionService));
this.argumentResolvers.add(new ModelArgumentResolver());
}
}
@@ -118,8 +121,8 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Initializin
HandlerMethod handlerMethod = (HandlerMethod) handler;
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
invocable.setHandlerMethodArgumentResolvers(this.argumentResolvers);
return invocable.invokeForRequest(exchange)
ModelMap model = new ExtendedModelMap();
return invocable.invokeForRequest(exchange, model)
.map(result -> result.setExceptionHandler(ex -> handleException(ex, handlerMethod, exchange)))
.otherwise(ex -> handleException(ex, handlerMethod, exchange));
}
@@ -135,7 +138,8 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Initializin
logger.debug("Invoking @ExceptionHandler method: " + invocable);
}
invocable.setHandlerMethodArgumentResolvers(getArgumentResolvers());
return invocable.invokeForRequest(exchange, ex);
ExtendedModelMap errorModel = new ExtendedModelMap();
return invocable.invokeForRequest(exchange, errorModel, ex);
}
catch (Exception invocationEx) {
if (logger.isErrorEnabled()) {

View File

@@ -19,6 +19,7 @@ package org.springframework.web.reactive.method.annotation;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.reactive.method.HandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
@@ -41,7 +42,7 @@ public class RequestParamArgumentResolver implements HandlerMethodArgumentResolv
@Override
public Mono<Object> resolveArgument(MethodParameter param, ServerWebExchange exchange) {
public Mono<Object> resolveArgument(MethodParameter param, ModelMap model, ServerWebExchange exchange) {
RequestParam annotation = param.getParameterAnnotation(RequestParam.class);
String name = (annotation.value().length() != 0 ? annotation.value() : param.getParameterName());
UriComponents uriComponents = UriComponentsBuilder.fromUri(exchange.getRequest().getURI()).build();