Browse Source

Add Reactor classpath checks in argument resolvers

HTTP interface client argument resolvers for RequestBody and
RequestPart now handle reactive input conditionally.

See gh-30117
pull/30869/head
rstoyanchev 3 years ago
parent
commit
3209cf5c7a
  1. 6
      spring-web/src/main/java/org/springframework/web/service/invoker/HttpExchangeAdapter.java
  2. 2
      spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java
  3. 12
      spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java
  4. 4
      spring-web/src/main/java/org/springframework/web/service/invoker/ReactorHttpExchangeAdapter.java
  5. 56
      spring-web/src/main/java/org/springframework/web/service/invoker/RequestBodyArgumentResolver.java
  6. 48
      spring-web/src/main/java/org/springframework/web/service/invoker/RequestPartArgumentResolver.java

6
spring-web/src/main/java/org/springframework/web/service/invoker/HttpExchangeAdapter.java

@ -22,8 +22,10 @@ import org.springframework.http.ResponseEntity; @@ -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

2
spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java

@ -43,7 +43,7 @@ import org.springframework.web.util.UriUtils; @@ -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

12
spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java

@ -53,6 +53,7 @@ import org.springframework.web.service.annotation.HttpExchange; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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());

4
spring-web/src/main/java/org/springframework/web/service/invoker/ReactorHttpExchangeAdapter.java

@ -28,8 +28,8 @@ import org.springframework.http.ResponseEntity; @@ -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

56
spring-web/src/main/java/org/springframework/web/service/invoker/RequestBodyArgumentResolver.java

@ -22,6 +22,7 @@ import org.springframework.core.ReactiveAdapter; @@ -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; @@ -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 @@ -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;

48
spring-web/src/main/java/org/springframework/web/service/invoker/RequestPartArgumentResolver.java

@ -24,7 +24,9 @@ import org.springframework.core.ReactiveAdapterRegistry; @@ -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; @@ -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 @@ -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);
}
}

Loading…
Cancel
Save