本文主要分析被 @FeignClient 注解的接口類請求過程中負載均衡邏輯,流程分析使用的依賴版本信息如下:
<spring-boot.version>3.2.1</spring-boot.version><spring-cloud.version>2023.0.0</spring-cloud.version><com.alibaba.cloud.version>2023.0.0.0-RC1</com.alibaba.cloud.version><!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><!-- spring-cloud --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><!--注冊中心--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>${com.alibaba.cloud.version}</version></dependency>
背景
平常我們代碼里用@FeignClien注解一個接口類,實現一個遠程接口(如下)
@FeignClient(name = ServiceNameConstants.XXX, fallbackFactory = XXXFactory.class)
public interface RemoteXXXService {@GetMapping("/XXX/getById")Result<XXX> getById(@RequestParam("Id") String Id);
}
被@FeignClien注解的類,在運行的時候容器會生成一個動態類,從調用堆棧能看出;
最終調用到 FeignBlockingLoadBalancerClient 的 execute 方法,下面我們詳細分析這個方法調用的由來;
分析
直接答案:openfeign負載均衡使用需要注解和自動裝配配合才能生效,即@EnableFeignClients與 spring-cloud-openfeign-core下的org.springframework.boot.autoconfigure.AutoConfiguration.imports 配合;
- @EnableFeignClients主要是決定是否要使用負載均衡(引用了負載均衡依賴,但是可以不使用該特性)
- AutoConfiguration 主要是初始化一些負載均衡所需的基礎設施
負載均衡代理類生成流程
使用open-feign我們一般會在啟動類上添加注解 @EnableFeignClients,這個注解內容如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients { ... }
可以看出該注解類是一個 @Import 的復合注解,也就是說在啟動過程中該注解具備@Import的功能,會引入FeignClientsRegistrar類;
我們看看FeignClientsRegistrar這個類是ImportBeanDefinitionRegistrar的子類,應用啟動后會執行registerBeanDefinitions方法;
注冊FeignClient BeanDefinition
我們直接來到核心方法
org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {String className = annotationMetadata.getClassName();if (String.valueOf(false).equals(environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) {eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);}else {lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);}}
這個方法主要根據配置注冊懶加載bean或者是立即實例化的類,配置key是:spring.cloud.openfeign.lazy-attributes-resolution ,如果是true的話應該會加長應用啟動時間,只有配置了true才會懶初始化;
BeanDefinition的目標類是org.springframework.cloud.openfeign.FeignClientFactoryBean,那么后續創建FeignClient 的代理類就由該類承擔;
1、核心入口方法是:org.springframework.cloud.openfeign.FeignClientFactoryBean#getTarget,
2、最終調用的方法是:feign.ReflectiveFeign#newInstance(feign.Target, C)
public <T> T newInstance(Target<T> target, C requestContext) {ReflectiveFeign.TargetSpecificationVerifier.verify(target);Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = this.targetToHandlersByName.apply(target, requestContext);InvocationHandler handler = this.factory.create(target, methodToHandler);T proxy = (T)Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);for(InvocationHandlerFactory.MethodHandler methodHandler : methodToHandler.values()) {if (methodHandler instanceof DefaultMethodHandler) {((DefaultMethodHandler)methodHandler).bindTo(proxy);}}return proxy;}
到這里 Proxy 代理類就創建好了;
open-feign自動裝配
我們打開spring-cloud-openfeign-core的spring配置
內容如下:
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration
org.springframework.cloud.openfeign.FeignAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
- FeignAutoConfiguration主要功能:
提供生成動態代理所需的默認組件(如 Encoder、Decoder、Contract),確保 @EnableFeignClients 掃描到的接口能正確實例化。
- FeignAutoConfiguration主要功能:
FeignAutoConfiguration 自動檢測項目中是否包含負載均衡依賴(如 spring-cloud-starter-loadbalancer),若存在則配置 LoadBalancerFeignClient
@ConditionalOnClass(Feign.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@AutoConfigureBefore(FeignAutoConfiguration.class)
@AutoConfigureAfter({ BlockingLoadBalancerClientAutoConfiguration.class, LoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties(FeignHttpClientProperties.class)
@Configuration(proxyBeanMethods = false)
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ OkHttpFeignLoadBalancerConfiguration.class, HttpClient5FeignLoadBalancerConfiguration.class,Http2ClientFeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {@Bean@ConditionalOnBean(LoadBalancerClientFactory.class)@ConditionalOnMissingBean(XForwardedHeadersTransformer.class)public XForwardedHeadersTransformer xForwarderHeadersFeignTransformer(LoadBalancerClientFactory factory) {return new XForwardedHeadersTransformer(factory);}
}
這個類通過@Import引入了DefaultFeignLoadBalancerConfiguration,其注入FeignBlockingLoadBalancerClient
@Bean@ConditionalOnMissingBean@Conditional(OnRetryNotEnabledCondition.class)public Client feignClient(LoadBalancerClient loadBalancerClient,LoadBalancerClientFactory loadBalancerClientFactory,List<LoadBalancerFeignRequestTransformer> transformers) {return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,loadBalancerClientFactory, transformers);}
FeignBlockingLoadBalancerClient
這個類是歸屬spring-cloud-starter-openfeign依賴
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
我們看看execute方法
public Response execute(Request request, Request.Options options) throws IOException {final URI originalUri = URI.create(request.url());String serviceId = originalUri.getHost();Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);String hint = getHint(serviceId);DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(buildRequestData(request), hint));Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),RequestDataContext.class, ResponseData.class, ServiceInstance.class);supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
// @AServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(instance);if (instance == null) {String message = "Load balancer does not contain an instance for the service " + serviceId;if (LOG.isWarnEnabled()) {LOG.warn(message);}supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(CompletionContext.Status.DISCARD, lbRequest, lbResponse)));return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value()).body(message, StandardCharsets.UTF_8).build();}// @BString reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();Request newRequest = buildRequest(request, reconstructedUrl, instance);return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,supportedLifecycleProcessors);}
@A:通過 loadBalancer 獲得服務實例
@B:獲得真是的請求地址,即nacos上注冊的地址
BlockingLoadBalancerClient
上面FeignBlockingLoadBalancerClient @A 和 @B的地方都調用了loadBalancerClient的方法,loadBalancerClient是一個BlockingLoadBalancerClient類對象。
pom里依賴如下,需要單獨引入:
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
choose方法
public <T> ServiceInstance choose(String serviceId, Request<T> request) {// @CReactiveLoadBalancer<ServiceInstance> loadBalancer = this.loadBalancerClientFactory.getInstance(serviceId);if (loadBalancer == null) {return null;} else {
// @DResponse<ServiceInstance> loadBalancerResponse = (Response)Mono.from(loadBalancer.choose(request)).block();return loadBalancerResponse == null ? null : (ServiceInstance)loadBalancerResponse.getServer();}}
@C:這個地方很奇特,底層代碼是根據serviceId找到緩存的GenericApplicationContext對象,然后通過getBean的方式獲得對象,沒有想通這樣做的理由;
@D:如果從緩存中有找到ReactiveLoadBalancer,則將結果封裝成ServiceInstance對象。(這個過程代碼比較復雜)
reconstructURI 方法
public URI reconstructURI(ServiceInstance serviceInstance, URI original) {return LoadBalancerUriTools.reconstructURI(serviceInstance, original);}
最終調用到如下方法:
private static URI doReconstructURI(ServiceInstance serviceInstance, URI original) {String host = serviceInstance.getHost();String scheme = (String)Optional.ofNullable(serviceInstance.getScheme()).orElse(computeScheme(original, serviceInstance));int port = computePort(serviceInstance.getPort(), scheme);if (Objects.equals(host, original.getHost()) && port == original.getPort() && Objects.equals(scheme, original.getScheme())) {return original;} else {boolean encoded = containsEncodedParts(original);return UriComponentsBuilder.fromUri(original).scheme(scheme).host(host).port(port).build(encoded).toUri();}}
最終從ServiceInstance 解析出目標服務信息,返回URI對象;
over~~