Skip to content

Commit 3209cf5

Browse files
committed
Add Reactor classpath checks in argument resolvers
HTTP interface client argument resolvers for RequestBody and RequestPart now handle reactive input conditionally. See gh-30117
1 parent 22376c2 commit 3209cf5

File tree

6 files changed

+77
-51
lines changed

6 files changed

+77
-51
lines changed

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
import org.springframework.lang.Nullable;
2323

2424
/**
25-
* Contract to abstract an underlying HTTP client and decouple it from the
26-
* {@linkplain HttpServiceProxyFactory#createClient(Class) HTTP service proxy}.
25+
* Contract to abstract an HTTP client from {@linkplain HttpServiceProxyFactory}
26+
* and make it pluggable.
27+
*
28+
* <p>For reactive clients, see {@link ReactorHttpExchangeAdapter}.
2729
*
2830
* @author Rossen Stoyanchev
2931
* @since 6.1

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
* Container for HTTP request values extracted from an
4444
* {@link org.springframework.web.service.annotation.HttpExchange @HttpExchange}-annotated
4545
* method and argument values passed to it. This is then given to
46-
* {@link HttpClientAdapter} to adapt to the underlying HTTP client.
46+
* {@link HttpExchangeAdapter} to adapt to the underlying HTTP client.
4747
*
4848
* @author Rossen Stoyanchev
4949
* @since 6.0

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
*
5454
* @author Rossen Stoyanchev
5555
* @since 6.0
56+
* @see org.springframework.web.client.support.RestTemplateAdapter
5657
* @see org.springframework.web.reactive.function.client.support.WebClientAdapter
5758
*/
5859
public final class HttpServiceProxyFactory {
@@ -107,14 +108,16 @@ private <S> HttpServiceMethod createHttpServiceMethod(Class<S> serviceType, Meth
107108

108109
/**
109110
* Return a builder that's initialized with the given client.
111+
* @since 6.1
110112
*/
111113
public static Builder builderFor(HttpExchangeAdapter exchangeAdapter) {
112114
return new Builder().exchangeAdapter(exchangeAdapter);
113115
}
114116

115117
/**
116118
* Return a builder that's initialized with the given client.
117-
* @deprecated in favor of {@link #builderFor(HttpExchangeAdapter)}
119+
* @deprecated in favor of {@link #builderFor(HttpExchangeAdapter)};
120+
* to be removed in 6.2.
118121
*/
119122
@SuppressWarnings("removal")
120123
@Deprecated(since = "6.1", forRemoval = true)
@@ -164,7 +167,8 @@ public Builder exchangeAdapter(HttpExchangeAdapter adapter) {
164167
* Provide the HTTP client to perform requests through.
165168
* @param clientAdapter a client adapted to {@link HttpClientAdapter}
166169
* @return this same builder instance
167-
* @deprecated in favor of {@link #exchangeAdapter(HttpExchangeAdapter)}
170+
* @deprecated in favor of {@link #exchangeAdapter(HttpExchangeAdapter)};
171+
* to be removed in 6.2
168172
*/
169173
@SuppressWarnings("removal")
170174
@Deprecated(since = "6.1", forRemoval = true)
@@ -260,10 +264,10 @@ private List<HttpServiceArgumentResolver> initArgumentResolvers() {
260264

261265
// Annotation-based
262266
resolvers.add(new RequestHeaderArgumentResolver(service));
263-
resolvers.add(new RequestBodyArgumentResolver());
267+
resolvers.add(new RequestBodyArgumentResolver(this.exchangeAdapter));
264268
resolvers.add(new PathVariableArgumentResolver(service));
265269
resolvers.add(new RequestParamArgumentResolver(service));
266-
resolvers.add(new RequestPartArgumentResolver());
270+
resolvers.add(new RequestPartArgumentResolver(this.exchangeAdapter));
267271
resolvers.add(new CookieValueArgumentResolver(service));
268272
if (this.exchangeAdapter.supportsRequestAttributes()) {
269273
resolvers.add(new RequestAttributeArgumentResolver());

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import org.springframework.lang.Nullable;
2929

3030
/**
31-
* Contract to abstract a Project Reactor, HTTP client to decouple it from the
32-
* {@linkplain HttpServiceProxyFactory#createClient(Class) HTTP service proxy}.
31+
* Contract to abstract a reactive, HTTP client from
32+
* {@linkplain HttpServiceProxyFactory} and make it pluggable.
3333
*
3434
* @author Rossen Stoyanchev
3535
* @since 6.1

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

+33-23
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.core.ReactiveAdapterRegistry;
2323
import org.springframework.lang.Nullable;
2424
import org.springframework.util.Assert;
25+
import org.springframework.util.ClassUtils;
2526
import org.springframework.web.bind.annotation.RequestBody;
2627

2728
/**
@@ -33,23 +34,28 @@
3334
*/
3435
public class RequestBodyArgumentResolver implements HttpServiceArgumentResolver {
3536

37+
private static final boolean REACTOR_PRESENT =
38+
ClassUtils.isPresent("reactor.core.publisher.Mono", RequestBodyArgumentResolver.class.getClassLoader());
39+
40+
41+
@Nullable
3642
private final ReactiveAdapterRegistry reactiveAdapterRegistry;
3743

3844

3945
/**
40-
* Default constructor that uses {@link ReactiveAdapterRegistry#getSharedInstance()}.
46+
* Constructor with a {@link HttpExchangeAdapter}, for access to config settings.
4147
* @since 6.1
4248
*/
43-
public RequestBodyArgumentResolver() {
44-
this(ReactiveAdapterRegistry.getSharedInstance());
45-
}
46-
47-
/**
48-
* Constructor with a {@link ReactiveAdapterRegistry}.
49-
*/
50-
public RequestBodyArgumentResolver(ReactiveAdapterRegistry reactiveAdapterRegistry) {
51-
Assert.notNull(reactiveAdapterRegistry, "ReactiveAdapterRegistry is required");
52-
this.reactiveAdapterRegistry = reactiveAdapterRegistry;
49+
public RequestBodyArgumentResolver(HttpExchangeAdapter exchangeAdapter) {
50+
if (REACTOR_PRESENT) {
51+
this.reactiveAdapterRegistry =
52+
(exchangeAdapter instanceof ReactorHttpExchangeAdapter reactorAdapter ?
53+
reactorAdapter.getReactiveAdapterRegistry() :
54+
ReactiveAdapterRegistry.getSharedInstance());
55+
}
56+
else {
57+
this.reactiveAdapterRegistry = null;
58+
}
5359
}
5460

5561

@@ -63,21 +69,25 @@ public boolean resolve(
6369
}
6470

6571
if (argument != null) {
66-
ReactiveAdapter reactiveAdapter = this.reactiveAdapterRegistry.getAdapter(parameter.getParameterType());
67-
if (reactiveAdapter == null) {
68-
requestValues.setBodyValue(argument);
69-
}
70-
else {
71-
MethodParameter nestedParameter = parameter.nested();
72+
if (this.reactiveAdapterRegistry != null) {
73+
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(parameter.getParameterType());
74+
if (adapter != null) {
75+
MethodParameter nestedParameter = parameter.nested();
7276

73-
String message = "Async type for @RequestBody should produce value(s)";
74-
Assert.isTrue(!reactiveAdapter.isNoValue(), message);
75-
Assert.isTrue(nestedParameter.getNestedParameterType() != Void.class, message);
77+
String message = "Async type for @RequestBody should produce value(s)";
78+
Assert.isTrue(!adapter.isNoValue(), message);
79+
Assert.isTrue(nestedParameter.getNestedParameterType() != Void.class, message);
7680

77-
requestValues.setBody(
78-
reactiveAdapter.toPublisher(argument),
79-
ParameterizedTypeReference.forType(nestedParameter.getNestedGenericParameterType()));
81+
requestValues.setBody(
82+
adapter.toPublisher(argument),
83+
ParameterizedTypeReference.forType(nestedParameter.getNestedGenericParameterType()));
84+
85+
return true;
86+
}
8087
}
88+
89+
// Not a reactive type
90+
requestValues.setBodyValue(argument);
8191
}
8292

8393
return true;

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

+29-19
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
import org.springframework.core.ResolvableType;
2525
import org.springframework.http.HttpEntity;
2626
import org.springframework.http.codec.multipart.Part;
27+
import org.springframework.lang.Nullable;
2728
import org.springframework.util.Assert;
29+
import org.springframework.util.ClassUtils;
2830
import org.springframework.web.bind.annotation.RequestPart;
2931

3032
/**
@@ -47,22 +49,28 @@
4749
*/
4850
public class RequestPartArgumentResolver extends AbstractNamedValueArgumentResolver {
4951

52+
private static final boolean REACTOR_PRESENT =
53+
ClassUtils.isPresent("reactor.core.publisher.Mono", RequestPartArgumentResolver.class.getClassLoader());
54+
55+
56+
@Nullable
5057
private final ReactiveAdapterRegistry reactiveAdapterRegistry;
5158

5259

5360
/**
54-
* Default constructor that uses {@link ReactiveAdapterRegistry#getSharedInstance()}.
61+
* Constructor with a {@link HttpExchangeAdapter}, for access to config settings.
5562
* @since 6.1
5663
*/
57-
public RequestPartArgumentResolver() {
58-
this(ReactiveAdapterRegistry.getSharedInstance());
59-
}
60-
61-
/**
62-
* Constructor with a {@link ReactiveAdapterRegistry}.
63-
*/
64-
public RequestPartArgumentResolver(ReactiveAdapterRegistry reactiveAdapterRegistry) {
65-
this.reactiveAdapterRegistry = reactiveAdapterRegistry;
64+
public RequestPartArgumentResolver(HttpExchangeAdapter exchangeAdapter) {
65+
if (REACTOR_PRESENT) {
66+
this.reactiveAdapterRegistry =
67+
(exchangeAdapter instanceof ReactorHttpExchangeAdapter reactorAdapter ?
68+
reactorAdapter.getReactiveAdapterRegistry() :
69+
ReactiveAdapterRegistry.getSharedInstance());
70+
}
71+
else {
72+
this.reactiveAdapterRegistry = null;
73+
}
6674
}
6775

6876

@@ -77,16 +85,18 @@ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
7785
protected void addRequestValue(
7886
String name, Object value, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
7987

80-
Class<?> type = parameter.getParameterType();
81-
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(type);
82-
if (adapter != null) {
83-
Assert.isTrue(!adapter.isNoValue(), "Expected publisher that produces a value");
84-
Publisher<?> publisher = adapter.toPublisher(value);
85-
requestValues.addRequestPart(name, publisher, ResolvableType.forMethodParameter(parameter.nested()));
86-
}
87-
else {
88-
requestValues.addRequestPart(name, value);
88+
if (this.reactiveAdapterRegistry != null) {
89+
Class<?> type = parameter.getParameterType();
90+
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(type);
91+
if (adapter != null) {
92+
Assert.isTrue(!adapter.isNoValue(), "Expected publisher that produces a value");
93+
Publisher<?> publisher = adapter.toPublisher(value);
94+
requestValues.addRequestPart(name, publisher, ResolvableType.forMethodParameter(parameter.nested()));
95+
return;
96+
}
8997
}
98+
99+
requestValues.addRequestPart(name, value);
90100
}
91101

92102
}

0 commit comments

Comments
 (0)