Spring-cloud Feign 的深入理解
feign的調(diào)用流程
讀取注解信息:EnableFeignClients-->FeignClientsRegistrar-->FeignClientFactoryBean
feigh流程:ReflectiveFeign-->Contract-->SynchronousMethodHandler
相關configuration:FeignClientsConfiguration,F(xiàn)eignAutoConfiguration,DefaultFeignLoadBalancedConfiguration,F(xiàn)eignRibbonClientAutoConfiguration(Ribbon)
在FeignClientsRegistrar中:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注冊feign配置信息
registerDefaultConfiguration(metadata, registry);
//注冊feign client
registerFeignClients(metadata, registry);
}
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//準備注入FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
...
}
查看FeignClientFactoryBean:
@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
//構建Feign.Builder
Feign.Builder builder = feign(context);
//如果注解沒有指定URL
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
//如果指定了URL
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// 因為指定了URL且classpath下有Ribbon,獲取client的delegate(unwrap)
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//獲取Feign Client實例
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
//DefaultTargeter或者HystrixTargeter
Targeter targeter = get(context, Targeter.class);
//調(diào)用builder的target,其中就調(diào)用了Feign的newInstance
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
在FeignClientsConfiguration配置了Feign.Builder,prototype類型:
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
Feign的Builder.build返回了一個ReflectiveFeign:
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
//ReflectiveFeign構造參數(shù)
//ParseHandlersByName作用是通過傳入的target返回代理接口下的方法的各種信息(MethodHandler)
//Contract:解析接口的方法注解規(guī)則,生成MethodMetadata
//Options:Request超時配置
//Encoder:請求編碼器
//Decoder:返回解碼器
//ErrorDecoder:錯誤解碼器
//SynchronousMethodHandler.Factory是構建SynchronousMethodHandler的工廠
//Client:代表真正執(zhí)行HTTP的組件
//Retryer:該組決定了在http請求失敗時是否需要重試
//RequestInterceptor:請求前的攔截器
//Logger:記錄日志組件,包含各個階段記錄日志的方法和留給用戶自己實現(xiàn)的log方法
//Logger.Level:日志級別
//decode404:處理404的策略,返回空還是報錯
//synchronousMethodHandlerFactory通過所有的信息去包裝一個synchronousMethodHandler,在調(diào)用invoke方法的時候執(zhí)行HTTP
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
在調(diào)用Feign.Builder的target的時候,調(diào)用了ReflectiveFeign.newInstance:
/**
* creates an api binding to the {@code target}. As this invokes reflection, care should be taken
* to cache the result.
*/
@SuppressWarnings("unchecked")
@Override
//接收Target參數(shù)(包含feign代理接口的類型class,名稱,http URL)
public <T> T newInstance(Target<T> target) {
//首先通過**ParseHandlersByName**解析出接口中包含的方法,包裝RequestTemplate,組裝成<name, MethodHandler>
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
//接口default方法List
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if(Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//InvocationHandlerFactory.Default()返回了一個ReflectiveFeign.FeignInvocationHandler對象,通過傳入的methodHandler map 調(diào)用目標對象的對應方法
InvocationHandler handler = factory.create(target, methodToHandler);
//生成JDK代理對象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
//綁定接口的默認方法到代理對象
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
生成Feign代理對象的基本流程圖:

當調(diào)用接口方法時,實際上就是調(diào)用代理對象invoke方法:
@Override
public Object invoke(Object[] argv) throws Throwable {
//工廠創(chuàng)建請求模版
RequestTemplate template = buildTemplateFromArgs.create(argv);
//每次克隆一個新的Retryer
Retryer retryer = this.retryer.clone();
while (true) {
try {
//這里調(diào)用實際的Feign client execute
return executeAndDecode(template);
} catch (RetryableException e) {
//失敗重試
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
在DefaultFeignLoadBalancedConfiguration里實例化了LoadBalancerFeignClient
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//delegate這里是Client.Default實例,底層調(diào)用的是java.net原生網(wǎng)絡訪問
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
//executeWithLoadBalancer會根據(jù)ribbon的負載均衡算法構建url,這里不展開
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Java中LinkedList和ArrayList的效率分析
本文主要介紹了Java中LinkedList和ArrayList的效率分析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-02-02
SpringBoot中分頁插件PageHelper的使用詳解
分頁查詢是為了高效展示大量數(shù)據(jù),通過分頁將數(shù)據(jù)劃分為多個部分逐頁展示,原生方法需手動計算數(shù)據(jù)起始行,而使用PageHelper插件則簡化這一過程,本文給大家介紹SpringBoot中分頁插件PageHelper的使用,感興趣的朋友一起看看吧2024-09-09
java中MultipartFile互轉(zhuǎn)File的方法
本文主要介紹了java中MultipartFile互轉(zhuǎn)File的方法,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10

