How Spring Cloud OpenFeign Implements Load Balancing and Client Bean Creation
This article explains how Spring Cloud OpenFeign discovers @FeignClient interfaces, registers them as beans, configures FeignClientFactoryBean, selects the appropriate HTTP client implementation, and integrates load‑balancing through FeignBlockingLoadBalancerClient and related classes.
Environment
Spring Cloud 2021.0.7 with Spring Boot 2.7.12.
Dependency Configuration
<code><dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement></code>Enable Feign Annotations
<code>@SpringBootApplication
// Enable Feign and configure default settings, scan packages, and specify @FeignClient classes
@EnableFeignClients
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}</code>FeignClient Bean Generation Principle
During container startup, Spring scans for interfaces annotated with @FeignClient and registers each as a bean using a FeignClientFactoryBean instance.
The class FeignClientsRegistrar (imported by @EnableFeignClients ) performs the actual scanning.
FeignClientFactoryBean
<code>public class FeignClientFactoryBean implements FactoryBean<Object> {
public Object getObject() {
return getTarget();
}
<T> T getTarget() {
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class) : applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
} else {
url = name;
}
url += cleanPath();
// load‑balance handling
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
}
// ...
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
// Core load‑balancing client implementation
Client client = getOptional(context, Client.class);
// ...
}
}</code>Client Implementations
Apache HttpClient
OkHttp
Default (JDK)
The actual client used depends on which dependency (httpclient or okhttp) is present on the classpath.
<code><!-- httpclient -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>${version}</version>
</dependency>
<!-- okhttp -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>${version}</version>
</dependency>
</code>Load‑Balancing Configuration
<code>@Import({
HttpClientFeignLoadBalancerConfiguration.class,
OkHttpFeignLoadBalancerConfiguration.class,
HttpClient5FeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {}
</code>If multiple implementations are present, the last imported configuration (e.g., DefaultFeignLoadBalancerConfiguration ) takes precedence.
<code>public class DefaultFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory) {
// Construct the final client that performs remote calls
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient, loadBalancerClientFactory);
}
}
</code>When neither httpclient nor okhttp is imported, the client implementation defaults to FeignBlockingLoadBalancerClient .
FeignBlockingLoadBalancerClient (Load‑Balancing Implementation)
<code>public class FeignBlockingLoadBalancerClient implements Client {
private final Client delegate;
private final LoadBalancerClient loadBalancerClient;
private final LoadBalancerClientFactory loadBalancerClientFactory;
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancerClientFactory = loadBalancerClientFactory;
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
String hint = getHint(serviceId);
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(buildRequestData(request), hint));
// Choose a service instance via load balancer
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
// Reconstruct the request URL with the chosen instance
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
Request newRequest = buildRequest(request, reconstructedUrl);
LoadBalancerProperties props = loadBalancerClientFactory.getProperties(serviceId);
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, null, null, props.isUseRawStatusCodeInResponseData());
}
protected Request buildRequest(Request request, String reconstructedUrl) {
return Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(), request.charset(), request.requestTemplate());
}
}
</code>LoadBalancerClient Implementation
<code>public class BlockingLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnBean(LoadBalancerClientFactory.class)
@ConditionalOnMissingBean
public LoadBalancerClient blockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory) {
return new BlockingLoadBalancerClient(loadBalancerClientFactory);
}
}
</code> <code>public class BlockingLoadBalancerClient implements LoadBalancerClient {
private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;
public BlockingLoadBalancerClient(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory) {
this.loadBalancerClientFactory = loadBalancerClientFactory;
}
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
}
Response<ServiceInstance> response = Mono.from(loadBalancer.choose(request)).block();
return response != null ? response.getServer() : null;
}
public URI reconstructURI(ServiceInstance serviceInstance, URI original) {
return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
}
}
</code>Round‑Robin Load‑Balancing Algorithm
<code>public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> response = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && response.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(response.getServer());
}
return response;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.size() == 1) {
return new DefaultResponse(instances.get(0));
}
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}
</code>Remote Call Execution Flow
The FeignBlockingLoadBalancerClient#execute method ultimately delegates to LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing , which invokes the underlying Client (usually Client.Default ) to perform the HTTP request.
<code>final class LoadBalancerUtils {
static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,
Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,
Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean useRawStatusCodes) throws IOException {
// Lifecycle pre‑processing can be added here
Response response = feignClient.execute(feignRequest, options);
// Lifecycle post‑processing can be added here
return response;
}
}
</code> <code>public interface Client {
Response execute(Request request, Options options) throws IOException;
}
// Default implementation uses JDK HttpURLConnection
public class Client.Default implements Client {
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
}
</code>The article concludes that this chain of components— FeignClientFactoryBean , FeignBlockingLoadBalancerClient , and the various LoadBalancerClient implementations—constitutes the load‑balancing mechanism for a Feign client request in Spring Cloud.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.