<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
@FeignClinet 通過動態代理實現的底層http呼叫,既然是動態代理,必然存在建立代理類的過程。如Proxy.newProxyInstance或者 CGlib org.springframework.cloud.openfeign 的代理類註冊實現如下。
首先,org.springframework.cloud.openfeign.FeignClientsRegistrar 註冊FeignClientFactoryBean到Singleton快取中. 一個介面對應FeignClientFactoryBean。
spring 初始化容器過程中執行
org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject()
@Override public Object getObject() throws Exception { return getTarget(); } /** * @param <T> the target type of the Feign client * @return a {@link Feign} client created with the specified data and the context information */ <T> T getTarget() { FeignContext context = applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } url += cleanPath(); return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, 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) { // 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 (T) targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); }
其中 getObject() 實現了 FactoryBean 的 getObject(),
作用是在springContext初始化時建立Bean範例,如果isSingleton()返回true,則該範例會放到Spring容器的單範例快取池中。
然後是targeter.target() 如果啟用了Hystrix呼叫的就是
org.springframework.cloud.openfeign.HystrixTargeter.target()
org.springframework.cloud.openfeign.HystrixTargeter
/** * @param factory bean工廠 * @param feign feign物件的構造類 * @param context feign介面上下文, * @param target 儲存了feign介面的name,url和FeignClient的Class物件 * **/ @Override public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) { if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { return feign.target(target); } feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign; SetterFactory setterFactory = getOptional(factory.getName(), context, SetterFactory.class); if (setterFactory != null) { builder.setterFactory(setterFactory); } Class<?> fallback = factory.getFallback(); if (fallback != void.class) { return targetWithFallback(factory.getName(), context, target, builder, fallback); } Class<?> fallbackFactory = factory.getFallbackFactory(); if (fallbackFactory != void.class) { return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory); } return feign.target(target); }
再看下去 feign.target(target)
feign.Feign.Builder
public <T> T target(Target<T> target) { return build().newInstance(target); } public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); }
build() 返回一個ReflectiveFeign物件。
往下看,ReflectiveFeign的newInstance方法。
feign.ReflectiveFeign
@Override public <T> T newInstance(Target<T> target) { //關鍵方法: 解析target物件,返回key 為 feign介面的url ,value 為請求執行類:SynchronousMethodHandler Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); 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))); } } //建立代理類 handler ,返回物件 feign.ReflectiveFeign.FeignInvocationHandler InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }
至此,代理類註冊完成。
當呼叫feign介面時,其實執行的是 feign.ReflectiveFeign.FeignInvocationHandler的invoke 方法
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("equals".equals(method.getName())) { try { Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) { return false; } } else if ("hashCode".equals(method.getName())) { return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); } //dispatch.get(method)返回的是 SynchronousMethodHandler 物件 return dispatch.get(method).invoke(args); }
呼叫的 SynchronousMethodHandler invoke 方法。
feign.SynchronousMethodHandler
@Override public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); Retryer retryer = this.retryer.clone(); while (true) { try { return executeAndDecode(template); } catch (RetryableException e) { try { retryer.continueOrPropagate(e); } catch (RetryableException th) { Throwable cause = th.getCause(); if (propagationPolicy == UNWRAP && cause != null) { throw cause; } else { throw th; } } if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } }
executeAndDecode 方法執行RPC呼叫的邏輯。
小結一下:FeignClientsRegistrar 解析@FeignClient註解,註冊對應的FeignClientFactoryBean–》通過FeignClientFactoryBean的getObject()方法返回代理物件 feign.ReflectiveFeign.FeignInvocationHandler
首先我要說的是springcloud沒有rpc,這就涉及rpc和微服務的區別。springcloud的模組通訊工具feign跟httpclient和okhttp是一樣的東西,都是對http請求封裝的工具,其實feign可以選擇httpclient或者okhttp作為底層實現(修改設定即可)。
①封裝http請求,使開發人員對傳送請求的過程無感知,給人一種偽rpc感覺(這也許是feign這個名字的由來吧,偽裝~)。
②feign整合ribbon和hystrix,結合eureka起到負載均衡和熔斷器、降級作用。
我們從@EnableFeignClients這個註解開始追蹤
我們發現有個@Import註解,參照FeignClientRegistrar類,跟進去看看
2個方法:①redisterDefalterConfiguration是載入設定,②registerFeignClients掃描你填寫的basepackage下的所有@FeignClient註解的介面。第一個方法沒啥好說的,我們主要看看第二個方法。
掃描完之後,把所有包含@FeignClient註解的介面都註冊到spring的beanfactory去,讓開發人員可以@Autowired來呼叫。這一部分程式碼我就不貼了,我們只是追求feign的原理流程,太涉及spring原始碼部分,我不做解釋。
=========== 以上是feign註冊流程,下面介紹拼裝request請求部分 ===========
首先,這裡看ReflectiveFeign類,這個類用的是jdk的動態代理
用到代理模式肯定是在傳送feign請求之前做一些操作,繼續看看請求之前做了哪些操作。
代理攔截每一個FeignClient請求,進入SynchronousMethodHandler的invoke方法,該方法呼叫executeAndDecode方法,這個方法看名字就知道是建立請求的方法,進去看看。
在該方法傳送請求並且解碼,解碼分為decoder和errordecoder,這兩個都是可以重寫。這裡你可能會問解碼器,那編碼器呢,feign預設用springEncoder,同樣是可以替換成Gson等。
=========== 以上是feign的呼叫流程,以下是feign使用過程的坑 ===========
①feign在D版本後預設關閉hystrix,要想傳遞請求頭,如果不用hystrix的話在feign攔截器裡塞一遍就好;如果要用hystrix,那麼改用號誌。
②在C版本後預設關閉hystrix,要使用要手動開啟
③不要妄想改變feign的邏輯,因為代理模式被寫成final,無法修改
④無法在解碼器裡拋自定義異常,因為feign最終會統一攔截,丟擲一個feignexception。你想把統一攔截也改了,那麼你可以看看第③坑。
⑤feign的重試機制,預設是1,也就是說超時時間會變成2倍。這個可以通過設定修改。
⑥feign整合的負載均衡器ribbon,feign有個快取,ribbon也有個快取,會造成上線延遲,可以修改設定實現。
⑦feign對格式化時間處理有問題
⑧如果你是使用生產者提供api,並且實現該介面,@requestparam可以不用在實現類寫,但是@requestbody不寫無法對映
以上的坑都是我在實際工作中一個一個爬過來的,僅為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45