Add Builder to HttpServiceProxyFactory
HttpServiceProxyFactory now support programmatic initialization through a builder, while bean-style initialization is deprecated. See gh-29296
This commit is contained in:
parent
e03abc94ff
commit
5cb3af708c
|
|
@ -43,10 +43,10 @@ import org.springframework.util.StringValueResolver;
|
||||||
import org.springframework.web.service.annotation.HttpExchange;
|
import org.springframework.web.service.annotation.HttpExchange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for creating a client proxy given an HTTP service interface with
|
* Factory to create a client proxy from an HTTP service interface with
|
||||||
* {@link HttpExchange @HttpExchange} methods.
|
* {@link HttpExchange @HttpExchange} methods.
|
||||||
*
|
*
|
||||||
* <p>This class is intended to be declared as a bean in a Spring configuration.
|
* <p>To create an instance, use static methods to obtain a {@link Builder Builder}.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 6.0
|
* @since 6.0
|
||||||
|
|
@ -54,81 +54,98 @@ import org.springframework.web.service.annotation.HttpExchange;
|
||||||
*/
|
*/
|
||||||
public final class HttpServiceProxyFactory implements InitializingBean, EmbeddedValueResolverAware {
|
public final class HttpServiceProxyFactory implements InitializingBean, EmbeddedValueResolverAware {
|
||||||
|
|
||||||
private final HttpClientAdapter clientAdapter;
|
@Nullable
|
||||||
|
private final BuilderInitializedFactory builderInitializedFactory;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private List<HttpServiceArgumentResolver> customArgumentResolvers;
|
private final BeanStyleFactory beanStyleFactory;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private List<HttpServiceArgumentResolver> argumentResolvers;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private ConversionService conversionService;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private StringValueResolver embeddedValueResolver;
|
|
||||||
|
|
||||||
private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
|
|
||||||
|
|
||||||
private Duration blockTimeout = Duration.ofSeconds(5);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance with the underlying HTTP client to use.
|
* Create an instance with the underlying HTTP client to use.
|
||||||
* @param clientAdapter an adapter for the client
|
* @param clientAdapter an adapter for the client
|
||||||
* @see org.springframework.web.reactive.function.client.support.WebClientAdapter#createHttpServiceProxyFactory(org.springframework.web.reactive.function.client.WebClient)
|
* @deprecated as of 6.0 RC1 in favor of using the Builder to initialize
|
||||||
|
* the HttpServiceProxyFactory instance.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.0.0-RC1", forRemoval = true)
|
||||||
public HttpServiceProxyFactory(HttpClientAdapter clientAdapter) {
|
public HttpServiceProxyFactory(HttpClientAdapter clientAdapter) {
|
||||||
Assert.notNull(clientAdapter, "HttpClientAdapter is required");
|
this.beanStyleFactory = new BeanStyleFactory(clientAdapter);
|
||||||
this.clientAdapter = clientAdapter;
|
this.builderInitializedFactory = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpServiceProxyFactory(
|
||||||
|
HttpClientAdapter clientAdapter, List<HttpServiceArgumentResolver> argumentResolvers,
|
||||||
|
@Nullable StringValueResolver embeddedValueResolver,
|
||||||
|
ReactiveAdapterRegistry reactiveAdapterRegistry, Duration blockTimeout) {
|
||||||
|
|
||||||
|
this.beanStyleFactory = null;
|
||||||
|
this.builderInitializedFactory = new BuilderInitializedFactory(
|
||||||
|
clientAdapter, argumentResolvers, embeddedValueResolver, reactiveAdapterRegistry, blockTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a custom argument resolver, invoked ahead of default resolvers.
|
* Register a custom argument resolver, invoked ahead of default resolvers.
|
||||||
* @param resolver the resolver to add
|
* @param resolver the resolver to add
|
||||||
|
* @deprecated as of 6.0 RC1 in favor of using the Builder to initialize
|
||||||
|
* the HttpServiceProxyFactory instance.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.0.0-RC1", forRemoval = true)
|
||||||
public void addCustomArgumentResolver(HttpServiceArgumentResolver resolver) {
|
public void addCustomArgumentResolver(HttpServiceArgumentResolver resolver) {
|
||||||
if (this.customArgumentResolvers == null) {
|
Assert.state(this.beanStyleFactory != null, "HttpServiceProxyFactory was created through the builder");
|
||||||
this.customArgumentResolvers = new ArrayList<>();
|
this.beanStyleFactory.addCustomArgumentResolver(resolver);
|
||||||
}
|
|
||||||
this.customArgumentResolvers.add(resolver);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the custom argument resolvers to use, ahead of default resolvers.
|
* Set the custom argument resolvers to use, ahead of default resolvers.
|
||||||
* @param resolvers the resolvers to use
|
* @param resolvers the resolvers to use
|
||||||
|
* @deprecated as of 6.0 RC1 in favor of using the Builder to initialize
|
||||||
|
* the HttpServiceProxyFactory instance.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.0.0-RC1", forRemoval = true)
|
||||||
public void setCustomArgumentResolvers(List<HttpServiceArgumentResolver> resolvers) {
|
public void setCustomArgumentResolvers(List<HttpServiceArgumentResolver> resolvers) {
|
||||||
this.customArgumentResolvers = new ArrayList<>(resolvers);
|
Assert.state(this.beanStyleFactory != null, "HttpServiceProxyFactory was created through the builder");
|
||||||
|
this.beanStyleFactory.setCustomArgumentResolvers(resolvers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the {@link ConversionService} to use where input values need to
|
* Set the {@link ConversionService} to use where input values need to
|
||||||
* be formatted as Strings.
|
* be formatted as Strings.
|
||||||
* <p>By default this is {@link DefaultFormattingConversionService}.
|
* <p>By default this is {@link DefaultFormattingConversionService}.
|
||||||
|
* @deprecated as of 6.0 RC1 in favor of using the Builder to initialize
|
||||||
|
* the HttpServiceProxyFactory instance.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.0.0-RC1", forRemoval = true)
|
||||||
public void setConversionService(ConversionService conversionService) {
|
public void setConversionService(ConversionService conversionService) {
|
||||||
this.conversionService = conversionService;
|
Assert.state(this.beanStyleFactory != null, "HttpServiceProxyFactory was created through the builder");
|
||||||
|
this.beanStyleFactory.setConversionService(conversionService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the StringValueResolver to use for resolving placeholders and
|
* Set the StringValueResolver to use for resolving placeholders and
|
||||||
* expressions in {@link HttpExchange#url()}.
|
* expressions in {@link HttpExchange#url()}.
|
||||||
* @param resolver the resolver to use
|
* @param resolver the resolver to use
|
||||||
|
* @deprecated as of 6.0 RC1 in favor of using the Builder to initialize
|
||||||
|
* an HttpServiceProxyFactory instance.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.0.0-RC1", forRemoval = true)
|
||||||
@Override
|
@Override
|
||||||
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
||||||
this.embeddedValueResolver = resolver;
|
Assert.state(this.beanStyleFactory != null, "HttpServiceProxyFactory was created through the builder");
|
||||||
|
this.beanStyleFactory.setEmbeddedValueResolver(resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
* <p>By default this is {@link ReactiveAdapterRegistry#getSharedInstance()}.
|
* <p>By default this is {@link ReactiveAdapterRegistry#getSharedInstance()}.
|
||||||
|
* @deprecated as of 6.0 RC1 in favor of using the Builder to initialize
|
||||||
|
* an HttpServiceProxyFactory instance.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.0.0-RC1", forRemoval = true)
|
||||||
public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) {
|
public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) {
|
||||||
this.reactiveAdapterRegistry = registry;
|
Assert.state(this.beanStyleFactory != null, "HttpServiceProxyFactory was created through the builder");
|
||||||
|
this.beanStyleFactory.setReactiveAdapterRegistry(registry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -136,42 +153,22 @@ public final class HttpServiceProxyFactory implements InitializingBean, Embedded
|
||||||
* with a synchronous (blocking) method signature.
|
* with a synchronous (blocking) method signature.
|
||||||
* <p>By default this is 5 seconds.
|
* <p>By default this is 5 seconds.
|
||||||
* @param blockTimeout the timeout value
|
* @param blockTimeout the timeout value
|
||||||
|
* @deprecated as of 6.0 RC1 in favor of using the Builder to initialize
|
||||||
|
* an HttpServiceProxyFactory instance.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.0.0-RC1", forRemoval = true)
|
||||||
public void setBlockTimeout(Duration blockTimeout) {
|
public void setBlockTimeout(Duration blockTimeout) {
|
||||||
this.blockTimeout = blockTimeout;
|
Assert.state(this.beanStyleFactory != null, "HttpServiceProxyFactory was created through the builder");
|
||||||
|
this.beanStyleFactory.setBlockTimeout(blockTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
if (this.beanStyleFactory != null) {
|
||||||
this.conversionService = (this.conversionService != null ?
|
this.beanStyleFactory.afterPropertiesSet();
|
||||||
this.conversionService : new DefaultFormattingConversionService());
|
|
||||||
|
|
||||||
this.argumentResolvers = initArgumentResolvers(this.conversionService);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<HttpServiceArgumentResolver> initArgumentResolvers(ConversionService conversionService) {
|
|
||||||
List<HttpServiceArgumentResolver> resolvers = new ArrayList<>();
|
|
||||||
|
|
||||||
// Custom
|
|
||||||
if (this.customArgumentResolvers != null) {
|
|
||||||
resolvers.addAll(this.customArgumentResolvers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Annotation-based
|
|
||||||
resolvers.add(new RequestHeaderArgumentResolver(conversionService));
|
|
||||||
resolvers.add(new RequestBodyArgumentResolver(this.reactiveAdapterRegistry));
|
|
||||||
resolvers.add(new PathVariableArgumentResolver(conversionService));
|
|
||||||
resolvers.add(new RequestParamArgumentResolver(conversionService));
|
|
||||||
resolvers.add(new CookieValueArgumentResolver(conversionService));
|
|
||||||
resolvers.add(new RequestAttributeArgumentResolver());
|
|
||||||
|
|
||||||
// Specific type
|
|
||||||
resolvers.add(new UrlArgumentResolver());
|
|
||||||
resolvers.add(new HttpMethodArgumentResolver());
|
|
||||||
|
|
||||||
return resolvers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -183,26 +180,163 @@ public final class HttpServiceProxyFactory implements InitializingBean, Embedded
|
||||||
* @return the created proxy
|
* @return the created proxy
|
||||||
*/
|
*/
|
||||||
public <S> S createClient(Class<S> serviceType) {
|
public <S> S createClient(Class<S> serviceType) {
|
||||||
|
if (this.builderInitializedFactory != null) {
|
||||||
List<HttpServiceMethod> httpServiceMethods =
|
return this.builderInitializedFactory.createClient(serviceType);
|
||||||
MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod).stream()
|
}
|
||||||
.map(method -> createHttpServiceMethod(serviceType, method))
|
else if (this.beanStyleFactory != null) {
|
||||||
.toList();
|
return this.beanStyleFactory.createClient(serviceType);
|
||||||
|
}
|
||||||
return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(httpServiceMethods));
|
else {
|
||||||
|
throw new IllegalStateException("Expected Builder initialized or Bean-style delegate");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isExchangeMethod(Method method) {
|
|
||||||
return AnnotatedElementUtils.hasAnnotation(method, HttpExchange.class);
|
/**
|
||||||
|
* Return an {@link HttpServiceProxyFactory} builder, initialized with the
|
||||||
|
* given client.
|
||||||
|
*/
|
||||||
|
public static Builder builder(HttpClientAdapter clientAdapter) {
|
||||||
|
return new Builder().clientAdapter(clientAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <S> HttpServiceMethod createHttpServiceMethod(Class<S> serviceType, Method method) {
|
/**
|
||||||
Assert.notNull(this.argumentResolvers,
|
* Return an {@link HttpServiceProxyFactory} builder.
|
||||||
"No argument resolvers: afterPropertiesSet was not called");
|
*/
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder to create an {@link HttpServiceProxyFactory}.
|
||||||
|
*/
|
||||||
|
public static final class Builder {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private HttpClientAdapter clientAdapter;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private List<HttpServiceArgumentResolver> argumentResolvers;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ConversionService conversionService;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private StringValueResolver embeddedValueResolver;
|
||||||
|
|
||||||
|
private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Duration blockTimeout;
|
||||||
|
|
||||||
|
private Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide the HTTP client to perform requests through.
|
||||||
|
* @param clientAdapter a client adapted to {@link HttpClientAdapter}
|
||||||
|
* @return this same builder instance
|
||||||
|
*/
|
||||||
|
public Builder clientAdapter(HttpClientAdapter clientAdapter) {
|
||||||
|
this.clientAdapter = clientAdapter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a custom argument resolver, invoked ahead of default resolvers.
|
||||||
|
* @param resolver the resolver to add
|
||||||
|
* @return this same builder instance
|
||||||
|
*/
|
||||||
|
public Builder customArgumentResolver(HttpServiceArgumentResolver resolver) {
|
||||||
|
this.argumentResolvers = (this.argumentResolvers != null ? this.argumentResolvers : new ArrayList<>());
|
||||||
|
this.argumentResolvers.add(resolver);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link ConversionService} to use where input values need to
|
||||||
|
* be formatted as Strings.
|
||||||
|
* <p>By default this is {@link DefaultFormattingConversionService}.
|
||||||
|
* @return this same builder instance
|
||||||
|
*/
|
||||||
|
public Builder conversionService(ConversionService conversionService) {
|
||||||
|
this.conversionService = conversionService;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link StringValueResolver} to use for resolving placeholders
|
||||||
|
* and expressions embedded in {@link HttpExchange#url()}.
|
||||||
|
* @param embeddedValueResolver the resolver to use
|
||||||
|
* @return this same builder instance
|
||||||
|
*/
|
||||||
|
public Builder embeddedValueResolver(StringValueResolver embeddedValueResolver) {
|
||||||
|
this.embeddedValueResolver = embeddedValueResolver;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link ReactiveAdapterRegistry} to use to support different
|
||||||
|
* asynchronous types for HTTP service method return values.
|
||||||
|
* <p>By default this is {@link ReactiveAdapterRegistry#getSharedInstance()}.
|
||||||
|
* @return this same builder instance
|
||||||
|
*/
|
||||||
|
public Builder reactiveAdapterRegistry(ReactiveAdapterRegistry registry) {
|
||||||
|
this.reactiveAdapterRegistry = registry;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure how long to wait for a response for an HTTP service method
|
||||||
|
* with a synchronous (blocking) method signature.
|
||||||
|
* <p>By default this is 5 seconds.
|
||||||
|
* @param blockTimeout the timeout value
|
||||||
|
* @return this same builder instance
|
||||||
|
*/
|
||||||
|
public Builder blockTimeout(Duration blockTimeout) {
|
||||||
|
this.blockTimeout = blockTimeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the {@link HttpServiceProxyFactory} instance.
|
||||||
|
*/
|
||||||
|
public HttpServiceProxyFactory build() {
|
||||||
|
Assert.notNull(this.clientAdapter, "HttpClientAdapter is required");
|
||||||
|
|
||||||
|
return new HttpServiceProxyFactory(
|
||||||
|
this.clientAdapter, initArgumentResolvers(),
|
||||||
|
this.embeddedValueResolver, this.reactiveAdapterRegistry,
|
||||||
|
(this.blockTimeout != null ? this.blockTimeout : Duration.ofSeconds(5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<HttpServiceArgumentResolver> initArgumentResolvers() {
|
||||||
|
List<HttpServiceArgumentResolver> resolvers = new ArrayList<>();
|
||||||
|
|
||||||
|
// Custom
|
||||||
|
if (this.argumentResolvers != null) {
|
||||||
|
resolvers.addAll(this.argumentResolvers);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConversionService service = (this.conversionService != null ?
|
||||||
|
this.conversionService : new DefaultFormattingConversionService());
|
||||||
|
|
||||||
|
// Annotation-based
|
||||||
|
resolvers.add(new RequestHeaderArgumentResolver(service));
|
||||||
|
resolvers.add(new RequestBodyArgumentResolver(this.reactiveAdapterRegistry));
|
||||||
|
resolvers.add(new PathVariableArgumentResolver(service));
|
||||||
|
resolvers.add(new RequestParamArgumentResolver(service));
|
||||||
|
resolvers.add(new CookieValueArgumentResolver(service));
|
||||||
|
resolvers.add(new RequestAttributeArgumentResolver());
|
||||||
|
|
||||||
|
// Specific type
|
||||||
|
resolvers.add(new UrlArgumentResolver());
|
||||||
|
resolvers.add(new HttpMethodArgumentResolver());
|
||||||
|
|
||||||
|
return resolvers;
|
||||||
|
}
|
||||||
|
|
||||||
return new HttpServiceMethod(
|
|
||||||
method, serviceType, this.argumentResolvers, this.clientAdapter,
|
|
||||||
this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -235,4 +369,174 @@ public final class HttpServiceProxyFactory implements InitializingBean, Embedded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary class until bean-style initialization is removed.
|
||||||
|
*/
|
||||||
|
private static final class BuilderInitializedFactory {
|
||||||
|
|
||||||
|
private final HttpClientAdapter clientAdapter;
|
||||||
|
|
||||||
|
private final List<HttpServiceArgumentResolver> argumentResolvers;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final StringValueResolver embeddedValueResolver;
|
||||||
|
|
||||||
|
private final ReactiveAdapterRegistry reactiveAdapterRegistry;
|
||||||
|
|
||||||
|
private final Duration blockTimeout;
|
||||||
|
|
||||||
|
private BuilderInitializedFactory(
|
||||||
|
HttpClientAdapter clientAdapter, List<HttpServiceArgumentResolver> argumentResolvers,
|
||||||
|
@Nullable StringValueResolver embeddedValueResolver,
|
||||||
|
ReactiveAdapterRegistry reactiveAdapterRegistry, Duration blockTimeout) {
|
||||||
|
|
||||||
|
this.clientAdapter = clientAdapter;
|
||||||
|
this.argumentResolvers = argumentResolvers;
|
||||||
|
this.embeddedValueResolver = embeddedValueResolver;
|
||||||
|
this.reactiveAdapterRegistry = reactiveAdapterRegistry;
|
||||||
|
this.blockTimeout = blockTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <S> S createClient(Class<S> serviceType) {
|
||||||
|
|
||||||
|
List<HttpServiceMethod> httpServiceMethods =
|
||||||
|
MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod).stream()
|
||||||
|
.map(method -> createHttpServiceMethod(serviceType, method))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(httpServiceMethods));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isExchangeMethod(Method method) {
|
||||||
|
return AnnotatedElementUtils.hasAnnotation(method, HttpExchange.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <S> HttpServiceMethod createHttpServiceMethod(Class<S> serviceType, Method method) {
|
||||||
|
Assert.notNull(this.argumentResolvers,
|
||||||
|
"No argument resolvers: afterPropertiesSet was not called");
|
||||||
|
|
||||||
|
return new HttpServiceMethod(
|
||||||
|
method, serviceType, this.argumentResolvers, this.clientAdapter,
|
||||||
|
this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary class to support bean-style initialization during deprecation period.
|
||||||
|
*/
|
||||||
|
private static final class BeanStyleFactory implements InitializingBean, EmbeddedValueResolverAware {
|
||||||
|
|
||||||
|
private final HttpClientAdapter clientAdapter;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private List<HttpServiceArgumentResolver> customArgumentResolvers;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private List<HttpServiceArgumentResolver> argumentResolvers;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ConversionService conversionService;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private StringValueResolver embeddedValueResolver;
|
||||||
|
|
||||||
|
private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
|
||||||
|
|
||||||
|
private Duration blockTimeout = Duration.ofSeconds(5);
|
||||||
|
|
||||||
|
BeanStyleFactory(HttpClientAdapter clientAdapter) {
|
||||||
|
Assert.notNull(clientAdapter, "HttpClientAdapter is required");
|
||||||
|
this.clientAdapter = clientAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCustomArgumentResolver(HttpServiceArgumentResolver resolver) {
|
||||||
|
if (this.customArgumentResolvers == null) {
|
||||||
|
this.customArgumentResolvers = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.customArgumentResolvers.add(resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomArgumentResolvers(List<HttpServiceArgumentResolver> resolvers) {
|
||||||
|
this.customArgumentResolvers = new ArrayList<>(resolvers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConversionService(ConversionService conversionService) {
|
||||||
|
this.conversionService = conversionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
||||||
|
this.embeddedValueResolver = resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) {
|
||||||
|
this.reactiveAdapterRegistry = registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockTimeout(Duration blockTimeout) {
|
||||||
|
this.blockTimeout = blockTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
|
||||||
|
this.conversionService = (this.conversionService != null ?
|
||||||
|
this.conversionService : new DefaultFormattingConversionService());
|
||||||
|
|
||||||
|
this.argumentResolvers = initArgumentResolvers(this.conversionService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<HttpServiceArgumentResolver> initArgumentResolvers(ConversionService conversionService) {
|
||||||
|
List<HttpServiceArgumentResolver> resolvers = new ArrayList<>();
|
||||||
|
|
||||||
|
// Custom
|
||||||
|
if (this.customArgumentResolvers != null) {
|
||||||
|
resolvers.addAll(this.customArgumentResolvers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotation-based
|
||||||
|
resolvers.add(new RequestHeaderArgumentResolver(conversionService));
|
||||||
|
resolvers.add(new RequestBodyArgumentResolver(this.reactiveAdapterRegistry));
|
||||||
|
resolvers.add(new PathVariableArgumentResolver(conversionService));
|
||||||
|
resolvers.add(new RequestParamArgumentResolver(conversionService));
|
||||||
|
resolvers.add(new CookieValueArgumentResolver(conversionService));
|
||||||
|
resolvers.add(new RequestAttributeArgumentResolver());
|
||||||
|
|
||||||
|
// Specific type
|
||||||
|
resolvers.add(new UrlArgumentResolver());
|
||||||
|
resolvers.add(new HttpMethodArgumentResolver());
|
||||||
|
|
||||||
|
return resolvers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public <S> S createClient(Class<S> serviceType) {
|
||||||
|
|
||||||
|
List<HttpServiceMethod> httpServiceMethods =
|
||||||
|
MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod).stream()
|
||||||
|
.map(method -> createHttpServiceMethod(serviceType, method))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(httpServiceMethods));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isExchangeMethod(Method method) {
|
||||||
|
return AnnotatedElementUtils.hasAnnotation(method, HttpExchange.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <S> HttpServiceMethod createHttpServiceMethod(Class<S> serviceType, Method method) {
|
||||||
|
Assert.notNull(this.argumentResolvers,
|
||||||
|
"No argument resolvers: afterPropertiesSet was not called");
|
||||||
|
|
||||||
|
return new HttpServiceMethod(
|
||||||
|
method, serviceType, this.argumentResolvers, this.clientAdapter,
|
||||||
|
this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,7 @@ class CookieValueArgumentResolverTests {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
HttpServiceProxyFactory proxyFactory = new HttpServiceProxyFactory(this.client);
|
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build();
|
||||||
proxyFactory.afterPropertiesSet();
|
|
||||||
this.service = proxyFactory.createClient(Service.class);
|
this.service = proxyFactory.createClient(Service.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public class HttpMethodArgumentResolverTests {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
HttpServiceProxyFactory proxyFactory = new HttpServiceProxyFactory(this.client);
|
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build();
|
||||||
proxyFactory.afterPropertiesSet();
|
proxyFactory.afterPropertiesSet();
|
||||||
this.service = proxyFactory.createClient(Service.class);
|
this.service = proxyFactory.createClient(Service.class);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ public class HttpServiceMethodTests {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
this.proxyFactory = new HttpServiceProxyFactory(this.client);
|
this.proxyFactory = HttpServiceProxyFactory.builder(this.client).build();
|
||||||
this.proxyFactory.afterPropertiesSet();
|
this.proxyFactory.afterPropertiesSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,10 +174,10 @@ public class HttpServiceMethodTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void typeAndMethodAnnotatedService() throws Exception {
|
void typeAndMethodAnnotatedService() {
|
||||||
HttpServiceProxyFactory proxyFactory = new HttpServiceProxyFactory(this.client);
|
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client)
|
||||||
proxyFactory.setEmbeddedValueResolver(value -> (value.equals("${baseUrl}") ? "/base" : value));
|
.embeddedValueResolver(value -> (value.equals("${baseUrl}") ? "/base" : value))
|
||||||
proxyFactory.afterPropertiesSet();
|
.build();
|
||||||
|
|
||||||
MethodLevelAnnotatedService service = proxyFactory.createClient(TypeAndMethodLevelAnnotatedService.class);
|
MethodLevelAnnotatedService service = proxyFactory.createClient(TypeAndMethodLevelAnnotatedService.class);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,9 @@ class NamedValueArgumentResolverTests {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
HttpServiceProxyFactory proxyFactory = new HttpServiceProxyFactory(this.client);
|
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client)
|
||||||
proxyFactory.addCustomArgumentResolver(this.argumentResolver);
|
.customArgumentResolver(this.argumentResolver)
|
||||||
|
.build();
|
||||||
proxyFactory.afterPropertiesSet();
|
proxyFactory.afterPropertiesSet();
|
||||||
|
|
||||||
this.service = proxyFactory.createClient(Service.class);
|
this.service = proxyFactory.createClient(Service.class);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class PathVariableArgumentResolverTests {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
HttpServiceProxyFactory proxyFactory = new HttpServiceProxyFactory(this.client);
|
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build();
|
||||||
proxyFactory.afterPropertiesSet();
|
proxyFactory.afterPropertiesSet();
|
||||||
this.service = proxyFactory.createClient(Service.class);
|
this.service = proxyFactory.createClient(Service.class);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,7 @@ class RequestAttributeArgumentResolverTests {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
HttpServiceProxyFactory proxyFactory = new HttpServiceProxyFactory(this.client);
|
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build();
|
||||||
proxyFactory.afterPropertiesSet();
|
|
||||||
this.service = proxyFactory.createClient(Service.class);
|
this.service = proxyFactory.createClient(Service.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,7 @@ public class RequestBodyArgumentResolverTests {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
HttpServiceProxyFactory proxyFactory = new HttpServiceProxyFactory(this.client);
|
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build();
|
||||||
proxyFactory.afterPropertiesSet();
|
|
||||||
this.service = proxyFactory.createClient(Service.class);
|
this.service = proxyFactory.createClient(Service.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,7 @@ class RequestHeaderArgumentResolverTests {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
HttpServiceProxyFactory proxyFactory = new HttpServiceProxyFactory(this.client);
|
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build();
|
||||||
proxyFactory.afterPropertiesSet();
|
|
||||||
this.service = proxyFactory.createClient(Service.class);
|
this.service = proxyFactory.createClient(Service.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,7 @@ public class RequestParamArgumentResolverTests {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
HttpServiceProxyFactory proxyFactory = new HttpServiceProxyFactory(this.client);
|
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build();
|
||||||
proxyFactory.afterPropertiesSet();
|
|
||||||
this.service = proxyFactory.createClient(Service.class);
|
this.service = proxyFactory.createClient(Service.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,7 @@ public class UrlArgumentResolverTests {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
HttpServiceProxyFactory proxyFactory = new HttpServiceProxyFactory(this.client);
|
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(this.client).build();
|
||||||
proxyFactory.afterPropertiesSet();
|
|
||||||
this.service = proxyFactory.createClient(Service.class);
|
this.service = proxyFactory.createClient(Service.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,15 @@ public final class WebClientAdapter implements HttpClientAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link WebClientAdapter} for the given {@code WebClient} instance.
|
||||||
|
* @param webClient the client to use
|
||||||
|
* @return the created adapter instance
|
||||||
|
*/
|
||||||
|
public static WebClientAdapter forClient(WebClient webClient) {
|
||||||
|
return new WebClientAdapter(webClient);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static method to create a {@link HttpServiceProxyFactory} configured to
|
* Static method to create a {@link HttpServiceProxyFactory} configured to
|
||||||
* use the given {@link WebClient} instance. Effectively a shortcut for:
|
* use the given {@link WebClient} instance. Effectively a shortcut for:
|
||||||
|
|
@ -133,7 +142,11 @@ public final class WebClientAdapter implements HttpClientAdapter {
|
||||||
* </pre>
|
* </pre>
|
||||||
* @param webClient the client to use
|
* @param webClient the client to use
|
||||||
* @return the created {@code HttpServiceProxyFactory} instance
|
* @return the created {@code HttpServiceProxyFactory} instance
|
||||||
|
* @deprecated in favor of using {@link #forClient(WebClient)} and
|
||||||
|
* {@link HttpServiceProxyFactory#builder(HttpClientAdapter)}
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("removal")
|
||||||
|
@Deprecated(since = "6.0.0-RC1", forRemoval = true)
|
||||||
public static HttpServiceProxyFactory createHttpServiceProxyFactory(WebClient webClient) {
|
public static HttpServiceProxyFactory createHttpServiceProxyFactory(WebClient webClient) {
|
||||||
return new HttpServiceProxyFactory(new WebClientAdapter(webClient));
|
return new HttpServiceProxyFactory(new WebClientAdapter(webClient));
|
||||||
}
|
}
|
||||||
|
|
@ -143,18 +156,12 @@ public final class WebClientAdapter implements HttpClientAdapter {
|
||||||
* a {@link WebClient.Builder} and uses it to create the client.
|
* a {@link WebClient.Builder} and uses it to create the client.
|
||||||
* @param webClientBuilder a builder to create the client to use with
|
* @param webClientBuilder a builder to create the client to use with
|
||||||
* @return the created {@code HttpServiceProxyFactory} instance
|
* @return the created {@code HttpServiceProxyFactory} instance
|
||||||
|
* @deprecated in favor of using {@link #forClient(WebClient)} and
|
||||||
|
* {@link HttpServiceProxyFactory#builder(HttpClientAdapter)}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.0.0-RC1", forRemoval = true)
|
||||||
public static HttpServiceProxyFactory createHttpServiceProxyFactory(WebClient.Builder webClientBuilder) {
|
public static HttpServiceProxyFactory createHttpServiceProxyFactory(WebClient.Builder webClientBuilder) {
|
||||||
return createHttpServiceProxyFactory(webClientBuilder.build());
|
return createHttpServiceProxyFactory(webClientBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a {@link WebClientAdapter} for the given {@code WebClient} instance.
|
|
||||||
* @param webClient the client to use
|
|
||||||
* @return the created adapter instance
|
|
||||||
*/
|
|
||||||
public static WebClientAdapter forClient(WebClient webClient) {
|
|
||||||
return new WebClientAdapter(webClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,10 +105,11 @@ public class WebClientHttpServiceProxyTests {
|
||||||
return initHttpService(webClient);
|
return initHttpService(webClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestHttpService initHttpService(WebClient webClient) throws Exception {
|
private TestHttpService initHttpService(WebClient webClient) {
|
||||||
HttpServiceProxyFactory factory = WebClientAdapter.createHttpServiceProxyFactory(webClient);
|
return HttpServiceProxyFactory.builder()
|
||||||
factory.afterPropertiesSet();
|
.clientAdapter(WebClientAdapter.forClient(webClient))
|
||||||
return factory.createClient(TestHttpService.class);
|
.build()
|
||||||
|
.createClient(TestHttpService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareResponse(Consumer<MockResponse> consumer) {
|
private void prepareResponse(Consumer<MockResponse> consumer) {
|
||||||
|
|
|
||||||
|
|
@ -390,8 +390,7 @@ Two, create a proxy that will perform the declared HTTP exchanges:
|
||||||
[source,java,indent=0,subs="verbatim,quotes"]
|
[source,java,indent=0,subs="verbatim,quotes"]
|
||||||
----
|
----
|
||||||
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
|
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
|
||||||
HttpServiceProxyFactory factory = WebClientAdapter.createHttpServiceProxyFactory(client);
|
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
|
||||||
factory.afterPropertiesSet();
|
|
||||||
|
|
||||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||||
----
|
----
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue