Resolve placeholders in HttpExchange#url

Closes gh-28492
This commit is contained in:
rstoyanchev 2022-05-19 20:39:12 +01:00
parent ce568468ae
commit 2a2fba6a37
3 changed files with 46 additions and 12 deletions

View File

@ -42,6 +42,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
import org.springframework.web.service.annotation.HttpExchange;
/**
@ -67,13 +68,13 @@ final class HttpServiceMethod {
HttpServiceMethod(
Method method, Class<?> containingClass, List<HttpServiceArgumentResolver> argumentResolvers,
HttpClientAdapter client, ReactiveAdapterRegistry reactiveRegistry,
Duration blockTimeout) {
HttpClientAdapter client, @Nullable StringValueResolver embeddedValueResolver,
ReactiveAdapterRegistry reactiveRegistry, Duration blockTimeout) {
this.method = method;
this.parameters = initMethodParameters(method);
this.argumentResolvers = argumentResolvers;
this.requestValuesInitializer = HttpRequestValuesInitializer.create(method, containingClass);
this.requestValuesInitializer = HttpRequestValuesInitializer.create(method, containingClass, embeddedValueResolver);
this.responseFunction = ResponseFunction.create(client, method, reactiveRegistry, blockTimeout);
}
@ -161,7 +162,8 @@ final class HttpServiceMethod {
/**
* Introspect the method and create the request factory for it.
*/
public static HttpRequestValuesInitializer create(Method method, Class<?> containingClass) {
public static HttpRequestValuesInitializer create(
Method method, Class<?> containingClass, @Nullable StringValueResolver embeddedValueResolver) {
HttpExchange annot1 = AnnotatedElementUtils.findMergedAnnotation(containingClass, HttpExchange.class);
HttpExchange annot2 = AnnotatedElementUtils.findMergedAnnotation(method, HttpExchange.class);
@ -169,7 +171,7 @@ final class HttpServiceMethod {
Assert.notNull(annot2, "Expected HttpRequest annotation");
HttpMethod httpMethod = initHttpMethod(annot1, annot2);
String url = initUrl(annot1, annot2);
String url = initUrl(annot1, annot2, embeddedValueResolver);
MediaType contentType = initContentType(annot1, annot2);
List<MediaType> acceptableMediaTypes = initAccept(annot1, annot2);
@ -194,11 +196,17 @@ final class HttpServiceMethod {
}
@Nullable
private static String initUrl(@Nullable HttpExchange typeAnnot, HttpExchange annot) {
private static String initUrl(
@Nullable HttpExchange typeAnnot, HttpExchange annot, @Nullable StringValueResolver embeddedValueResolver) {
String url1 = (typeAnnot != null ? typeAnnot.url() : null);
String url2 = annot.url();
if (embeddedValueResolver != null) {
url1 = (url1 != null ? embeddedValueResolver.resolveStringValue(url1) : null);
url2 = embeddedValueResolver.resolveStringValue(url2);
}
boolean hasUrl1 = StringUtils.hasText(url1);
boolean hasUrl2 = StringUtils.hasText(url2);

View File

@ -35,6 +35,7 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringValueResolver;
import org.springframework.web.service.annotation.HttpExchange;
/**
@ -50,6 +51,9 @@ public final class HttpServiceProxyFactory {
private final List<HttpServiceArgumentResolver> argumentResolvers;
@Nullable
private final StringValueResolver embeddedValueResolver;
private final ReactiveAdapterRegistry reactiveAdapterRegistry;
private final Duration blockTimeout;
@ -57,10 +61,12 @@ public final class HttpServiceProxyFactory {
private HttpServiceProxyFactory(
HttpClientAdapter clientAdapter, List<HttpServiceArgumentResolver> argumentResolvers,
ReactiveAdapterRegistry reactiveAdapterRegistry, Duration blockTimeout) {
@Nullable StringValueResolver embeddedValueResolver, ReactiveAdapterRegistry reactiveAdapterRegistry,
Duration blockTimeout) {
this.clientAdapter = clientAdapter;
this.argumentResolvers = argumentResolvers;
this.embeddedValueResolver = embeddedValueResolver;
this.reactiveAdapterRegistry = reactiveAdapterRegistry;
this.blockTimeout = blockTimeout;
}
@ -80,8 +86,8 @@ public final class HttpServiceProxyFactory {
.stream()
.map(method ->
new HttpServiceMethod(
method, serviceType, this.argumentResolvers,
this.clientAdapter, this.reactiveAdapterRegistry, this.blockTimeout))
method, serviceType, this.argumentResolvers, this.clientAdapter,
this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout))
.toList();
return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(methods));
@ -114,6 +120,9 @@ public final class HttpServiceProxyFactory {
@Nullable
private ConversionService conversionService;
@Nullable
private StringValueResolver embeddedValueResolver;
private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
private Duration blockTimeout = Duration.ofSeconds(5);
@ -144,6 +153,18 @@ public final class HttpServiceProxyFactory {
return this;
}
/**
* Set the StringValueResolver to use for resolving placeholders and
* expressions in {@link HttpExchange#url()}.
* @param embeddedValueResolver the resolver to use
* @return the same builder instance
* @see org.springframework.context.EmbeddedValueResolverAware
*/
public Builder setEmbeddedValueResolver(@Nullable StringValueResolver embeddedValueResolver) {
this.embeddedValueResolver = embeddedValueResolver;
return this;
}
/**
* Set the {@link ReactiveAdapterRegistry} to use to support different
* asynchronous types for HTTP service method return values.
@ -176,7 +197,8 @@ public final class HttpServiceProxyFactory {
List<HttpServiceArgumentResolver> resolvers = initArgumentResolvers(conversionService);
return new HttpServiceProxyFactory(
this.clientAdapter, resolvers, this.reactiveAdapterRegistry, this.blockTimeout);
this.clientAdapter, resolvers, this.embeddedValueResolver, this.reactiveAdapterRegistry,
this.blockTimeout);
}
private ConversionService initConversionService() {

View File

@ -168,7 +168,11 @@ public class HttpServiceMethodTests {
@Test
void typeAndMethodAnnotatedService() {
MethodLevelAnnotatedService service = this.proxyFactory.createClient(TypeAndMethodLevelAnnotatedService.class);
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.clientAdapter)
.setEmbeddedValueResolver(value -> (value.equals("${baseUrl}") ? "/base" : value))
.build();
MethodLevelAnnotatedService service = proxyFactory.createClient(TypeAndMethodLevelAnnotatedService.class);
service.performGet();
@ -281,7 +285,7 @@ public class HttpServiceMethodTests {
@SuppressWarnings("unused")
@HttpExchange(url = "/base", contentType = APPLICATION_CBOR_VALUE, accept = APPLICATION_CBOR_VALUE)
@HttpExchange(url = "${baseUrl}", contentType = APPLICATION_CBOR_VALUE, accept = APPLICATION_CBOR_VALUE)
private interface TypeAndMethodLevelAnnotatedService extends MethodLevelAnnotatedService {
}