關(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;
? }至此,代理類注冊完成。
當調(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默認用springEncoder,同樣是可以替換成Gson等。

=========== 以上是feign的調(diào)用流程,以下是feign使用過程的坑 ===========
①feign在D版本后默認關(guān)閉hystrix,要想傳遞請求頭,如果不用hystrix的話在feign攔截器里塞一遍就好;如果要用hystrix,那么改用信號量。
②在C版本后默認關(guān)閉hystrix,要使用要手動開啟
③不要妄想改變feign的邏輯,因為代理模式被寫成final,無法修改
④無法在解碼器里拋自定義異常,因為feign最終會統(tǒng)一攔截,拋出一個feignexception。你想把統(tǒng)一攔截也改了,那么你可以看看第③坑。
⑤feign的重試機制,默認是1,也就是說超時時間會變成2倍。這個可以通過配置修改。
⑥feign集成的負載均衡器ribbon,feign有個緩存,ribbon也有個緩存,會造成上線延遲,可以修改配置實現(xiàn)。
⑦feign對格式化時間處理有問題
⑧如果你是使用生產(chǎn)者提供api,并且實現(xiàn)該接口,@requestparam可以不用在實現(xiàn)類寫,但是@requestbody不寫無法映射
以上的坑都是我在實際工作中一個一個爬過來的,僅為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Mybatis配置之<properties>屬性配置元素解析
這篇文章主要介紹了Mybatis配置之<properties>屬性配置元素解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
JVM生產(chǎn)環(huán)境調(diào)優(yōu)實戰(zhàn)案例講解
這篇文章主要介紹了JVM生產(chǎn)環(huán)境調(diào)優(yōu)實戰(zhàn),本文通過實例代碼給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2025-04-04
SpringBoot Mybatis批量插入Oracle數(shù)據(jù)庫數(shù)據(jù)
這篇文章主要介紹了SpringBoot Mybatis批量插入Oracle數(shù)據(jù)庫數(shù)據(jù),文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-08-08
JUnit測試控制@Test執(zhí)行順序的三種方式小結(jié)
這篇文章主要介紹了JUnit測試控制@Test執(zhí)行順序的三種方式小結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
Spring mvc結(jié)果跳轉(zhuǎn)方法詳解
這篇文章主要介紹了Spring mvc結(jié)果跳轉(zhuǎn)方法詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03

