關(guān)于feign接口動態(tài)代理源碼解析
feign接口動態(tài)代理源碼解析
@FeignClinet 代理類注冊
@FeignClinet 通過動態(tài)代理實現(xiàn)的底層http調(diào)用,既然是動態(tài)代理,必然存在創(chuàng)建代理類的過程。如Proxy.newProxyInstance或者 CGlib org.springframework.cloud.openfeign 的代理類注冊實現(xiàn)如下。
首先,org.springframework.cloud.openfeign.FeignClientsRegistrar 注冊FeignClientFactoryBean到Singleton緩存中. 一個接口對應(yīng)FeignClientFactoryBean。
spring 初始化容器過程中執(zhí)行
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() 實現(xiàn)了 FactoryBean 的 getObject(),
作用是在springContext初始化時創(chuàng)建Bean實例,如果isSingleton()返回true,則該實例會放到Spring容器的單實例緩存池中。
然后是targeter.target() 如果啟用了Hystrix調(diào)用的就是
org.springframework.cloud.openfeign.HystrixTargeter.target()
org.springframework.cloud.openfeign.HystrixTargeter
/** * @param factory bean工廠 * @param feign ?feign對象的構(gòu)造類 * @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) { ? ? //關(guān)鍵方法: 解析target對象,返回key 為 feign接口的url ,value 為請求執(zhí)行類: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))); ? ? ? } ? ? } ? ? //創(chuàng)建代理類 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; ? }
至此,代理類注冊完成。
當(dāng)調(diào)用feign接口時,其實執(zhí)行的是 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); ? ? }
調(diào)用的 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 方法執(zhí)行RPC調(diào)用的邏輯。
小結(jié)一下:FeignClientsRegistrar 解析@FeignClient注解,注冊對應(yīng)的FeignClientFactoryBean–》通過FeignClientFactoryBean的getObject()方法返回代理對象 feign.ReflectiveFeign.FeignInvocationHandler
feign源碼解析
首先我要說的是springcloud沒有rpc,這就涉及rpc和微服務(wù)的區(qū)別。springcloud的模塊通信工具feign跟httpclient和okhttp是一樣的東西,都是對http請求封裝的工具,其實feign可以選擇httpclient或者okhttp作為底層實現(xiàn)(修改配置即可)。
Feign的作用
①封裝http請求,使開發(fā)人員對發(fā)送請求的過程無感知,給人一種偽rpc感覺(這也許是feign這個名字的由來吧,偽裝~)。
②feign整合ribbon和hystrix,結(jié)合eureka起到負載均衡和熔斷器、降級作用。
源碼及流程介紹
我們從@EnableFeignClients這個注解開始追蹤
我們發(fā)現(xiàn)有個@Import注解,引用FeignClientRegistrar類,跟進去看看
2個方法:①redisterDefalterConfiguration是加載配置,②registerFeignClients掃描你填寫的basepackage下的所有@FeignClient注解的接口。第一個方法沒啥好說的,我們主要看看第二個方法。
掃描完之后,把所有包含@FeignClient注解的接口都注冊到spring的beanfactory去,讓開發(fā)人員可以@Autowired來調(diào)用。這一部分代碼我就不貼了,我們只是追求feign的原理流程,太涉及spring源碼部分,我不做解釋。
=========== 以上是feign注冊流程,下面介紹拼裝request請求部分 ===========
首先,這里看ReflectiveFeign類,這個類用的是jdk的動態(tài)代理
用到代理模式肯定是在發(fā)送feign請求之前做一些操作,繼續(xù)看看請求之前做了哪些操作。
代理攔截每一個FeignClient請求,進入SynchronousMethodHandler的invoke方法,該方法調(diào)用executeAndDecode方法,這個方法看名字就知道是創(chuàng)建請求的方法,進去看看。
在該方法發(fā)送請求并且解碼,解碼分為decoder和errordecoder,這兩個都是可以重寫。這里你可能會問解碼器,那編碼器呢,feign默認(rèn)用springEncoder,同樣是可以替換成Gson等。
=========== 以上是feign的調(diào)用流程,以下是feign使用過程的坑 ===========
①feign在D版本后默認(rèn)關(guān)閉hystrix,要想傳遞請求頭,如果不用hystrix的話在feign攔截器里塞一遍就好;如果要用hystrix,那么改用信號量。
②在C版本后默認(rèn)關(guān)閉hystrix,要使用要手動開啟
③不要妄想改變feign的邏輯,因為代理模式被寫成final,無法修改
④無法在解碼器里拋自定義異常,因為feign最終會統(tǒng)一攔截,拋出一個feignexception。你想把統(tǒng)一攔截也改了,那么你可以看看第③坑。
⑤feign的重試機制,默認(rèn)是1,也就是說超時時間會變成2倍。這個可以通過配置修改。
⑥feign集成的負載均衡器ribbon,feign有個緩存,ribbon也有個緩存,會造成上線延遲,可以修改配置實現(xiàn)。
⑦feign對格式化時間處理有問題
⑧如果你是使用生產(chǎn)者提供api,并且實現(xiàn)該接口,@requestparam可以不用在實現(xiàn)類寫,但是@requestbody不寫無法映射
以上的坑都是我在實際工作中一個一個爬過來的,僅為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot + mybatis配置多數(shù)據(jù)源示例
本篇文章主要介紹了springboot + mybatis配置多數(shù)據(jù)源示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03springBoo3.0集成knife4j4.1.0的詳細教程(swagger3)
這篇文章主要介紹了springBoo3.0集成knife4j4.1.0的詳細教程(swagger3),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07java實現(xiàn)上傳圖片尺寸修改和質(zhì)量壓縮
這篇文章主要為大家詳細介紹了java實現(xiàn)上傳圖片尺寸修改和質(zhì)量壓縮,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04Spring中的@ExceptionHandler注解統(tǒng)一異常處理詳解
這篇文章主要介紹了Spring中的@ExceptionHandler注解統(tǒng)一異常處理詳解,當(dāng)我們使用這個@ExceptionHandler注解時,定義一個異常的處理方法,加上@ExceptionHandler注解,這個方法就會處理類中其他方法拋出的異常,需要的朋友可以參考下2024-01-01MybatisPlus #{param}和${param}的用法詳解
這篇文章主要介紹了MybatisPlus #{param}和${param}的用法詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09