Add RequestBody resolver and minor improvements
Support for RequestBody arguments. List supported arguments on HttpExchange. Improve null handling. See gh-28386
This commit is contained in:
@@ -24,6 +24,7 @@ import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.web.bind.annotation.Mapping;
|
||||
import org.springframework.web.service.invoker.UrlArgumentResolver;
|
||||
|
||||
|
||||
/**
|
||||
@@ -49,16 +50,52 @@ import org.springframework.web.bind.annotation.Mapping;
|
||||
* <tr>
|
||||
* <th>Method Argument</th>
|
||||
* <th>Description</th>
|
||||
* <th>Resolver</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link org.springframework.http.HttpMethod}</td>
|
||||
* <td>Set the HTTP method for the request, overriding the annotation
|
||||
* {@link #method()} attribute value</td>
|
||||
* <td>{@link java.net.URI URI}</td>
|
||||
* <td>Dynamically set the URL for the request, overriding the annotation
|
||||
* {@link #url()} attribute</td>
|
||||
* <td>{@link UrlArgumentResolver
|
||||
* HttpUrlArgumentResolver}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link org.springframework.http.HttpMethod HttpMethod}</td>
|
||||
* <td>Dynamically set the HTTP method for the request, overriding the annotation
|
||||
* {@link #method()} attribute</td>
|
||||
* <td>{@link org.springframework.web.service.invoker.HttpMethodArgumentResolver
|
||||
* HttpMethodArgumentResolver}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link org.springframework.web.bind.annotation.RequestHeader @RequestHeader}</td>
|
||||
* <td>Add a request header</td>
|
||||
* <td>{@link org.springframework.web.service.invoker.RequestHeaderArgumentResolver
|
||||
* RequestHeaderArgumentResolver}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link org.springframework.web.bind.annotation.PathVariable @PathVariable}</td>
|
||||
* <td>Provide a path variable to expand the URI template with. This may be an
|
||||
* individual value or a Map of values.</td>
|
||||
* <td>Add a path variable for the URI template</td>
|
||||
* <td>{@link org.springframework.web.service.invoker.PathVariableArgumentResolver
|
||||
* PathVariableArgumentResolver}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link org.springframework.web.bind.annotation.RequestBody @RequestBody}</td>
|
||||
* <td>Set the body of the request</td>
|
||||
* <td>{@link org.springframework.web.service.invoker.RequestBodyArgumentResolver
|
||||
* RequestBodyArgumentResolver}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link org.springframework.web.bind.annotation.RequestParam @RequestParam}</td>
|
||||
* <td>Add a request parameter, either form data if {@code "Content-Type"} is
|
||||
* {@code "application/x-www-form-urlencoded"} or query params otherwise</td>
|
||||
* <td>{@link org.springframework.web.service.invoker.RequestParamArgumentResolver
|
||||
* RequestParamArgumentResolver}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link org.springframework.web.bind.annotation.CookieValue @CookieValue}</td>
|
||||
* <td>Add a cookie</td>
|
||||
* <td>{@link org.springframework.web.service.invoker.CookieValueArgumentResolver
|
||||
* CookieValueArgumentResolver}</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
|
||||
@@ -40,15 +40,19 @@ public class HttpMethodArgumentResolver implements HttpServiceArgumentResolver {
|
||||
public boolean resolve(
|
||||
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
|
||||
|
||||
if (argument instanceof HttpMethod httpMethod) {
|
||||
if (!parameter.getParameterType().equals(HttpMethod.class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (argument != null) {
|
||||
HttpMethod httpMethod = (HttpMethod) argument;
|
||||
requestValues.setHttpMethod(httpMethod);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Resolved HTTP method to: " + httpMethod.name());
|
||||
}
|
||||
requestValues.setHttpMethod(httpMethod);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ public final class HttpRequestValues {
|
||||
* <p>This is mutually exclusive with, and resets any previously set
|
||||
* {@link #setBodyValue(Object) body value}.
|
||||
*/
|
||||
public <T, P extends Publisher<T>> void setBody(Publisher<P> body, ParameterizedTypeReference<?> elementTye) {
|
||||
public <T, P extends Publisher<T>> void setBody(P body, ParameterizedTypeReference<T> elementTye) {
|
||||
this.body = body;
|
||||
this.bodyElementType = elementTye;
|
||||
this.bodyValue = null;
|
||||
|
||||
@@ -110,14 +110,22 @@ final class HttpServiceMethod {
|
||||
Assert.isTrue(arguments.length == this.parameters.length, "Method argument mismatch");
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
Object value = arguments[i];
|
||||
boolean resolved = false;
|
||||
for (HttpServiceArgumentResolver resolver : this.argumentResolvers) {
|
||||
if (resolver.resolve(value, this.parameters[i], requestValues)) {
|
||||
resolved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert.state(resolved, formatArgumentError(this.parameters[i], "No suitable resolver"));
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatArgumentError(MethodParameter param, String message) {
|
||||
return "Could not resolve parameter [" + param.getParameterIndex() + "] in " +
|
||||
param.getExecutable().toGenericString() + (StringUtils.hasText(message) ? ": " + message : "");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory for an {@link HttpRequestValues} with values extracted from
|
||||
|
||||
@@ -188,10 +188,11 @@ public final class HttpServiceProxyFactory {
|
||||
private List<HttpServiceArgumentResolver> initArgumentResolvers(ConversionService conversionService) {
|
||||
List<HttpServiceArgumentResolver> resolvers = new ArrayList<>(this.customResolvers);
|
||||
resolvers.add(new RequestHeaderArgumentResolver(conversionService));
|
||||
resolvers.add(new RequestBodyArgumentResolver(this.reactiveAdapterRegistry));
|
||||
resolvers.add(new PathVariableArgumentResolver(conversionService));
|
||||
resolvers.add(new CookieValueArgumentResolver(conversionService));
|
||||
resolvers.add(new RequestParamArgumentResolver(conversionService));
|
||||
resolvers.add(new HttpUrlArgumentResolver());
|
||||
resolvers.add(new CookieValueArgumentResolver(conversionService));
|
||||
resolvers.add(new UrlArgumentResolver());
|
||||
resolvers.add(new HttpMethodArgumentResolver());
|
||||
return resolvers;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.service.invoker;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
|
||||
/**
|
||||
* {@link HttpServiceArgumentResolver} for {@link RequestBody @RequestBody}
|
||||
* annotated arguments.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.0
|
||||
*/
|
||||
public class RequestBodyArgumentResolver implements HttpServiceArgumentResolver {
|
||||
|
||||
private final ReactiveAdapterRegistry reactiveAdapterRegistry;
|
||||
|
||||
|
||||
public RequestBodyArgumentResolver(ReactiveAdapterRegistry reactiveAdapterRegistry) {
|
||||
Assert.notNull(reactiveAdapterRegistry, "ReactiveAdapterRegistry is required");
|
||||
this.reactiveAdapterRegistry = reactiveAdapterRegistry;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean resolve(
|
||||
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
|
||||
|
||||
RequestBody annot = parameter.getParameterAnnotation(RequestBody.class);
|
||||
if (annot == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (argument != null) {
|
||||
ReactiveAdapter reactiveAdapter = this.reactiveAdapterRegistry.getAdapter(parameter.getParameterType());
|
||||
if (reactiveAdapter != null) {
|
||||
setBody(argument, parameter, reactiveAdapter, requestValues);
|
||||
}
|
||||
else {
|
||||
requestValues.setBodyValue(argument);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private <E> void setBody(
|
||||
Object argument, MethodParameter parameter, ReactiveAdapter reactiveAdapter,
|
||||
HttpRequestValues.Builder requestValues) {
|
||||
|
||||
String message = "Async type for @RequestBody should produce value(s)";
|
||||
Assert.isTrue(!reactiveAdapter.isNoValue(), message);
|
||||
|
||||
parameter = parameter.nested();
|
||||
Class<?> elementClass = parameter.getNestedParameterType();
|
||||
Assert.isTrue(elementClass != Void.class, message);
|
||||
ParameterizedTypeReference<E> typeRef = ParameterizedTypeReference.forType(parameter.getNestedGenericParameterType());
|
||||
Publisher<E> publisher = reactiveAdapter.toPublisher(argument);
|
||||
|
||||
requestValues.setBody(publisher, typeRef);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,29 +19,32 @@ package org.springframework.web.service.invoker;
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* {@link HttpServiceArgumentResolver} that resolves the target
|
||||
* request's URL from an {@link HttpMethod} argument.
|
||||
* {@link HttpServiceArgumentResolver} that resolves the URL for the request
|
||||
* from an {@link URI} argument.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.0
|
||||
*/
|
||||
public class HttpUrlArgumentResolver implements HttpServiceArgumentResolver {
|
||||
public class UrlArgumentResolver implements HttpServiceArgumentResolver {
|
||||
|
||||
@Override
|
||||
public boolean resolve(
|
||||
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
|
||||
|
||||
if (argument instanceof URI uri) {
|
||||
requestValues.setUri(uri);
|
||||
if (!parameter.getParameterType().equals(URI.class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (argument != null) {
|
||||
requestValues.setUri((URI) argument);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user