/* * Copyright © 2012 The Feign Authors (feign@commonhaus.dev) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package feign; import feign.InvocationHandlerFactory.MethodHandler; import feign.Logger.Level; import feign.Request.Options; import feign.Target.HardCodedTarget; import feign.codec.Decoder; import feign.codec.Encoder; import feign.codec.ErrorDecoder; import feign.interceptor.MethodInterceptor; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Supplier; /** * Enhances {@link Feign} to provide support for asynchronous clients. Context (for example for * session cookies or tokens) is explicit, as calls for the same session may be done across several * threads.
*
* {@link Retryer} is not supported in this model, as that is a blocking API. {@link * ExceptionPropagationPolicy} is made redundant as {@link RetryableException} is never thrown.
* Alternative approaches to retrying can be handled through {@link AsyncClient clients}.
*
* Target interface methods must return {@link CompletableFuture} with a non-wildcard type. As the * completion is done by the {@link AsyncClient}, it is important that any subsequent processing on * the thread be short - generally, this should involve notifying some other thread of the work to * be done (for example, creating and submitting a task to an {@link ExecutorService}). */ @Experimental public final class AsyncFeign { public static AsyncBuilder builder() { return new AsyncBuilder<>(); } /** * @deprecated use {@link #builder()} instead. */ @Deprecated() public static AsyncBuilder asyncBuilder() { return builder(); } /** * Creates the default {@link ExecutorService} used when neither a custom {@link AsyncClient} nor * a custom executor is provided. The executor is created per built client (instance scoped) * rather than being held in a static singleton, so that it - together with its daemon threads - * becomes eligible for garbage collection once the owning client is discarded. This avoids the * {@code ClassLoader} leak that a static singleton causes on application redeployments inside * servlet containers (see gh-3178). */ private static ExecutorService defaultExecutorService() { return Executors.newCachedThreadPool( r -> { final Thread result = new Thread(r); result.setDaemon(true); return result; }); } public static class AsyncBuilder extends BaseBuilder, AsyncFeign> { private AsyncContextSupplier defaultContextSupplier = () -> null; private AsyncClient client; private ExecutorService executorService; private MethodInfoResolver methodInfoResolver = MethodInfo::new; @Deprecated public AsyncBuilder defaultContextSupplier(Supplier supplier) { this.defaultContextSupplier = supplier::get; return this; } public AsyncBuilder client(AsyncClient client) { this.client = client; return this; } /** * Sets the {@link ExecutorService} used by the default {@link AsyncClient} to run the * (blocking) underlying client calls. When provided, the caller owns the executor's lifecycle * and is responsible for shutting it down. This is the recommended way to avoid the {@code * ClassLoader} leak described in gh-3178: supply a managed, shut-downable executor instead of * relying on the built-in default. Ignored when a custom {@link #client(AsyncClient)} is * supplied. */ public AsyncBuilder executorService(ExecutorService executorService) { this.executorService = executorService; return this; } private AsyncClient resolveClient() { if (client != null) { return client; } final ExecutorService executor = executorService != null ? executorService : defaultExecutorService(); return new DefaultAsyncClient<>(new DefaultClient(null, null), executor); } public AsyncBuilder methodInfoResolver(MethodInfoResolver methodInfoResolver) { this.methodInfoResolver = methodInfoResolver; return this; } @Override public AsyncBuilder mapAndDecode(ResponseMapper mapper, Decoder decoder) { return super.mapAndDecode(mapper, decoder); } @Override public AsyncBuilder decoder(Decoder decoder) { return super.decoder(decoder); } @Override @Deprecated public AsyncBuilder decode404() { return super.decode404(); } @Override public AsyncBuilder dismiss404() { return super.dismiss404(); } @Override public AsyncBuilder errorDecoder(ErrorDecoder errorDecoder) { return super.errorDecoder(errorDecoder); } @Override public AsyncBuilder doNotCloseAfterDecode() { return super.doNotCloseAfterDecode(); } @Override public AsyncBuilder decodeVoid() { return super.decodeVoid(); } public AsyncBuilder defaultContextSupplier(AsyncContextSupplier supplier) { this.defaultContextSupplier = supplier; return this; } public T target(Class apiType, String url) { return target(new HardCodedTarget<>(apiType, url)); } public T target(Class apiType, String url, C context) { return target(new HardCodedTarget<>(apiType, url), context); } public T target(Target target) { return build().newInstance(target); } public T target(Target target, C context) { return build().newInstance(target, context); } @Override public AsyncBuilder logLevel(Level logLevel) { return super.logLevel(logLevel); } @Override public AsyncBuilder contract(Contract contract) { return super.contract(contract); } @Override public AsyncBuilder logger(Logger logger) { return super.logger(logger); } @Override public AsyncBuilder encoder(Encoder encoder) { return super.encoder(encoder); } @Override public AsyncBuilder queryMapEncoder(QueryMapEncoder queryMapEncoder) { return super.queryMapEncoder(queryMapEncoder); } @Override public AsyncBuilder options(Options options) { return super.options(options); } @Override public AsyncBuilder requestInterceptor(RequestInterceptor requestInterceptor) { return super.requestInterceptor(requestInterceptor); } @Override public AsyncBuilder requestInterceptors(Iterable requestInterceptors) { return super.requestInterceptors(requestInterceptors); } @Override public AsyncBuilder methodInterceptor(MethodInterceptor methodInterceptor) { return super.methodInterceptor(methodInterceptor); } @Override public AsyncBuilder methodInterceptors(Iterable methodInterceptors) { return super.methodInterceptors(methodInterceptors); } @Override public AsyncBuilder invocationHandlerFactory( InvocationHandlerFactory invocationHandlerFactory) { return super.invocationHandlerFactory(invocationHandlerFactory); } @Override public AsyncFeign internalBuild() { final AsyncClient client = resolveClient(); AsyncResponseHandler responseHandler = (AsyncResponseHandler) Capability.enrich( new AsyncResponseHandler( logLevel, logger, decoder, errorDecoder, dismiss404, closeAfterDecode, decodeVoid, responseInterceptorChain()), AsyncResponseHandler.class, capabilities); final MethodHandler.Factory methodHandlerFactory = new AsynchronousMethodHandler.Factory<>( client, retryer, requestInterceptors, methodInterceptors, responseHandler, logger, logLevel, propagationPolicy, methodInfoResolver, new RequestTemplateFactoryResolver(encoder, queryMapEncoder), options); final ReflectiveFeign feign = new ReflectiveFeign<>( contract, methodHandlerFactory, invocationHandlerFactory, defaultContextSupplier); return new AsyncFeign<>(feign); } } private final ReflectiveFeign feign; private AsyncFeign(ReflectiveFeign feign) { this.feign = feign; } public T newInstance(Target target) { return feign.newInstance(target); } public T newInstance(Target target, C context) { return feign.newInstance(target, context); } }