Spring-cloud Feign 的深入理解
feign的調(diào)用流程
讀取注解信息:EnableFeignClients-->FeignClientsRegistrar-->FeignClientFactoryBean
feigh流程:ReflectiveFeign-->Contract-->SynchronousMethodHandler
相關(guān)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); //構(gòu)建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構(gòu)造參數(shù) //ParseHandlersByName作用是通過傳入的target返回代理接口下的方法的各種信息(MethodHandler) //Contract:解析接口的方法注解規(guī)則,生成MethodMetadata //Options:Request超時配置 //Encoder:請求編碼器 //Decoder:返回解碼器 //ErrorDecoder:錯誤解碼器 //SynchronousMethodHandler.Factory是構(gòu)建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)用目標對象的對應(yīng)方法 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)絡(luò)訪問 FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); //executeWithLoadBalancer會根據(jù)ribbon的負載均衡算法構(gòu)建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)容,希望對大家的學(xué)習有所幫助,也希望大家多多支持腳本之家。
- spring cloud feign不支持@RequestBody+ RequestMethod.GET報錯的解決方法
- SpringCloud版本問題報錯及解決方法
- spring cloud consul注冊的服務(wù)報錯critical的解決
- Spring cloud Feign 深度學(xué)習與應(yīng)用詳解
- 詳解springcloud 基于feign的服務(wù)接口的統(tǒng)一hystrix降級處理
- Spring Cloud中FeignClient實現(xiàn)文件上傳功能
- SpringCloud使用Feign實現(xiàn)服務(wù)調(diào)用
- Spring Cloud Feign報錯問題解決
相關(guān)文章
java中jdbcTemplate的queryForList(坑)
本文主要介紹了java中jdbcTemplate的queryForList,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09Java CountDownLatch應(yīng)用場景代碼實例
這篇文章主要介紹了Java CountDownLatch應(yīng)用場景代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友可以參考下2020-09-09java注釋轉(zhuǎn)json插件開發(fā)實戰(zhàn)詳解
這篇文章主要為大家介紹了java注釋轉(zhuǎn)json插件開發(fā)實戰(zhàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06一篇文章總結(jié)Java虛擬機內(nèi)存區(qū)域模型
這篇文章主要介紹了一篇文章總結(jié)Java虛擬機內(nèi)存區(qū)域模型,本篇文章主要來總結(jié)一下Java虛擬機內(nèi)存的各個區(qū)域,以及這些區(qū)域的作用、服務(wù)對象以及其中可能產(chǎn)生的問題,作為大家的面試寶典。,需要的朋友可以參考下2019-06-06Javaweb mybatis接口開發(fā)實現(xiàn)過程詳解
這篇文章主要介紹了Javaweb mybatis接口開發(fā)實現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友可以參考下2020-07-07