Refactor HttpServiceProxyFactory for use as a bean
Closes gh-28505
This commit is contained in:
@@ -28,6 +28,8 @@ import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.core.MethodIntrospector;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
@@ -42,36 +44,133 @@ import org.springframework.web.service.annotation.HttpExchange;
|
||||
* Factory for creating a client proxy given an HTTP service interface with
|
||||
* {@link HttpExchange @HttpExchange} methods.
|
||||
*
|
||||
* <p>This class is intended to be declared as a bean in a Spring configuration.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class HttpServiceProxyFactory {
|
||||
public final class HttpServiceProxyFactory implements InitializingBean, EmbeddedValueResolverAware {
|
||||
|
||||
private final HttpClientAdapter clientAdapter;
|
||||
|
||||
private final List<HttpServiceArgumentResolver> argumentResolvers;
|
||||
@Nullable
|
||||
private List<HttpServiceArgumentResolver> customArgumentResolvers;
|
||||
|
||||
@Nullable
|
||||
private final StringValueResolver embeddedValueResolver;
|
||||
private List<HttpServiceArgumentResolver> argumentResolvers;
|
||||
|
||||
private final ReactiveAdapterRegistry reactiveAdapterRegistry;
|
||||
@Nullable
|
||||
private ConversionService conversionService;
|
||||
|
||||
private final Duration blockTimeout;
|
||||
@Nullable
|
||||
private StringValueResolver embeddedValueResolver;
|
||||
|
||||
private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
|
||||
|
||||
private Duration blockTimeout = Duration.ofSeconds(5);
|
||||
|
||||
|
||||
private HttpServiceProxyFactory(
|
||||
HttpClientAdapter clientAdapter, List<HttpServiceArgumentResolver> argumentResolvers,
|
||||
@Nullable StringValueResolver embeddedValueResolver, ReactiveAdapterRegistry reactiveAdapterRegistry,
|
||||
Duration blockTimeout) {
|
||||
|
||||
/**
|
||||
* Create an instance with the underlying HTTP client to use.
|
||||
* @param clientAdapter an adapter for the client
|
||||
*/
|
||||
public HttpServiceProxyFactory(HttpClientAdapter clientAdapter) {
|
||||
Assert.notNull(clientAdapter, "HttpClientAdapter is required");
|
||||
this.clientAdapter = clientAdapter;
|
||||
this.argumentResolvers = argumentResolvers;
|
||||
this.embeddedValueResolver = embeddedValueResolver;
|
||||
this.reactiveAdapterRegistry = reactiveAdapterRegistry;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register a custom argument resolver, invoked ahead of default resolvers.
|
||||
* @param resolver the resolver to add
|
||||
*/
|
||||
public void addCustomArgumentResolver(HttpServiceArgumentResolver resolver) {
|
||||
if (this.customArgumentResolvers == null) {
|
||||
this.customArgumentResolvers = new ArrayList<>();
|
||||
}
|
||||
this.customArgumentResolvers.add(resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom argument resolvers to use, ahead of default resolvers.
|
||||
* @param resolvers the resolvers to use
|
||||
*/
|
||||
public void setCustomArgumentResolvers(List<HttpServiceArgumentResolver> resolvers) {
|
||||
this.customArgumentResolvers = new ArrayList<>(resolvers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ConversionService} to use where input values need to
|
||||
* be formatted as Strings.
|
||||
* <p>By default this is {@link DefaultFormattingConversionService}.
|
||||
*/
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the StringValueResolver to use for resolving placeholders and
|
||||
* expressions in {@link HttpExchange#url()}.
|
||||
* @param resolver the resolver to use
|
||||
*/
|
||||
@Override
|
||||
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
||||
this.embeddedValueResolver = resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()}.
|
||||
*/
|
||||
public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) {
|
||||
this.reactiveAdapterRegistry = registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a proxy that implements the given HTTP service interface to perform
|
||||
* HTTP requests and retrieve responses through an HTTP client.
|
||||
@@ -81,150 +180,25 @@ public final class HttpServiceProxyFactory {
|
||||
*/
|
||||
public <S> S createClient(Class<S> serviceType) {
|
||||
|
||||
List<HttpServiceMethod> methods =
|
||||
MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod)
|
||||
.stream()
|
||||
.map(method ->
|
||||
new HttpServiceMethod(
|
||||
method, serviceType, this.argumentResolvers, this.clientAdapter,
|
||||
this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout))
|
||||
List<HttpServiceMethod> httpServiceMethods =
|
||||
MethodIntrospector.selectMethods(serviceType, this::isExchangeMethod).stream()
|
||||
.map(method -> createHttpServiceMethod(serviceType, method))
|
||||
.toList();
|
||||
|
||||
return ProxyFactory.getProxy(serviceType, new HttpServiceMethodInterceptor(methods));
|
||||
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 a builder for an {@link HttpServiceProxyFactory}.
|
||||
* @param adapter an adapter for the underlying HTTP client
|
||||
* @return the builder
|
||||
*/
|
||||
public static Builder builder(HttpClientAdapter adapter) {
|
||||
return new Builder(adapter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for {@link HttpServiceProxyFactory}.
|
||||
*/
|
||||
public final static class Builder {
|
||||
|
||||
private final HttpClientAdapter clientAdapter;
|
||||
|
||||
private final List<HttpServiceArgumentResolver> customResolvers = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
private ConversionService conversionService;
|
||||
|
||||
@Nullable
|
||||
private StringValueResolver embeddedValueResolver;
|
||||
|
||||
private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
|
||||
|
||||
private Duration blockTimeout = Duration.ofSeconds(5);
|
||||
|
||||
private Builder(HttpClientAdapter clientAdapter) {
|
||||
Assert.notNull(clientAdapter, "HttpClientAdapter is required");
|
||||
this.clientAdapter = clientAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom argument resolver. This will be inserted ahead of
|
||||
* default resolvers.
|
||||
* @return the same builder instance
|
||||
*/
|
||||
public Builder addCustomResolver(HttpServiceArgumentResolver resolver) {
|
||||
this.customResolvers.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 the same builder instance
|
||||
*/
|
||||
public Builder setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
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.
|
||||
* <p>By default this is {@link ReactiveAdapterRegistry#getSharedInstance()}.
|
||||
* @return the same builder instance
|
||||
*/
|
||||
public Builder setReactiveAdapterRegistry(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 the same builder instance
|
||||
*/
|
||||
public Builder setBlockTimeout(Duration blockTimeout) {
|
||||
this.blockTimeout = blockTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the {@link HttpServiceProxyFactory} instance.
|
||||
*/
|
||||
public HttpServiceProxyFactory build() {
|
||||
|
||||
ConversionService conversionService = initConversionService();
|
||||
List<HttpServiceArgumentResolver> resolvers = initArgumentResolvers(conversionService);
|
||||
|
||||
return new HttpServiceProxyFactory(
|
||||
this.clientAdapter, resolvers, this.embeddedValueResolver, this.reactiveAdapterRegistry,
|
||||
this.blockTimeout);
|
||||
}
|
||||
|
||||
private ConversionService initConversionService() {
|
||||
return (this.conversionService != null ?
|
||||
this.conversionService : new DefaultFormattingConversionService());
|
||||
}
|
||||
|
||||
private List<HttpServiceArgumentResolver> initArgumentResolvers(ConversionService conversionService) {
|
||||
|
||||
List<HttpServiceArgumentResolver> resolvers = new ArrayList<>(this.customResolvers);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
return new HttpServiceMethod(
|
||||
method, serviceType, this.argumentResolvers, this.clientAdapter,
|
||||
this.embeddedValueResolver, this.reactiveAdapterRegistry, this.blockTimeout);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user