diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/AbstractHandlerResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/AbstractHandlerResultHandler.java index 56eb1b4062..9075733ebb 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/AbstractHandlerResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/AbstractHandlerResultHandler.java @@ -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}. */ diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java index 8f82dd27de..f434aecaee 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java @@ -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); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java index 57784837e0..5017f0f901 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java @@ -93,21 +93,19 @@ public abstract class AbstractMessageWriterResultHandler extends AbstractHandler @SuppressWarnings("unchecked") protected Mono 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())); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java index 1150d93810..7956317b23 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java @@ -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 resolveArgument(MethodParameter param, BindingContext bindingContext, + public Mono 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()); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java index 472944ee22..d482f691b0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java @@ -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 model = context.getModel().asMap(); MonoProcessor 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); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java index c5e2adca1b..de235e4d02 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java @@ -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 diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java index 8f518fc5e7..414f1b2792 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java @@ -74,8 +74,8 @@ public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentRe public Mono 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); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java index 7d0a403882..452863321e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java @@ -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> messageWriters, - RequestedContentTypeResolver contentTypeResolver) { + public ResponseBodyResultHandler(List> 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> messageWriters, - RequestedContentTypeResolver contentTypeResolver, - ReactiveAdapterRegistry adapterRegistry) { + public ResponseBodyResultHandler(List> 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 handleResult(ServerWebExchange exchange, HandlerResult result) { Object body = result.getReturnValue().orElse(null); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java index 6b900eeae9..6470f5d1d6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java @@ -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> messageWriters, - RequestedContentTypeResolver contentTypeResolver) { + public ResponseEntityResultHandler(List> 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> messageWriters, - RequestedContentTypeResolver contentTypeResolver, - ReactiveAdapterRegistry adapterRegistry) { + public ResponseEntityResultHandler(List> 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 handleResult(ServerWebExchange exchange, HandlerResult result) { - ResolvableType returnType = result.getReturnType(); - MethodParameter bodyType; Mono returnValueMono; - Optional 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); }); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java index b1a4bac2af..103247e4e5 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java @@ -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 resolvers, + public ViewResolutionResultHandler(List 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 resolvers, - RequestedContentTypeResolver contentTypeResolver, - ReactiveAdapterRegistry adapterRegistry) { + public ViewResolutionResultHandler(List 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 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 handleResult(ServerWebExchange exchange, HandlerResult result) { - Mono returnValueMono; - ResolvableType elementType; - ResolvableType parameterType = result.getReturnType(); - - Optional optional = result.getReturnValue(); - ReactiveAdapter adapter = getAdapterRegistry().getAdapter(parameterType.getRawClass(), optional); + Mono 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> 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) 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 model = context.getModel().asMap(); model.keySet().stream() .filter(name -> isBindingCandidate(name, model.get(name))) diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java index d450c7f704..45f9dac671 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java @@ -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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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 resolveValue(ResolvableType type, String body) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java index 2420201e9b..1add66cb59 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java @@ -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 handleToResponseEntity() { - return null; - } - - public Mono> handleToMonoResponseEntity() { - return null; - } }