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