Browse Source

Support property placeholders in HTTP service registry

Implement EmbeddedValueResolverAware to resolve ${...} placeholders
in @HttpExchange URL attributes.

See gh-36126
Signed-off-by: Juhwan Lee <jhan0121@gmail.com>
pull/36132/head
Juhwan Lee 3 weeks ago committed by rstoyanchev
parent
commit
3df66580f3
  1. 22
      spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBean.java
  2. 93
      spring-web/src/test/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBeanTests.java

22
spring-web/src/main/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBean.java

@ -39,10 +39,12 @@ import org.springframework.beans.factory.FactoryBean; @@ -39,10 +39,12 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringValueResolver;
import org.springframework.web.service.invoker.HttpExchangeAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
@ -61,8 +63,8 @@ import org.springframework.web.service.invoker.HttpServiceProxyFactory; @@ -61,8 +63,8 @@ import org.springframework.web.service.invoker.HttpServiceProxyFactory;
* @see AbstractHttpServiceRegistrar
*/
public final class HttpServiceProxyRegistryFactoryBean
implements ApplicationContextAware, BeanClassLoaderAware, InitializingBean,
FactoryBean<HttpServiceProxyRegistry> {
implements ApplicationContextAware, BeanClassLoaderAware, EmbeddedValueResolverAware,
InitializingBean, FactoryBean<HttpServiceProxyRegistry> {
private static final Map<HttpServiceGroup.ClientType, HttpServiceGroupAdapter<?>> groupAdapters =
GroupAdapterInitializer.initGroupAdapters();
@ -76,6 +78,7 @@ public final class HttpServiceProxyRegistryFactoryBean @@ -76,6 +78,7 @@ public final class HttpServiceProxyRegistryFactoryBean
private @Nullable HttpServiceProxyRegistry proxyRegistry;
private @Nullable StringValueResolver embeddedValueResolver;
HttpServiceProxyRegistryFactoryBean(GroupsMetadata groupsMetadata) {
this.groupsMetadata = groupsMetadata;
@ -92,6 +95,11 @@ public final class HttpServiceProxyRegistryFactoryBean @@ -92,6 +95,11 @@ public final class HttpServiceProxyRegistryFactoryBean
this.beanClassLoader = beanClassLoader;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.embeddedValueResolver = resolver;
}
@Override
public Class<?> getObjectType() {
return HttpServiceProxyRegistry.class;
@ -105,7 +113,7 @@ public final class HttpServiceProxyRegistryFactoryBean @@ -105,7 +113,7 @@ public final class HttpServiceProxyRegistryFactoryBean
// Create the groups from the metadata
Set<ConfigurableGroup> groups = this.groupsMetadata.groups(this.beanClassLoader).stream()
.map(ConfigurableGroup::new)
.map(group -> new ConfigurableGroup(group, this.embeddedValueResolver))
.collect(Collectors.toSet());
// Apply group configurers
@ -169,11 +177,14 @@ public final class HttpServiceProxyRegistryFactoryBean @@ -169,11 +177,14 @@ public final class HttpServiceProxyRegistryFactoryBean
private @Nullable Object clientBuilder;
private final @Nullable StringValueResolver embeddedValueResolver;
private final HttpServiceProxyFactory.Builder proxyFactoryBuilder = HttpServiceProxyFactory.builder();
ConfigurableGroup(HttpServiceGroup group) {
ConfigurableGroup(HttpServiceGroup group, @Nullable StringValueResolver embeddedValueResolver) {
this.group = group;
this.groupAdapter = getGroupAdapter(group.clientType());
this.embeddedValueResolver = embeddedValueResolver;
}
private static HttpServiceGroupAdapter<?> getGroupAdapter(HttpServiceGroup.ClientType clientType) {
@ -218,6 +229,9 @@ public final class HttpServiceProxyRegistryFactoryBean @@ -218,6 +229,9 @@ public final class HttpServiceProxyRegistryFactoryBean
public Map<Class<?>, Object> createProxies() {
Map<Class<?>, Object> map = new LinkedHashMap<>(this.group.httpServiceTypes().size());
HttpExchangeAdapter adapter = this.groupAdapter.createExchangeAdapter(getClientBuilder());
if (this.embeddedValueResolver != null) {
this.proxyFactoryBuilder.embeddedValueResolver(this.embeddedValueResolver);
}
HttpServiceProxyFactory factory = this.proxyFactoryBuilder.exchangeAdapter(adapter).build();
this.group.httpServiceTypes().forEach(type -> map.put(type, factory.createClient(type)));
return map;

93
spring-web/src/test/java/org/springframework/web/service/registry/HttpServiceProxyRegistryFactoryBeanTests.java

@ -16,18 +16,23 @@ @@ -16,18 +16,23 @@
package org.springframework.web.service.registry;
import java.net.URI;
import java.util.List;
import java.util.function.Predicate;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringValueResolver;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
import org.springframework.web.service.registry.echo.EchoA;
import org.springframework.web.service.registry.echo.EchoB;
@ -37,6 +42,7 @@ import org.springframework.web.testfixture.http.client.MockClientHttpRequest; @@ -37,6 +42,7 @@ import org.springframework.web.testfixture.http.client.MockClientHttpRequest;
import org.springframework.web.testfixture.http.client.MockClientHttpResponse;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
@ -95,6 +101,87 @@ public class HttpServiceProxyRegistryFactoryBeanTests { @@ -95,6 +101,87 @@ public class HttpServiceProxyRegistryFactoryBeanTests {
verify(requestFactory, atLeastOnce()).createRequest(any(), any());
}
@Test
void propertyPlaceholderInHttpExchangeUrlIsResolved() throws Exception {
GroupsMetadata groupsMetadata = new GroupsMetadata();
groupsMetadata.getOrCreateGroup("testGroup", REST_CLIENT)
.httpServiceTypeNames()
.add(PlaceholderService.class.getName());
ClientHttpRequestFactory requestFactory = Mockito.mock(ClientHttpRequestFactory.class);
MockClientHttpRequest mockRequest = new MockClientHttpRequest();
mockRequest.setResponse(new MockClientHttpResponse());
ArgumentCaptor<URI> uriCaptor = ArgumentCaptor.forClass(URI.class);
given(requestFactory.createRequest(uriCaptor.capture(), any())).willReturn(mockRequest);
StringValueResolver resolver = value -> {
if (value.contains("${test.base.url}")) {
return value.replace("${test.base.url}", "https://api.example.com");
}
return value;
};
RestClient.Builder clientBuilder = RestClient.builder().requestFactory(requestFactory);
RestClientHttpServiceGroupConfigurer configurer = groups -> groups.forEachClient(group -> clientBuilder);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(RestClientHttpServiceGroupConfigurer.class, () -> configurer);
context.refresh();
HttpServiceProxyRegistryFactoryBean factoryBean = new HttpServiceProxyRegistryFactoryBean(groupsMetadata);
factoryBean.setApplicationContext(context);
factoryBean.setBeanClassLoader(getClass().getClassLoader());
factoryBean.setEmbeddedValueResolver(resolver);
factoryBean.afterPropertiesSet();
HttpServiceProxyRegistry registry = factoryBean.getObject();
PlaceholderService service = registry.getClient(PlaceholderService.class);
service.callEndpoint();
URI requestedUri = uriCaptor.getValue();
assertThat(requestedUri.toString())
.startsWith("https://api.example.com")
.doesNotContain("${")
.contains("/endpoint");
}
@Test
void withoutResolverPlaceholderRemainsUnresolved() throws Exception {
GroupsMetadata groupsMetadata = new GroupsMetadata();
groupsMetadata.getOrCreateGroup("testGroup", REST_CLIENT)
.httpServiceTypeNames()
.add(PlaceholderService.class.getName());
ClientHttpRequestFactory requestFactory = Mockito.mock(ClientHttpRequestFactory.class);
MockClientHttpRequest capturedRequest = new MockClientHttpRequest();
capturedRequest.setResponse(new MockClientHttpResponse());
given(requestFactory.createRequest(any(), any())).willReturn(capturedRequest);
RestClient.Builder clientBuilder = RestClient.builder().requestFactory(requestFactory);
RestClientHttpServiceGroupConfigurer configurer = groups ->
groups.forEachClient(group -> clientBuilder);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(RestClientHttpServiceGroupConfigurer.class, () -> configurer);
context.refresh();
HttpServiceProxyRegistryFactoryBean factoryBean = new HttpServiceProxyRegistryFactoryBean(groupsMetadata);
factoryBean.setApplicationContext(context);
factoryBean.setBeanClassLoader(getClass().getClassLoader());
factoryBean.afterPropertiesSet();
HttpServiceProxyRegistry registry = factoryBean.getObject();
PlaceholderService service = registry.getClient(PlaceholderService.class);
assertThatThrownBy(service::callEndpoint)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("test.base.url");
}
private HttpServiceProxyRegistry initProxyRegistry(
RestClientHttpServiceGroupConfigurer groupConfigurer, GroupsMetadata groupsMetadata) {
@ -136,4 +223,10 @@ public class HttpServiceProxyRegistryFactoryBeanTests { @@ -136,4 +223,10 @@ public class HttpServiceProxyRegistryFactoryBeanTests {
}
}
@HttpExchange(url = "${test.base.url}")
interface PlaceholderService {
@GetExchange("/endpoint")
String callEndpoint();
}
}

Loading…
Cancel
Save