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.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
import org.springframework.web.service.annotation.HttpExchange; import org.springframework.web.service.annotation.HttpExchange;
/** /**
@ -67,13 +68,13 @@ final class HttpServiceMethod {
HttpServiceMethod( HttpServiceMethod(
Method method, Class<?> containingClass, List<HttpServiceArgumentResolver> argumentResolvers, Method method, Class<?> containingClass, List<HttpServiceArgumentResolver> argumentResolvers,
HttpClientAdapter client, ReactiveAdapterRegistry reactiveRegistry, HttpClientAdapter client, @Nullable StringValueResolver embeddedValueResolver,
Duration blockTimeout) { ReactiveAdapterRegistry reactiveRegistry, Duration blockTimeout) {
this.method = method; this.method = method;
this.parameters = initMethodParameters(method); this.parameters = initMethodParameters(method);
this.argumentResolvers = argumentResolvers; this.argumentResolvers = argumentResolvers;
this.requestValuesInitializer = HttpRequestValuesInitializer.create(method, containingClass); this.requestValuesInitializer = HttpRequestValuesInitializer.create(method, containingClass, embeddedValueResolver);
this.responseFunction = ResponseFunction.create(client, method, reactiveRegistry, blockTimeout); 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. * 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 annot1 = AnnotatedElementUtils.findMergedAnnotation(containingClass, HttpExchange.class);
HttpExchange annot2 = AnnotatedElementUtils.findMergedAnnotation(method, HttpExchange.class); HttpExchange annot2 = AnnotatedElementUtils.findMergedAnnotation(method, HttpExchange.class);
@ -169,7 +171,7 @@ final class HttpServiceMethod {
Assert.notNull(annot2, "Expected HttpRequest annotation"); Assert.notNull(annot2, "Expected HttpRequest annotation");
HttpMethod httpMethod = initHttpMethod(annot1, annot2); HttpMethod httpMethod = initHttpMethod(annot1, annot2);
String url = initUrl(annot1, annot2); String url = initUrl(annot1, annot2, embeddedValueResolver);
MediaType contentType = initContentType(annot1, annot2); MediaType contentType = initContentType(annot1, annot2);
List<MediaType> acceptableMediaTypes = initAccept(annot1, annot2); List<MediaType> acceptableMediaTypes = initAccept(annot1, annot2);
@ -194,11 +196,17 @@ final class HttpServiceMethod {
} }
@Nullable @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 url1 = (typeAnnot != null ? typeAnnot.url() : null);
String url2 = annot.url(); String url2 = annot.url();
if (embeddedValueResolver != null) {
url1 = (url1 != null ? embeddedValueResolver.resolveStringValue(url1) : null);
url2 = embeddedValueResolver.resolveStringValue(url2);
}
boolean hasUrl1 = StringUtils.hasText(url1); boolean hasUrl1 = StringUtils.hasText(url1);
boolean hasUrl2 = StringUtils.hasText(url2); 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.format.support.DefaultFormattingConversionService;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringValueResolver;
import org.springframework.web.service.annotation.HttpExchange; import org.springframework.web.service.annotation.HttpExchange;
/** /**
@ -50,6 +51,9 @@ public final class HttpServiceProxyFactory {
private final List<HttpServiceArgumentResolver> argumentResolvers; private final List<HttpServiceArgumentResolver> argumentResolvers;
@Nullable
private final StringValueResolver embeddedValueResolver;
private final ReactiveAdapterRegistry reactiveAdapterRegistry; private final ReactiveAdapterRegistry reactiveAdapterRegistry;
private final Duration blockTimeout; private final Duration blockTimeout;
@ -57,10 +61,12 @@ public final class HttpServiceProxyFactory {
private HttpServiceProxyFactory( private HttpServiceProxyFactory(
HttpClientAdapter clientAdapter, List<HttpServiceArgumentResolver> argumentResolvers, HttpClientAdapter clientAdapter, List<HttpServiceArgumentResolver> argumentResolvers,
ReactiveAdapterRegistry reactiveAdapterRegistry, Duration blockTimeout) { @Nullable StringValueResolver embeddedValueResolver, ReactiveAdapterRegistry reactiveAdapterRegistry,
Duration blockTimeout) {
this.clientAdapter = clientAdapter; this.clientAdapter = clientAdapter;
this.argumentResolvers = argumentResolvers; this.argumentResolvers = argumentResolvers;
this.embeddedValueResolver = embeddedValueResolver;
this.reactiveAdapterRegistry = reactiveAdapterRegistry; this.reactiveAdapterRegistry = reactiveAdapterRegistry;
this.blockTimeout = blockTimeout; this.blockTimeout = blockTimeout;
} }
@ -80,8 +86,8 @@ public final class HttpServiceProxyFactory {
.stream() .stream()
.map(method -> .map(method ->
new HttpServiceMethod( new HttpServiceMethod(
method, serviceType, this.argumentResolvers, method, serviceType, this.argumentResolvers, this.clientAdapter,
this.clientAdapter, this.reactiveAdapterRegistry, this.blockTimeout)) this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout))
.toList(); .toList();
return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(methods)); return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(methods));
@ -114,6 +120,9 @@ public final class HttpServiceProxyFactory {
@Nullable @Nullable
private ConversionService conversionService; private ConversionService conversionService;
@Nullable
private StringValueResolver embeddedValueResolver;
private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
private Duration blockTimeout = Duration.ofSeconds(5); private Duration blockTimeout = Duration.ofSeconds(5);
@ -144,6 +153,18 @@ public final class HttpServiceProxyFactory {
return this; 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 * Set the {@link ReactiveAdapterRegistry} to use to support different
* asynchronous types for HTTP service method return values. * asynchronous types for HTTP service method return values.
@ -176,7 +197,8 @@ public final class HttpServiceProxyFactory {
List<HttpServiceArgumentResolver> resolvers = initArgumentResolvers(conversionService); List<HttpServiceArgumentResolver> resolvers = initArgumentResolvers(conversionService);
return new HttpServiceProxyFactory( return new HttpServiceProxyFactory(
this.clientAdapter, resolvers, this.reactiveAdapterRegistry, this.blockTimeout); this.clientAdapter, resolvers, this.embeddedValueResolver, this.reactiveAdapterRegistry,
this.blockTimeout);
} }
private ConversionService initConversionService() { private ConversionService initConversionService() {

View File

@ -168,7 +168,11 @@ public class HttpServiceMethodTests {
@Test @Test
void typeAndMethodAnnotatedService() { 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(); service.performGet();
@ -281,7 +285,7 @@ public class HttpServiceMethodTests {
@SuppressWarnings("unused") @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 { private interface TypeAndMethodLevelAnnotatedService extends MethodLevelAnnotatedService {
} }