Skip to content

Commit 2a2fba6

Browse files
committed
Resolve placeholders in HttpExchange#url
Closes gh-28492
1 parent ce56846 commit 2a2fba6

File tree

3 files changed

+46
-12
lines changed

3 files changed

+46
-12
lines changed

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.util.Assert;
4343
import org.springframework.util.ObjectUtils;
4444
import org.springframework.util.StringUtils;
45+
import org.springframework.util.StringValueResolver;
4546
import org.springframework.web.service.annotation.HttpExchange;
4647

4748
/**
@@ -67,13 +68,13 @@ final class HttpServiceMethod {
6768

6869
HttpServiceMethod(
6970
Method method, Class<?> containingClass, List<HttpServiceArgumentResolver> argumentResolvers,
70-
HttpClientAdapter client, ReactiveAdapterRegistry reactiveRegistry,
71-
Duration blockTimeout) {
71+
HttpClientAdapter client, @Nullable StringValueResolver embeddedValueResolver,
72+
ReactiveAdapterRegistry reactiveRegistry, Duration blockTimeout) {
7273

7374
this.method = method;
7475
this.parameters = initMethodParameters(method);
7576
this.argumentResolvers = argumentResolvers;
76-
this.requestValuesInitializer = HttpRequestValuesInitializer.create(method, containingClass);
77+
this.requestValuesInitializer = HttpRequestValuesInitializer.create(method, containingClass, embeddedValueResolver);
7778
this.responseFunction = ResponseFunction.create(client, method, reactiveRegistry, blockTimeout);
7879
}
7980

@@ -161,15 +162,16 @@ public HttpRequestValues.Builder initializeRequestValuesBuilder() {
161162
/**
162163
* Introspect the method and create the request factory for it.
163164
*/
164-
public static HttpRequestValuesInitializer create(Method method, Class<?> containingClass) {
165+
public static HttpRequestValuesInitializer create(
166+
Method method, Class<?> containingClass, @Nullable StringValueResolver embeddedValueResolver) {
165167

166168
HttpExchange annot1 = AnnotatedElementUtils.findMergedAnnotation(containingClass, HttpExchange.class);
167169
HttpExchange annot2 = AnnotatedElementUtils.findMergedAnnotation(method, HttpExchange.class);
168170

169171
Assert.notNull(annot2, "Expected HttpRequest annotation");
170172

171173
HttpMethod httpMethod = initHttpMethod(annot1, annot2);
172-
String url = initUrl(annot1, annot2);
174+
String url = initUrl(annot1, annot2, embeddedValueResolver);
173175
MediaType contentType = initContentType(annot1, annot2);
174176
List<MediaType> acceptableMediaTypes = initAccept(annot1, annot2);
175177

@@ -194,11 +196,17 @@ private static HttpMethod initHttpMethod(@Nullable HttpExchange typeAnnot, HttpE
194196
}
195197

196198
@Nullable
197-
private static String initUrl(@Nullable HttpExchange typeAnnot, HttpExchange annot) {
199+
private static String initUrl(
200+
@Nullable HttpExchange typeAnnot, HttpExchange annot, @Nullable StringValueResolver embeddedValueResolver) {
198201

199202
String url1 = (typeAnnot != null ? typeAnnot.url() : null);
200203
String url2 = annot.url();
201204

205+
if (embeddedValueResolver != null) {
206+
url1 = (url1 != null ? embeddedValueResolver.resolveStringValue(url1) : null);
207+
url2 = embeddedValueResolver.resolveStringValue(url2);
208+
}
209+
202210
boolean hasUrl1 = StringUtils.hasText(url1);
203211
boolean hasUrl2 = StringUtils.hasText(url2);
204212

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.format.support.DefaultFormattingConversionService;
3636
import org.springframework.lang.Nullable;
3737
import org.springframework.util.Assert;
38+
import org.springframework.util.StringValueResolver;
3839
import org.springframework.web.service.annotation.HttpExchange;
3940

4041
/**
@@ -50,17 +51,22 @@ public final class HttpServiceProxyFactory {
5051

5152
private final List<HttpServiceArgumentResolver> argumentResolvers;
5253

54+
@Nullable
55+
private final StringValueResolver embeddedValueResolver;
56+
5357
private final ReactiveAdapterRegistry reactiveAdapterRegistry;
5458

5559
private final Duration blockTimeout;
5660

5761

5862
private HttpServiceProxyFactory(
5963
HttpClientAdapter clientAdapter, List<HttpServiceArgumentResolver> argumentResolvers,
60-
ReactiveAdapterRegistry reactiveAdapterRegistry, Duration blockTimeout) {
64+
@Nullable StringValueResolver embeddedValueResolver, ReactiveAdapterRegistry reactiveAdapterRegistry,
65+
Duration blockTimeout) {
6166

6267
this.clientAdapter = clientAdapter;
6368
this.argumentResolvers = argumentResolvers;
69+
this.embeddedValueResolver = embeddedValueResolver;
6470
this.reactiveAdapterRegistry = reactiveAdapterRegistry;
6571
this.blockTimeout = blockTimeout;
6672
}
@@ -80,8 +86,8 @@ public <S> S createClient(Class<S> serviceType) {
8086
.stream()
8187
.map(method ->
8288
new HttpServiceMethod(
83-
method, serviceType, this.argumentResolvers,
84-
this.clientAdapter, this.reactiveAdapterRegistry, this.blockTimeout))
89+
method, serviceType, this.argumentResolvers, this.clientAdapter,
90+
this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout))
8591
.toList();
8692

8793
return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(methods));
@@ -114,6 +120,9 @@ public final static class Builder {
114120
@Nullable
115121
private ConversionService conversionService;
116122

123+
@Nullable
124+
private StringValueResolver embeddedValueResolver;
125+
117126
private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
118127

119128
private Duration blockTimeout = Duration.ofSeconds(5);
@@ -144,6 +153,18 @@ public Builder setConversionService(ConversionService conversionService) {
144153
return this;
145154
}
146155

156+
/**
157+
* Set the StringValueResolver to use for resolving placeholders and
158+
* expressions in {@link HttpExchange#url()}.
159+
* @param embeddedValueResolver the resolver to use
160+
* @return the same builder instance
161+
* @see org.springframework.context.EmbeddedValueResolverAware
162+
*/
163+
public Builder setEmbeddedValueResolver(@Nullable StringValueResolver embeddedValueResolver) {
164+
this.embeddedValueResolver = embeddedValueResolver;
165+
return this;
166+
}
167+
147168
/**
148169
* Set the {@link ReactiveAdapterRegistry} to use to support different
149170
* asynchronous types for HTTP service method return values.
@@ -176,7 +197,8 @@ public HttpServiceProxyFactory build() {
176197
List<HttpServiceArgumentResolver> resolvers = initArgumentResolvers(conversionService);
177198

178199
return new HttpServiceProxyFactory(
179-
this.clientAdapter, resolvers, this.reactiveAdapterRegistry, this.blockTimeout);
200+
this.clientAdapter, resolvers, this.embeddedValueResolver, this.reactiveAdapterRegistry,
201+
this.blockTimeout);
180202
}
181203

182204
private ConversionService initConversionService() {

spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,11 @@ void methodAnnotatedService() {
168168
@Test
169169
void typeAndMethodAnnotatedService() {
170170

171-
MethodLevelAnnotatedService service = this.proxyFactory.createClient(TypeAndMethodLevelAnnotatedService.class);
171+
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.clientAdapter)
172+
.setEmbeddedValueResolver(value -> (value.equals("${baseUrl}") ? "/base" : value))
173+
.build();
174+
175+
MethodLevelAnnotatedService service = proxyFactory.createClient(TypeAndMethodLevelAnnotatedService.class);
172176

173177
service.performGet();
174178

@@ -281,7 +285,7 @@ private interface MethodLevelAnnotatedService {
281285

282286

283287
@SuppressWarnings("unused")
284-
@HttpExchange(url = "/base", contentType = APPLICATION_CBOR_VALUE, accept = APPLICATION_CBOR_VALUE)
288+
@HttpExchange(url = "${baseUrl}", contentType = APPLICATION_CBOR_VALUE, accept = APPLICATION_CBOR_VALUE)
285289
private interface TypeAndMethodLevelAnnotatedService extends MethodLevelAnnotatedService {
286290
}
287291

0 commit comments

Comments
 (0)