Add Reactor classpath checks in argument resolvers
HTTP interface client argument resolvers for RequestBody and RequestPart now handle reactive input conditionally. See gh-30117
This commit is contained in:
@@ -22,8 +22,10 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Contract to abstract an underlying HTTP client and decouple it from the
|
||||
* {@linkplain HttpServiceProxyFactory#createClient(Class) HTTP service proxy}.
|
||||
* Contract to abstract an HTTP client from {@linkplain HttpServiceProxyFactory}
|
||||
* and make it pluggable.
|
||||
*
|
||||
* <p>For reactive clients, see {@link ReactorHttpExchangeAdapter}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.1
|
||||
|
||||
@@ -43,7 +43,7 @@ import org.springframework.web.util.UriUtils;
|
||||
* Container for HTTP request values extracted from an
|
||||
* {@link org.springframework.web.service.annotation.HttpExchange @HttpExchange}-annotated
|
||||
* method and argument values passed to it. This is then given to
|
||||
* {@link HttpClientAdapter} to adapt to the underlying HTTP client.
|
||||
* {@link HttpExchangeAdapter} to adapt to the underlying HTTP client.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.0
|
||||
|
||||
@@ -53,6 +53,7 @@ import org.springframework.web.service.annotation.HttpExchange;
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.0
|
||||
* @see org.springframework.web.client.support.RestTemplateAdapter
|
||||
* @see org.springframework.web.reactive.function.client.support.WebClientAdapter
|
||||
*/
|
||||
public final class HttpServiceProxyFactory {
|
||||
@@ -107,6 +108,7 @@ public final class HttpServiceProxyFactory {
|
||||
|
||||
/**
|
||||
* Return a builder that's initialized with the given client.
|
||||
* @since 6.1
|
||||
*/
|
||||
public static Builder builderFor(HttpExchangeAdapter exchangeAdapter) {
|
||||
return new Builder().exchangeAdapter(exchangeAdapter);
|
||||
@@ -114,7 +116,8 @@ public final class HttpServiceProxyFactory {
|
||||
|
||||
/**
|
||||
* Return a builder that's initialized with the given client.
|
||||
* @deprecated in favor of {@link #builderFor(HttpExchangeAdapter)}
|
||||
* @deprecated in favor of {@link #builderFor(HttpExchangeAdapter)};
|
||||
* to be removed in 6.2.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
@@ -164,7 +167,8 @@ public final class HttpServiceProxyFactory {
|
||||
* Provide the HTTP client to perform requests through.
|
||||
* @param clientAdapter a client adapted to {@link HttpClientAdapter}
|
||||
* @return this same builder instance
|
||||
* @deprecated in favor of {@link #exchangeAdapter(HttpExchangeAdapter)}
|
||||
* @deprecated in favor of {@link #exchangeAdapter(HttpExchangeAdapter)};
|
||||
* to be removed in 6.2
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
@@ -260,10 +264,10 @@ public final class HttpServiceProxyFactory {
|
||||
|
||||
// Annotation-based
|
||||
resolvers.add(new RequestHeaderArgumentResolver(service));
|
||||
resolvers.add(new RequestBodyArgumentResolver());
|
||||
resolvers.add(new RequestBodyArgumentResolver(this.exchangeAdapter));
|
||||
resolvers.add(new PathVariableArgumentResolver(service));
|
||||
resolvers.add(new RequestParamArgumentResolver(service));
|
||||
resolvers.add(new RequestPartArgumentResolver());
|
||||
resolvers.add(new RequestPartArgumentResolver(this.exchangeAdapter));
|
||||
resolvers.add(new CookieValueArgumentResolver(service));
|
||||
if (this.exchangeAdapter.supportsRequestAttributes()) {
|
||||
resolvers.add(new RequestAttributeArgumentResolver());
|
||||
|
||||
@@ -28,8 +28,8 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Contract to abstract a Project Reactor, HTTP client to decouple it from the
|
||||
* {@linkplain HttpServiceProxyFactory#createClient(Class) HTTP service proxy}.
|
||||
* Contract to abstract a reactive, HTTP client from
|
||||
* {@linkplain HttpServiceProxyFactory} and make it pluggable.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.1
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
/**
|
||||
@@ -33,23 +34,28 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
*/
|
||||
public class RequestBodyArgumentResolver implements HttpServiceArgumentResolver {
|
||||
|
||||
private static final boolean REACTOR_PRESENT =
|
||||
ClassUtils.isPresent("reactor.core.publisher.Mono", RequestBodyArgumentResolver.class.getClassLoader());
|
||||
|
||||
|
||||
@Nullable
|
||||
private final ReactiveAdapterRegistry reactiveAdapterRegistry;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor that uses {@link ReactiveAdapterRegistry#getSharedInstance()}.
|
||||
* Constructor with a {@link HttpExchangeAdapter}, for access to config settings.
|
||||
* @since 6.1
|
||||
*/
|
||||
public RequestBodyArgumentResolver() {
|
||||
this(ReactiveAdapterRegistry.getSharedInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with a {@link ReactiveAdapterRegistry}.
|
||||
*/
|
||||
public RequestBodyArgumentResolver(ReactiveAdapterRegistry reactiveAdapterRegistry) {
|
||||
Assert.notNull(reactiveAdapterRegistry, "ReactiveAdapterRegistry is required");
|
||||
this.reactiveAdapterRegistry = reactiveAdapterRegistry;
|
||||
public RequestBodyArgumentResolver(HttpExchangeAdapter exchangeAdapter) {
|
||||
if (REACTOR_PRESENT) {
|
||||
this.reactiveAdapterRegistry =
|
||||
(exchangeAdapter instanceof ReactorHttpExchangeAdapter reactorAdapter ?
|
||||
reactorAdapter.getReactiveAdapterRegistry() :
|
||||
ReactiveAdapterRegistry.getSharedInstance());
|
||||
}
|
||||
else {
|
||||
this.reactiveAdapterRegistry = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,21 +69,25 @@ public class RequestBodyArgumentResolver implements HttpServiceArgumentResolver
|
||||
}
|
||||
|
||||
if (argument != null) {
|
||||
ReactiveAdapter reactiveAdapter = this.reactiveAdapterRegistry.getAdapter(parameter.getParameterType());
|
||||
if (reactiveAdapter == null) {
|
||||
requestValues.setBodyValue(argument);
|
||||
}
|
||||
else {
|
||||
MethodParameter nestedParameter = parameter.nested();
|
||||
if (this.reactiveAdapterRegistry != null) {
|
||||
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(parameter.getParameterType());
|
||||
if (adapter != null) {
|
||||
MethodParameter nestedParameter = parameter.nested();
|
||||
|
||||
String message = "Async type for @RequestBody should produce value(s)";
|
||||
Assert.isTrue(!reactiveAdapter.isNoValue(), message);
|
||||
Assert.isTrue(nestedParameter.getNestedParameterType() != Void.class, message);
|
||||
String message = "Async type for @RequestBody should produce value(s)";
|
||||
Assert.isTrue(!adapter.isNoValue(), message);
|
||||
Assert.isTrue(nestedParameter.getNestedParameterType() != Void.class, message);
|
||||
|
||||
requestValues.setBody(
|
||||
reactiveAdapter.toPublisher(argument),
|
||||
ParameterizedTypeReference.forType(nestedParameter.getNestedGenericParameterType()));
|
||||
requestValues.setBody(
|
||||
adapter.toPublisher(argument),
|
||||
ParameterizedTypeReference.forType(nestedParameter.getNestedGenericParameterType()));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Not a reactive type
|
||||
requestValues.setBodyValue(argument);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -24,7 +24,9 @@ import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
|
||||
/**
|
||||
@@ -47,22 +49,28 @@ import org.springframework.web.bind.annotation.RequestPart;
|
||||
*/
|
||||
public class RequestPartArgumentResolver extends AbstractNamedValueArgumentResolver {
|
||||
|
||||
private static final boolean REACTOR_PRESENT =
|
||||
ClassUtils.isPresent("reactor.core.publisher.Mono", RequestPartArgumentResolver.class.getClassLoader());
|
||||
|
||||
|
||||
@Nullable
|
||||
private final ReactiveAdapterRegistry reactiveAdapterRegistry;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor that uses {@link ReactiveAdapterRegistry#getSharedInstance()}.
|
||||
* Constructor with a {@link HttpExchangeAdapter}, for access to config settings.
|
||||
* @since 6.1
|
||||
*/
|
||||
public RequestPartArgumentResolver() {
|
||||
this(ReactiveAdapterRegistry.getSharedInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with a {@link ReactiveAdapterRegistry}.
|
||||
*/
|
||||
public RequestPartArgumentResolver(ReactiveAdapterRegistry reactiveAdapterRegistry) {
|
||||
this.reactiveAdapterRegistry = reactiveAdapterRegistry;
|
||||
public RequestPartArgumentResolver(HttpExchangeAdapter exchangeAdapter) {
|
||||
if (REACTOR_PRESENT) {
|
||||
this.reactiveAdapterRegistry =
|
||||
(exchangeAdapter instanceof ReactorHttpExchangeAdapter reactorAdapter ?
|
||||
reactorAdapter.getReactiveAdapterRegistry() :
|
||||
ReactiveAdapterRegistry.getSharedInstance());
|
||||
}
|
||||
else {
|
||||
this.reactiveAdapterRegistry = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,16 +85,18 @@ public class RequestPartArgumentResolver extends AbstractNamedValueArgumentResol
|
||||
protected void addRequestValue(
|
||||
String name, Object value, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
|
||||
|
||||
Class<?> type = parameter.getParameterType();
|
||||
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(type);
|
||||
if (adapter != null) {
|
||||
Assert.isTrue(!adapter.isNoValue(), "Expected publisher that produces a value");
|
||||
Publisher<?> publisher = adapter.toPublisher(value);
|
||||
requestValues.addRequestPart(name, publisher, ResolvableType.forMethodParameter(parameter.nested()));
|
||||
}
|
||||
else {
|
||||
requestValues.addRequestPart(name, value);
|
||||
if (this.reactiveAdapterRegistry != null) {
|
||||
Class<?> type = parameter.getParameterType();
|
||||
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(type);
|
||||
if (adapter != null) {
|
||||
Assert.isTrue(!adapter.isNoValue(), "Expected publisher that produces a value");
|
||||
Publisher<?> publisher = adapter.toPublisher(value);
|
||||
requestValues.addRequestPart(name, publisher, ResolvableType.forMethodParameter(parameter.nested()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
requestValues.addRequestPart(name, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user