提交 a7582fcc 编写于 作者: R Rossen Stoyanchev

Polish method parameter handling

上级 5ae9afd5
......@@ -25,10 +25,12 @@ import java.util.Set;
import java.util.function.Supplier;
import org.springframework.core.Ordered;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.server.ServerWebExchange;
......@@ -72,6 +74,14 @@ public abstract class AbstractHandlerResultHandler implements Ordered {
return this.adapterRegistry;
}
/**
* Shortcut to get a ReactiveAdapter for the top-level return value type.
*/
protected ReactiveAdapter getAdapter(HandlerResult result) {
Class<?> returnType = result.getReturnType().getRawClass();
return getAdapterRegistry().getAdapter(returnType, result.getReturnValue());
}
/**
* Return the configured {@link RequestedContentTypeResolver}.
*/
......
......@@ -113,11 +113,7 @@ public abstract class AbstractMessageReaderArgumentResolver {
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(bodyType.resolve());
ResolvableType elementType = ResolvableType.forMethodParameter(bodyParameter);
if (adapter != null) {
elementType = elementType.getGeneric(0);
}
ResolvableType elementType = (adapter != null ? bodyType.getGeneric(0) : bodyType);
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
......@@ -139,8 +135,8 @@ public abstract class AbstractMessageReaderArgumentResolver {
else {
flux = reader.read(elementType, request, readHints);
}
flux = flux.onErrorResumeWith(ex -> Flux.error(wrapReadError(ex, bodyParameter)));
if (checkRequired(adapter, isBodyRequired)) {
flux = flux.onErrorResumeWith(ex -> Flux.error(getReadError(bodyParameter, ex)));
if (isBodyRequired || !adapter.supportsEmpty()) {
flux = flux.switchIfEmpty(Flux.error(getRequiredBodyError(bodyParameter)));
}
Object[] hints = extractValidationHints(bodyParameter);
......@@ -159,8 +155,8 @@ public abstract class AbstractMessageReaderArgumentResolver {
else {
mono = reader.readMono(elementType, request, readHints);
}
mono = mono.otherwise(ex -> Mono.error(wrapReadError(ex, bodyParameter)));
if (checkRequired(adapter, isBodyRequired)) {
mono = mono.otherwise(ex -> Mono.error(getReadError(bodyParameter, ex)));
if (isBodyRequired || (adapter != null && !adapter.supportsEmpty())) {
mono = mono.otherwiseIfEmpty(Mono.error(getRequiredBodyError(bodyParameter)));
}
Object[] hints = extractValidationHints(bodyParameter);
......@@ -181,15 +177,11 @@ public abstract class AbstractMessageReaderArgumentResolver {
return Mono.error(new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes));
}
protected ServerWebInputException wrapReadError(Throwable ex, MethodParameter parameter) {
private ServerWebInputException getReadError(MethodParameter parameter, Throwable ex) {
return new ServerWebInputException("Failed to read HTTP message", parameter, ex);
}
protected boolean checkRequired(ReactiveAdapter adapter, boolean isBodyRequired) {
return adapter != null && !adapter.supportsEmpty() || isBodyRequired;
}
protected ServerWebInputException getRequiredBodyError(MethodParameter parameter) {
private ServerWebInputException getRequiredBodyError(MethodParameter parameter) {
return new ServerWebInputException("Required request body is missing: " +
parameter.getMethod().toGenericString());
}
......@@ -199,7 +191,7 @@ public abstract class AbstractMessageReaderArgumentResolver {
* a (possibly empty) Object[] with validation hints. A return value of
* {@code null} indicates that validation is not required.
*/
protected Object[] extractValidationHints(MethodParameter parameter) {
private Object[] extractValidationHints(MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validAnnot = AnnotationUtils.getAnnotation(ann, Validated.class);
......@@ -211,8 +203,8 @@ public abstract class AbstractMessageReaderArgumentResolver {
return null;
}
protected void validate(Object target, Object[] validationHints,
MethodParameter param, BindingContext binding, ServerWebExchange exchange) {
private void validate(Object target, Object[] validationHints, MethodParameter param,
BindingContext binding, ServerWebExchange exchange) {
String name = Conventions.getVariableNameForParameter(param);
WebExchangeDataBinder binder = binding.createDataBinder(exchange, target, name);
......
......@@ -93,21 +93,19 @@ public abstract class AbstractMessageWriterResultHandler extends AbstractHandler
@SuppressWarnings("unchecked")
protected Mono<Void> writeBody(Object body, MethodParameter bodyParameter, ServerWebExchange exchange) {
ResolvableType valueType = ResolvableType.forMethodParameter(bodyParameter);
Class<?> valueClass = valueType.resolve();
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(valueClass, body);
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
Class<?> bodyClass = bodyType.resolve();
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(bodyClass, body);
Publisher<?> publisher;
ResolvableType elementType;
if (adapter != null) {
publisher = adapter.toPublisher(body);
elementType = adapter.isNoValue() ?
ResolvableType.forClass(Void.class) : valueType.getGeneric(0);
elementType = adapter.isNoValue() ? ResolvableType.forClass(Void.class) : bodyType.getGeneric(0);
}
else {
publisher = Mono.justOrEmpty(body);
elementType = (valueClass == null && body != null ?
ResolvableType.forInstance(body) : valueType);
elementType = (bodyClass == null && body != null ? ResolvableType.forInstance(body) : bodyType);
}
if (void.class == elementType.getRawClass() || Void.class == elementType.getRawClass()) {
......@@ -122,7 +120,7 @@ public abstract class AbstractMessageWriterResultHandler extends AbstractHandler
if (messageWriter.canWrite(elementType, bestMediaType)) {
return (messageWriter instanceof ServerHttpMessageWriter ?
((ServerHttpMessageWriter<?>) messageWriter).write((Publisher) publisher,
valueType, elementType, bestMediaType, request, response, Collections.emptyMap()) :
bodyType, elementType, bestMediaType, request, response, Collections.emptyMap()) :
messageWriter.write((Publisher) publisher, elementType,
bestMediaType, response, Collections.emptyMap()));
}
......
......@@ -22,9 +22,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
......@@ -69,29 +67,20 @@ public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentRes
}
@Override
public Mono<Object> resolveArgument(MethodParameter param, BindingContext bindingContext,
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
ResolvableType entityType = ResolvableType.forMethodParameter(param);
MethodParameter bodyParameter = new MethodParameter(param);
bodyParameter.increaseNestingLevel();
Class<?> entityType = parameter.getParameterType();
return readBody(bodyParameter, false, bindingContext, exchange)
.map(body -> createHttpEntity(body, entityType, exchange))
.defaultIfEmpty(createHttpEntity(null, entityType, exchange));
return readBody(parameter.nested(), false, bindingContext, exchange)
.map(body -> createEntity(body, entityType, exchange.getRequest()))
.defaultIfEmpty(createEntity(null, entityType, exchange.getRequest()));
}
private Object createHttpEntity(Object body, ResolvableType entityType,
ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
if (RequestEntity.class == entityType.getRawClass()) {
return new RequestEntity<>(body, headers, request.getMethod(), request.getURI());
}
else {
return new HttpEntity<>(body, headers);
}
private Object createEntity(Object body, Class<?> entityType, ServerHttpRequest request) {
return RequestEntity.class.equals(entityType) ?
new RequestEntity<>(body, request.getHeaders(), request.getMethod(), request.getURI()) :
new HttpEntity<>(body, request.getHeaders());
}
}
......@@ -28,6 +28,7 @@ import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
......@@ -122,9 +123,10 @@ public class ModelAttributeMethodArgumentResolver implements HandlerMethodArgume
ResolvableType type = ResolvableType.forMethodParameter(parameter);
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type.resolve());
Class<?> valueType = (adapter != null ? type.resolveGeneric(0) : parameter.getParameterType());
ResolvableType valueType = (adapter != null ? type.getGeneric(0) : type);
String name = getAttributeName(valueType, parameter);
Mono<?> valueMono = getAttributeMono(name, valueType, parameter, context, exchange);
Mono<?> valueMono = getAttributeMono(name, valueType, context.getModel());
Map<String, Object> model = context.getModel().asMap();
MonoProcessor<BindingResult> bindingResultMono = MonoProcessor.create();
......@@ -146,10 +148,10 @@ public class ModelAttributeMethodArgumentResolver implements HandlerMethodArgume
if (adapter != null) {
return adapter.fromPublisher(errors.hasErrors() ?
Mono.error(new WebExchangeBindException(parameter, errors)) :
Mono.just(value));
valueMono);
}
else {
if (errors.hasErrors() && checkErrorsArgument(parameter)) {
if (errors.hasErrors() && !hasErrorsArgument(parameter)) {
throw new WebExchangeBindException(parameter, errors);
}
return value;
......@@ -158,46 +160,37 @@ public class ModelAttributeMethodArgumentResolver implements HandlerMethodArgume
});
}
private String getAttributeName(Class<?> valueType, MethodParameter parameter) {
private String getAttributeName(ResolvableType valueType, MethodParameter parameter) {
ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);
if (annot != null && StringUtils.hasText(annot.value())) {
return annot.value();
}
// TODO: Conventions does not deal with async wrappers
return ClassUtils.getShortNameAsProperty(valueType);
return ClassUtils.getShortNameAsProperty(valueType.getRawClass());
}
private Mono<?> getAttributeMono(String attributeName, Class<?> attributeType,
MethodParameter param, BindingContext context, ServerWebExchange exchange) {
Object attribute = context.getModel().asMap().get(attributeName);
private Mono<?> getAttributeMono(String attributeName, ResolvableType attributeType, Model model) {
Object attribute = model.asMap().get(attributeName);
if (attribute == null) {
attribute = createAttribute(attributeName, attributeType, param, context, exchange);
attribute = BeanUtils.instantiateClass(attributeType.getRawClass());
}
if (attribute != null) {
ReactiveAdapter adapterFrom = getAdapterRegistry().getAdapter(null, attribute);
if (adapterFrom != null) {
Assert.isTrue(!adapterFrom.isMultiValue(), "Data binding supports single-value async types.");
return Mono.from(adapterFrom.toPublisher(attribute));
}
ReactiveAdapter adapterFrom = getAdapterRegistry().getAdapter(null, attribute);
if (adapterFrom != null) {
Assert.isTrue(!adapterFrom.isMultiValue(), "Data binding supports single-value async types.");
return Mono.from(adapterFrom.toPublisher(attribute));
}
else {
return Mono.justOrEmpty(attribute);
}
return Mono.justOrEmpty(attribute);
}
protected Object createAttribute(String attributeName, Class<?> attributeType,
MethodParameter parameter, BindingContext context, ServerWebExchange exchange) {
return BeanUtils.instantiateClass(attributeType);
}
protected boolean checkErrorsArgument(MethodParameter methodParam) {
private boolean hasErrorsArgument(MethodParameter methodParam) {
int i = methodParam.getParameterIndex();
Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
return paramTypes.length <= (i + 1) || !Errors.class.isAssignableFrom(paramTypes[i + 1]);
return paramTypes.length > i && Errors.class.isAssignableFrom(paramTypes[i + 1]);
}
protected void validateIfApplicable(WebExchangeDataBinder binder, MethodParameter parameter) {
private void validateIfApplicable(WebExchangeDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validAnnot = AnnotationUtils.getAnnotation(ann, Validated.class);
......
......@@ -43,9 +43,10 @@ public class PathVariableMapMethodArgumentResolver implements SyncHandlerMethodA
@Override
public boolean supportsParameter(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
return (ann != null && (Map.class.isAssignableFrom(parameter.getParameterType()))
&& !StringUtils.hasText(ann.value()));
PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class);
return (annotation != null &&
Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(annotation.value()));
}
@Override
......
......@@ -74,8 +74,8 @@ public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentRe
public Mono<Object> resolveArgument(MethodParameter param, BindingContext bindingContext,
ServerWebExchange exchange) {
boolean isRequired = param.getParameterAnnotation(RequestBody.class).required();
return readBody(param, isRequired, bindingContext, exchange);
RequestBody annotation = param.getParameterAnnotation(RequestBody.class);
return readBody(param, annotation.required(), bindingContext, exchange);
}
}
......@@ -55,31 +55,26 @@ public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandle
/**
* Constructor with {@link HttpMessageWriter}s and a
* {@code RequestedContentTypeResolver}.
*
* @param messageWriters writers for serializing to the response body stream
* @param contentTypeResolver for resolving the requested content type
* Basic constructor with a default {@link ReactiveAdapterRegistry}.
* @param writers writers for serializing to the response body
* @param resolver to determine the requested content type
*/
public ResponseBodyResultHandler(List<HttpMessageWriter<?>> messageWriters,
RequestedContentTypeResolver contentTypeResolver) {
public ResponseBodyResultHandler(List<HttpMessageWriter<?>> writers,
RequestedContentTypeResolver resolver) {
this(messageWriters, contentTypeResolver, new ReactiveAdapterRegistry());
this(writers, resolver, new ReactiveAdapterRegistry());
}
/**
* Constructor with an additional {@link ReactiveAdapterRegistry}.
*
* @param messageWriters writers for serializing to the response body stream
* @param contentTypeResolver for resolving the requested content type
* @param adapterRegistry for adapting other reactive types (e.g. rx.Observable,
* rx.Single, etc.) to Flux or Mono
* Constructor with an {@link ReactiveAdapterRegistry} instance.
* @param writers writers for serializing to the response body
* @param resolver to determine the requested content type
* @param registry for adaptation to reactive types
*/
public ResponseBodyResultHandler(List<HttpMessageWriter<?>> messageWriters,
RequestedContentTypeResolver contentTypeResolver,
ReactiveAdapterRegistry adapterRegistry) {
public ResponseBodyResultHandler(List<HttpMessageWriter<?>> writers,
RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) {
super(messageWriters, contentTypeResolver, adapterRegistry);
super(writers, resolver, registry);
setOrder(100);
}
......@@ -87,32 +82,11 @@ public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandle
@Override
public boolean supports(HandlerResult result) {
MethodParameter parameter = result.getReturnTypeSource();
return hasResponseBodyAnnotation(parameter) && !isHttpEntityType(result);
}
private boolean hasResponseBodyAnnotation(MethodParameter parameter) {
Class<?> containingClass = parameter.getContainingClass();
return (AnnotationUtils.findAnnotation(containingClass, ResponseBody.class) != null ||
parameter.getMethodAnnotation(ResponseBody.class) != null);
}
private boolean isHttpEntityType(HandlerResult result) {
Class<?> rawClass = result.getReturnType().getRawClass();
if (HttpEntity.class.isAssignableFrom(rawClass)) {
return true;
}
else {
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(rawClass, result.getReturnValue());
if (adapter != null && !adapter.isNoValue()) {
ResolvableType genericType = result.getReturnType().getGeneric(0);
if (HttpEntity.class.isAssignableFrom(genericType.getRawClass())) {
return true;
}
}
}
return false;
}
@Override
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
Object body = result.getReturnValue().orElse(null);
......
......@@ -18,7 +18,6 @@ package org.springframework.web.reactive.result.method.annotation;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import reactor.core.publisher.Mono;
......@@ -54,78 +53,61 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand
/**
* Constructor with {@link HttpMessageWriter}s and a
* {@code RequestedContentTypeResolver}.
*
* @param messageWriters writers for serializing to the response body stream
* @param contentTypeResolver for resolving the requested content type
* Basic constructor with a default {@link ReactiveAdapterRegistry}.
* @param writers writers for serializing to the response body
* @param resolver to determine the requested content type
*/
public ResponseEntityResultHandler(List<HttpMessageWriter<?>> messageWriters,
RequestedContentTypeResolver contentTypeResolver) {
public ResponseEntityResultHandler(List<HttpMessageWriter<?>> writers,
RequestedContentTypeResolver resolver) {
this(messageWriters, contentTypeResolver, new ReactiveAdapterRegistry());
this(writers, resolver, new ReactiveAdapterRegistry());
}
/**
* Constructor with an additional {@link ReactiveAdapterRegistry}.
*
* @param messageWriters writers for serializing to the response body stream
* @param contentTypeResolver for resolving the requested content type
* @param adapterRegistry for adapting other reactive types (e.g. rx.Observable,
* rx.Single, etc.) to Flux or Mono
* Constructor with an {@link ReactiveAdapterRegistry} instance.
* @param writers writers for serializing to the response body
* @param resolver to determine the requested content type
* @param registry for adaptation to reactive types
*/
public ResponseEntityResultHandler(List<HttpMessageWriter<?>> messageWriters,
RequestedContentTypeResolver contentTypeResolver,
ReactiveAdapterRegistry adapterRegistry) {
public ResponseEntityResultHandler(List<HttpMessageWriter<?>> writers,
RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) {
super(messageWriters, contentTypeResolver, adapterRegistry);
super(writers, resolver, registry);
setOrder(0);
}
@Override
public boolean supports(HandlerResult result) {
Class<?> returnType = result.getReturnType().getRawClass();
if (isSupportedType(returnType)) {
if (isSupportedType(result.getReturnType())) {
return true;
}
else {
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(returnType, result.getReturnValue());
if (adapter != null && !adapter.isMultiValue() && !adapter.isNoValue()) {
ResolvableType genericType = result.getReturnType().getGeneric(0);
return isSupportedType(genericType.getRawClass());
}
}
return false;
ReactiveAdapter adapter = getAdapter(result);
return adapter != null && !adapter.isNoValue() &&
isSupportedType(result.getReturnType().getGeneric(0));
}
private boolean isSupportedType(Class<?> clazz) {
private boolean isSupportedType(ResolvableType type) {
Class<?> clazz = type.getRawClass();
return (HttpEntity.class.isAssignableFrom(clazz) && !RequestEntity.class.isAssignableFrom(clazz));
}
@Override
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
ResolvableType returnType = result.getReturnType();
MethodParameter bodyType;
Mono<?> returnValueMono;
Optional<Object> optionalValue = result.getReturnValue();
Class<?> rawClass = returnType.getRawClass();
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(rawClass, optionalValue);
MethodParameter bodyParameter;
ReactiveAdapter adapter = getAdapter(result);
if (adapter != null) {
Assert.isTrue(!adapter.isMultiValue(), "Only a single ResponseEntity supported");
returnValueMono = Mono.from(adapter.toPublisher(optionalValue));
bodyType = new MethodParameter(result.getReturnTypeSource());
bodyType.increaseNestingLevel();
bodyType.increaseNestingLevel();
returnValueMono = Mono.from(adapter.toPublisher(result.getReturnValue()));
bodyParameter = result.getReturnTypeSource().nested().nested();
}
else {
returnValueMono = Mono.justOrEmpty(optionalValue);
bodyType = new MethodParameter(result.getReturnTypeSource());
bodyType.increaseNestingLevel();
returnValueMono = Mono.justOrEmpty(result.getReturnValue());
bodyParameter = result.getReturnTypeSource().nested();
}
return returnValueMono.then(returnValue -> {
......@@ -156,7 +138,7 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand
return exchange.getResponse().setComplete();
}
return writeBody(httpEntity.getBody(), bodyType, exchange);
return writeBody(httpEntity.getBody(), bodyParameter, exchange);
});
}
......
......@@ -22,7 +22,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import reactor.core.publisher.Flux;
......@@ -94,29 +93,27 @@ public class ViewResolutionResultHandler extends AbstractHandlerResultHandler
/**
* Constructor with {@link ViewResolver}s and a {@link RequestedContentTypeResolver}.
* @param resolvers the resolver to use
* @param contentTypeResolver for resolving the requested content type
* Basic constructor with a default {@link ReactiveAdapterRegistry}.
* @param viewResolvers the resolver to use
* @param contentTypeResolver to determine the requested content type
*/
public ViewResolutionResultHandler(List<ViewResolver> resolvers,
public ViewResolutionResultHandler(List<ViewResolver> viewResolvers,
RequestedContentTypeResolver contentTypeResolver) {
this(resolvers, contentTypeResolver, new ReactiveAdapterRegistry());
this(viewResolvers, contentTypeResolver, new ReactiveAdapterRegistry());
}
/**
* Constructor with {@code ViewResolver}s tand a {@code ConversionService}.
* @param resolvers the resolver to use
* @param contentTypeResolver for resolving the requested content type
* @param adapterRegistry for adapting from other reactive types (e.g.
* rx.Single) to Mono
* Constructor with an {@link ReactiveAdapterRegistry} instance.
* @param viewResolvers the view resolver to use
* @param contentTypeResolver to determine the requested content type
* @param registry for adaptation to reactive types
*/
public ViewResolutionResultHandler(List<ViewResolver> resolvers,
RequestedContentTypeResolver contentTypeResolver,
ReactiveAdapterRegistry adapterRegistry) {
public ViewResolutionResultHandler(List<ViewResolver> viewResolvers,
RequestedContentTypeResolver contentTypeResolver, ReactiveAdapterRegistry registry) {
super(contentTypeResolver, adapterRegistry);
this.viewResolvers.addAll(resolvers);
super(contentTypeResolver, registry);
this.viewResolvers.addAll(viewResolvers);
AnnotationAwareOrderComparator.sort(this.viewResolvers);
}
......@@ -148,113 +145,97 @@ public class ViewResolutionResultHandler extends AbstractHandlerResultHandler
@Override
public boolean supports(HandlerResult result) {
Class<?> clazz = result.getReturnType().getRawClass();
if (hasModelAttributeAnnotation(result)) {
if (hasModelAnnotation(result.getReturnTypeSource())) {
return true;
}
Optional<Object> optional = result.getReturnValue();
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(clazz, optional);
Class<?> type = result.getReturnType().getRawClass();
ReactiveAdapter adapter = getAdapter(result);
if (adapter != null) {
if (adapter.isNoValue()) {
return true;
}
else {
clazz = result.getReturnType().getGeneric(0).getRawClass();
return isSupportedType(clazz);
}
}
else if (isSupportedType(clazz)) {
return true;
type = result.getReturnType().getGeneric(0).getRawClass();
}
return false;
return (CharSequence.class.isAssignableFrom(type) || View.class.isAssignableFrom(type) ||
Model.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type) ||
!BeanUtils.isSimpleProperty(type));
}
private boolean hasModelAttributeAnnotation(HandlerResult result) {
MethodParameter returnType = result.getReturnTypeSource();
return returnType.hasMethodAnnotation(ModelAttribute.class);
}
private boolean isSupportedType(Class<?> clazz) {
return (CharSequence.class.isAssignableFrom(clazz) || View.class.isAssignableFrom(clazz) ||
Model.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz) ||
!BeanUtils.isSimpleProperty(clazz));
private boolean hasModelAnnotation(MethodParameter parameter) {
return parameter.hasMethodAnnotation(ModelAttribute.class);
}
@Override
@SuppressWarnings("unchecked")
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
Mono<Object> returnValueMono;
ResolvableType elementType;
ResolvableType parameterType = result.getReturnType();
Optional<Object> optional = result.getReturnValue();
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(parameterType.getRawClass(), optional);
Mono<Object> valueMono;
ResolvableType valueType;
ReactiveAdapter adapter = getAdapter(result);
if (adapter != null) {
Assert.isTrue(!adapter.isMultiValue(), "Only single-value async return type supported.");
returnValueMono = optional
.map(value -> Mono.from(adapter.toPublisher(value)))
.orElse(Mono.empty());
elementType = !adapter.isNoValue() ?
parameterType.getGeneric(0) : ResolvableType.forClass(Void.class);
valueMono = result.getReturnValue()
.map(value -> Mono.from(adapter.toPublisher(value))).orElse(Mono.empty());
valueType = adapter.isNoValue() ?
ResolvableType.forClass(Void.class) : result.getReturnType().getGeneric(0);
}
else {
returnValueMono = Mono.justOrEmpty(result.getReturnValue());
elementType = parameterType;
valueMono = Mono.justOrEmpty(result.getReturnValue());
valueType = result.getReturnType();
}
return returnValueMono
return valueMono
.otherwiseIfEmpty(exchange.isNotModified() ? Mono.empty() : NO_VALUE_MONO)
.then(returnValue -> {
Mono<List<View>> viewsMono;
Model model = result.getModel();
MethodParameter parameter = result.getReturnTypeSource();
Locale acceptLocale = exchange.getRequest().getHeaders().getAcceptLanguageAsLocale();
Locale locale = acceptLocale != null ? acceptLocale : Locale.getDefault();
Class<?> clazz = elementType.getRawClass();
Class<?> clazz = valueType.getRawClass();
if (clazz == null) {
clazz = returnValue.getClass();
}
if (returnValue == NO_VALUE || Void.class.equals(clazz) || void.class.equals(clazz)) {
viewsMono = resolveViews(getDefaultViewName(result, exchange), locale);
viewsMono = resolveViews(getDefaultViewName(exchange), locale);
}
else if (Model.class.isAssignableFrom(clazz)) {
model.addAllAttributes(((Model) returnValue).asMap());
viewsMono = resolveViews(getDefaultViewName(result, exchange), locale);
viewsMono = resolveViews(getDefaultViewName(exchange), locale);
}
else if (Map.class.isAssignableFrom(clazz)) {
model.addAllAttributes((Map<String, ?>) returnValue);
viewsMono = resolveViews(getDefaultViewName(result, exchange), locale);
viewsMono = resolveViews(getDefaultViewName(exchange), locale);
}
else if (View.class.isAssignableFrom(clazz)) {
viewsMono = Mono.just(Collections.singletonList((View) returnValue));
}
else if (CharSequence.class.isAssignableFrom(clazz) && !hasModelAttributeAnnotation(result)) {
else if (CharSequence.class.isAssignableFrom(clazz) && !hasModelAnnotation(parameter)) {
viewsMono = resolveViews(returnValue.toString(), locale);
}
else {
String name = getNameForReturnValue(clazz, result.getReturnTypeSource());
String name = getNameForReturnValue(clazz, parameter);
model.addAttribute(name, returnValue);
viewsMono = resolveViews(getDefaultViewName(result, exchange), locale);
viewsMono = resolveViews(getDefaultViewName(exchange), locale);
}
return resolveAsyncAttributes(model.asMap())
.doOnSuccess(aVoid -> addBindingResult(result, exchange))
.doOnSuccess(aVoid -> addBindingResult(result.getBindingContext(), exchange))
.then(viewsMono)
.then(views -> render(views, model.asMap(), exchange));
});
}
/**
* Select a default view name when a controller leaves the view unspecified.
* The default implementation strips the leading and trailing slash from the
* as well as any extension and uses that as the view name.
* Select a default view name when a controller did not specify it.
* Use the request path the leading and trailing slash stripped.
*/
protected String getDefaultViewName(HandlerResult result, ServerWebExchange exchange) {
private String getDefaultViewName(ServerWebExchange exchange) {
String path = this.pathHelper.getLookupPathForRequest(exchange);
if (path.startsWith("/")) {
path = path.substring(1);
......@@ -332,8 +313,7 @@ public class ViewResolutionResultHandler extends AbstractHandlerResultHandler
.then();
}
private void addBindingResult(HandlerResult result, ServerWebExchange exchange) {
BindingContext context = result.getBindingContext();
private void addBindingResult(BindingContext context, ServerWebExchange exchange) {
Map<String, Object> model = context.getModel().asMap();
model.keySet().stream()
.filter(name -> isBindingCandidate(name, model.get(name)))
......
......@@ -43,6 +43,7 @@ import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.method.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
......@@ -83,23 +84,26 @@ public class HttpEntityArgumentResolverTests {
@Test
public void supports() throws Exception {
testSupports(httpEntityType(String.class));
testSupports(httpEntityType(forClassWithGenerics(Mono.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(Single.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(io.reactivex.Single.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(Maybe.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(CompletableFuture.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(Flux.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(Observable.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(io.reactivex.Observable.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(Flowable.class, String.class)));
testSupports(forClassWithGenerics(RequestEntity.class, String.class));
testSupports(this.testMethod.arg(httpEntityType(String.class)));
testSupports(this.testMethod.arg(httpEntityType(Mono.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(Single.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(io.reactivex.Single.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(Maybe.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(CompletableFuture.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(Flux.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(Observable.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(io.reactivex.Observable.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(Flowable.class, String.class)));
testSupports(this.testMethod.arg(forClassWithGenerics(RequestEntity.class, String.class)));
}
private void testSupports(MethodParameter parameter) {
assertTrue(this.resolver.supportsParameter(parameter));
}
@Test
public void doesNotSupport() throws Exception {
ResolvableType type = ResolvableType.forClassWithGenerics(Mono.class, String.class);
assertFalse(this.resolver.supportsParameter(this.testMethod.arg(type)));
assertFalse(this.resolver.supportsParameter(this.testMethod.arg(Mono.class, String.class)));
assertFalse(this.resolver.supportsParameter(this.testMethod.arg(String.class)));
}
......@@ -113,7 +117,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void emptyBodyWithMono() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Mono.class, String.class));
ResolvableType type = httpEntityType(Mono.class, String.class);
HttpEntity<Mono<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody()).expectNextCount(0).expectComplete().verify();
......@@ -121,7 +125,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void emptyBodyWithFlux() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Flux.class, String.class));
ResolvableType type = httpEntityType(Flux.class, String.class);
HttpEntity<Flux<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody()).expectNextCount(0).expectComplete().verify();
......@@ -129,7 +133,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void emptyBodyWithSingle() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Single.class, String.class));
ResolvableType type = httpEntityType(Single.class, String.class);
HttpEntity<Single<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(RxReactiveStreams.toPublisher(entity.getBody()))
......@@ -140,7 +144,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void emptyBodyWithRxJava2Single() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(io.reactivex.Single.class, String.class));
ResolvableType type = httpEntityType(io.reactivex.Single.class, String.class);
HttpEntity<io.reactivex.Single<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody().toFlowable())
......@@ -151,7 +155,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void emptyBodyWithRxJava2Maybe() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Maybe.class, String.class));
ResolvableType type = httpEntityType(Maybe.class, String.class);
HttpEntity<Maybe<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody().toFlowable())
......@@ -162,7 +166,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void emptyBodyWithObservable() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Observable.class, String.class));
ResolvableType type = httpEntityType(Observable.class, String.class);
HttpEntity<Observable<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(RxReactiveStreams.toPublisher(entity.getBody()))
......@@ -173,7 +177,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void emptyBodyWithRxJava2Observable() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(io.reactivex.Observable.class, String.class));
ResolvableType type = httpEntityType(io.reactivex.Observable.class, String.class);
HttpEntity<io.reactivex.Observable<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody().toFlowable(BackpressureStrategy.BUFFER))
......@@ -184,7 +188,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void emptyBodyWithFlowable() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Flowable.class, String.class));
ResolvableType type = httpEntityType(Flowable.class, String.class);
HttpEntity<Flowable<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody())
......@@ -195,7 +199,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void emptyBodyWithCompletableFuture() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(CompletableFuture.class, String.class));
ResolvableType type = httpEntityType(CompletableFuture.class, String.class);
HttpEntity<CompletableFuture<String>> entity = resolveValueWithEmptyBody(type);
entity.getBody().whenComplete((body, ex) -> {
......@@ -217,7 +221,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void httpEntityWithMonoBody() throws Exception {
String body = "line1";
ResolvableType type = httpEntityType(forClassWithGenerics(Mono.class, String.class));
ResolvableType type = httpEntityType(Mono.class, String.class);
HttpEntity<Mono<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
......@@ -227,7 +231,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void httpEntityWithSingleBody() throws Exception {
String body = "line1";
ResolvableType type = httpEntityType(forClassWithGenerics(Single.class, String.class));
ResolvableType type = httpEntityType(Single.class, String.class);
HttpEntity<Single<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
......@@ -237,7 +241,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void httpEntityWithRxJava2SingleBody() throws Exception {
String body = "line1";
ResolvableType type = httpEntityType(forClassWithGenerics(io.reactivex.Single.class, String.class));
ResolvableType type = httpEntityType(io.reactivex.Single.class, String.class);
HttpEntity<io.reactivex.Single<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
......@@ -247,7 +251,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void httpEntityWithRxJava2MaybeBody() throws Exception {
String body = "line1";
ResolvableType type = httpEntityType(forClassWithGenerics(Maybe.class, String.class));
ResolvableType type = httpEntityType(Maybe.class, String.class);
HttpEntity<Maybe<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
......@@ -257,7 +261,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void httpEntityWithCompletableFutureBody() throws Exception {
String body = "line1";
ResolvableType type = httpEntityType(forClassWithGenerics(CompletableFuture.class, String.class));
ResolvableType type = httpEntityType(CompletableFuture.class, String.class);
HttpEntity<CompletableFuture<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
......@@ -267,7 +271,7 @@ public class HttpEntityArgumentResolverTests {
@Test
public void httpEntityWithFluxBody() throws Exception {
String body = "line1\nline2\nline3\n";
ResolvableType type = httpEntityType(forClassWithGenerics(Flux.class, String.class));
ResolvableType type = httpEntityType(Flux.class, String.class);
HttpEntity<Flux<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
......@@ -292,18 +296,13 @@ public class HttpEntityArgumentResolverTests {
}
private ResolvableType httpEntityType(Class<?> bodyType) {
return httpEntityType(ResolvableType.forClass(bodyType));
private ResolvableType httpEntityType(Class<?> bodyType, Class<?>... generics) {
return ResolvableType.forClassWithGenerics(HttpEntity.class,
ObjectUtils.isEmpty(generics) ?
ResolvableType.forClass(bodyType) :
ResolvableType.forClassWithGenerics(bodyType, generics));
}
private ResolvableType httpEntityType(ResolvableType type) {
return forClassWithGenerics(HttpEntity.class, type);
}
private void testSupports(ResolvableType type) {
MethodParameter parameter = this.testMethod.arg(type);
assertTrue(this.resolver.supportsParameter(parameter));
}
@SuppressWarnings("unchecked")
private <T> T resolveValue(ResolvableType type, String body) {
......
......@@ -16,6 +16,7 @@
package org.springframework.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
......@@ -27,7 +28,6 @@ import rx.Single;
import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageWriter;
......@@ -41,7 +41,10 @@ import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.springframework.web.method.ResolvableMethod.on;
/**
* Unit tests for {@link ResponseBodyResultHandler}.When adding a test also
......@@ -75,22 +78,42 @@ public class ResponseBodyResultHandlerTests {
@Test
public void supports() throws NoSuchMethodException {
Object controller = new TestController();
testSupports(controller, "handleToString", true);
testSupports(controller, "doWork", false);
controller = new TestRestController();
testSupports(controller, "handleToString", true);
testSupports(controller, "handleToMonoString", true);
testSupports(controller, "handleToSingleString", true);
testSupports(controller, "handleToCompletable", true);
testSupports(controller, "handleToResponseEntity", false);
testSupports(controller, "handleToMonoResponseEntity", false);
Method method;
method = on(TestController.class).annotPresent(ResponseBody.class).resolveMethod();
testSupports(controller, method);
method = on(TestController.class).annotNotPresent(ResponseBody.class).resolveMethod();
HandlerResult handlerResult = getHandlerResult(controller, method);
assertFalse(this.resultHandler.supports(handlerResult));
}
@Test
public void supportsRestController() throws NoSuchMethodException {
Object controller = new TestRestController();
Method method;
method = on(TestRestController.class).returning(String.class).resolveMethod();
testSupports(controller, method);
method = on(TestRestController.class).returning(Mono.class, String.class).resolveMethod();
testSupports(controller, method);
method = on(TestRestController.class).returning(Single.class, String.class).resolveMethod();
testSupports(controller, method);
method = on(TestRestController.class).returning(Completable.class).resolveMethod();
testSupports(controller, method);
}
private void testSupports(Object controller, String method, boolean result) throws NoSuchMethodException {
HandlerMethod hm = handlerMethod(controller, method);
HandlerResult handlerResult = new HandlerResult(hm, null, hm.getReturnType());
assertEquals(result, this.resultHandler.supports(handlerResult));
private void testSupports(Object controller, Method method) {
HandlerResult handlerResult = getHandlerResult(controller, method);
assertTrue(this.resultHandler.supports(handlerResult));
}
private HandlerResult getHandlerResult(Object controller, Method method) {
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
return new HandlerResult(handlerMethod, null, handlerMethod.getReturnType());
}
@Test
......@@ -99,10 +122,6 @@ public class ResponseBodyResultHandlerTests {
}
private HandlerMethod handlerMethod(Object controller, String method) throws NoSuchMethodException {
return new HandlerMethod(controller, controller.getClass().getMethod(method));
}
@RestController
@SuppressWarnings("unused")
......@@ -125,14 +144,6 @@ public class ResponseBodyResultHandlerTests {
public Completable handleToCompletable() {
return null;
}
public ResponseEntity<String> handleToResponseEntity() {
return null;
}
public Mono<ResponseEntity<String>> handleToMonoResponseEntity() {
return null;
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册