Feign核心流程源碼分析

Feign是簡化Java HTTP客戶端開發(fā)的工具(java-to-httpclient-binder)宙地,它的靈感來自于Retrofit田炭、JAXRS-2.0和WebSocket未妹。Feign的初衷是降低統(tǒng)一綁定Denominator到HTTP API的復(fù)雜度舀奶。

下來我們通過簡單用例來分析工作核心原理以及流程


interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
 
static class Contributor {
  String login;
  int contributions;
}
 
public static void main(String... args) {
  GitHub github = Feign.builder()
                       .decoder(new GsonDecoder())
                       .target(GitHub.class, "https://api.github.com");
 
  // 獲取貢獻者列表蔚叨,并打印其登錄名以及貢獻次數(shù)
  List<Contributor> contributors = github.contributors("netflix", "feign");
  for (Contributor contributor : contributors) {
    System.out.println(contributor.login + " (" + contributor.contributions + ")");
  }
}

  • ==通過上面樣例我們看到首先定義了一個 GitHub接口敬扛,然后通過Feign.builder().target()創(chuàng)建了一個GitHub接口的實例。
    那么Feign.builder().target()這里應(yīng)該就是Feign的核心部分了幔荒,接下來我們跟蹤源碼糊闽,查看Feign都為我們做了什么事情==
  1. Feign.builder()創(chuàng)建Builder實例對象
   //1.創(chuàng)建Builder實例對象
   public static Feign.Builder builder() {
        //調(diào)用創(chuàng)建Builder構(gòu)建函數(shù)
        return new Feign.Builder();
    }


  //2.Builder構(gòu)建函數(shù)梳玫,初始化組件信息,當(dāng)然我們也可以自定義組件
    public Builder() {
           //日志級別
            this.logLevel = Level.NONE;
            //注解解析組件
            this.contract = new Default();
            //http發(fā)送組件
            this.client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
            //重試機制組件
            this.retryer = new feign.Retryer.Default();
            //日志
            this.logger = new NoOpLogger();
            //編碼解碼器組件
            this.encoder = new feign.codec.Encoder.Default();
            this.decoder = new feign.codec.Decoder.Default();
            this.errorDecoder = new feign.codec.ErrorDecoder.Default();
            this.options = new Options();
            //默認(rèn)的反射InvocationHandlerFactory  
            // Feign 使用的jdk自帶的動態(tài)代理
            this.invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
        }
        
  1. Builder.target()創(chuàng)建目標(biāo)實例對象
    //1. 調(diào)用target傳入目標(biāo)接口以及請求URL
    public <T> T target(Class<T> apiType, String url) {
       
         return this.target(new HardCodedTarget(apiType, url));
    }

    //2.根據(jù)Target包裝對象創(chuàng)建目標(biāo)接口的實例對象
    public <T> T target(Target<T> target) {
            //通過3構(gòu)建一個Feign對象右犹,然后通過newInstance方法獲取目標(biāo)實例對象
            return this.build().newInstance(target);
    }

    // 3. 構(gòu)建Feign對象
    public Feign build() {
            //創(chuàng)建方法代理類工廠
            Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404);
            //
            ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.errorDecoder, synchronousMethodHandlerFactory);
            //這里返回真實的Feign對象
            return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory);
    }
  1. Feign.newInstance()(通過2我們可以看出實際是通過ReflectiveFeign對象的newInstance方法創(chuàng)建)
//1. 這里是創(chuàng)建目錄接口實例對象的真正地方
// 這里可以看到是使用了jdk自帶的動態(tài)代理實現(xiàn)
//可以很清楚的看到返回的是目標(biāo)接口的代理對象
 public <T> T newInstance(Target<T> target) {
        Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();

        InvocationHandler handler = this.factory.create(target, methodToHandler);
        //這里可以看到是使用了jdk自帶的動態(tài)代理實現(xiàn)的
        //那么我們知道jdk動態(tài)代理真正執(zhí)行的是InvocationHandler接口中的invoke方法提澎,我們再跟蹤invoke,看下執(zhí)行目標(biāo)接口方法時具體邏輯念链。
        T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
        return proxy;
    }
  
    //2. 執(zhí)行目標(biāo)接口方法帶來具體實現(xiàn)(FeignInvocationHandler)
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     //在此我們可以看出目標(biāo)函數(shù)除了equals盼忌,hashCode,toString方法外都會調(diào)用this.dispatch.get(method)).invoke(args)
     //dispatch是目標(biāo)函數(shù)代理類集合掂墓,目標(biāo)接口中每個函數(shù)都會對應(yīng)有一個MethodHandler類谦纱,至于怎么得到的有興趣可以查看源碼
            if(!"equals".equals(method.getName())) {
                return "hashCode".equals(method.getName())?Integer.valueOf(this.hashCode()):("toString".equals(method.getName())?this.toString():((MethodHandler)this.dispatch.get(method)).invoke(args));
            } else {
               
            }
        }
  1. 通過3我們看出了目錄接口每個函數(shù)的執(zhí)行其實是執(zhí)行其MethodHandler類的invoke方法那么下來我們看下這里具體邏輯
    MethodHandler默認(rèn)實現(xiàn)SynchronousMethodHandler
//1. 接口方法執(zhí)行都會調(diào)用其對應(yīng)的invoke方法
 public Object invoke(Object[] argv) throws Throwable {
        RequestTemplate template = this.buildTemplateFromArgs.create(argv);
        //重試組件
        Retryer retryer = this.retryer.clone();

        while(true) {
            try {
                //執(zhí)行請求并解碼
                return this.executeAndDecode(template);
            } catch (RetryableException var5) {
                retryer.continueOrPropagate(var5);
                if(this.logLevel != Level.NONE) {
                    this.logger.logRetry(this.metadata.configKey(), this.logLevel);
                }
            }
        }
    }
    
    //2 構(gòu)建request請求并執(zhí)行和解碼
    Object executeAndDecode(RequestTemplate template) throws Throwable {
        //1. 獲取request請求
        Request request = this.targetRequest(template);
      
        long start = System.nanoTime();
        Response response;
        try {
           //通過http組件發(fā)送請求
            response = this.client.execute(request, this.options);
            response.toBuilder().request(request).build();
        } catch (IOException var15) {
           
        }
        //解碼操作調(diào)用解碼組件進行解碼
        //這里省略
        return var9;
    }
   
    //3組裝request請求,這里同時完成了攔截器調(diào)用的邏輯
     Request targetRequest(RequestTemplate template) {
        //獲取當(dāng)前請求的所有攔截器
        Iterator var2 = this.requestInterceptors.iterator();

        while(var2.hasNext()) {
            RequestInterceptor interceptor = (RequestInterceptor)var2.next();
            //依次調(diào)用攔截器進行攔截操作
            interceptor.apply(template);
        }

        //返回Request對象
        return this.target.apply(new RequestTemplate(template));
    }
 

通過上面四步我們可以清晰的看出我們通過定義目標(biāo)接口是怎么一步一步的完成了http請求發(fā)送與接收君编。
在項目中我們使用Feign是簡化我們的http操作跨嘉,同時我們理解整個http請求響應(yīng)是怎么通過Feign來完成的。這樣后期不管是定制還是問題定位吃嘿,我們都能快速有效的分析祠乃。

后記

本文記錄了使用Feign后想了解Feign實現(xiàn)原理然后跟蹤源碼的一些流程和心得。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兑燥,一起剝皮案震驚了整個濱河市亮瓷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌降瞳,老刑警劉巖嘱支,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異力崇,居然都是意外死亡斗塘,警方通過查閱死者的電腦和手機赢织,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進店門亮靴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人于置,你說我怎么就攤上這事茧吊。” “怎么了八毯?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵搓侄,是天一觀的道長。 經(jīng)常有香客問我话速,道長讶踪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任泊交,我火速辦了婚禮乳讥,結(jié)果婚禮上柱查,老公的妹妹穿的比我還像新娘。我一直安慰自己云石,他們只是感情好唉工,可當(dāng)我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著汹忠,像睡著了一般淋硝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宽菜,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天谣膳,我揣著相機與錄音,去河邊找鬼赋焕。 笑死参歹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的隆判。 我是一名探鬼主播犬庇,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼侨嘀!你這毒婦竟也來了臭挽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤咬腕,失蹤者是張志新(化名)和其女友劉穎欢峰,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涨共,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡纽帖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了举反。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懊直。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖火鼻,靈堂內(nèi)的尸體忽然破棺而出室囊,到底是詐尸還是另有隱情,我是刑警寧澤魁索,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布融撞,位于F島的核電站,受9級特大地震影響粗蔚,放射性物質(zhì)發(fā)生泄漏尝偎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一紧卒、第九天 我趴在偏房一處隱蔽的房頂上張望汁果。 院中可真熱鬧洽腺,春花似錦婚瓜、人聲如沸赛糟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葫掉。三九已至裆针,卻和暖如春刨摩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背世吨。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工澡刹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耘婚。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓罢浇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親沐祷。 傳聞我的和親對象是個殘疾皇子嚷闭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,566評論 2 349

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)赖临,斷路器胞锰,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法兢榨,內(nèi)部類的語法嗅榕,繼承相關(guān)的語法,異常的語法吵聪,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,339評論 8 265
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,773評論 6 342
  • 若不是時光匆匆 怎會恍然驚醒凌那,此笑終將成彼聲 若不是人生如夢 怎會行至臨別,方知相惜與相重 只道當(dāng)時是懵懂 不知何...
    茉小茜閱讀 326評論 5 20