Zuul源碼分析

目標(biāo)

明確Zuul的執(zhí)行流程和重要類的分析

Zuul過濾器的生命周期

zuul.jpg

源碼分析

zuul怎么攔截我們的請(qǐng)求弃衍?

ZuulServletFilter - 繼承 Filter | ZuulServlet - 繼承 HttpServlet
可以通過這兩個(gè)類,讓Zuul接管請(qǐng)求。由于他們的邏輯基本一致慈迈,下面用ZuulServletFilter來分析

/**
* Zuul核心處理類,攔截請(qǐng)求
**/
public class ZuulServletFilter implements Filter {

    private ZuulRunner zuulRunner;

        // 省略...

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
                // 初始化requests和responses到RequestContext中,詳見ZuulRunner#init
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            try {
                // 執(zhí)行 filterType=pre 的過濾器
                preRouting();
            } catch (ZuulException e) {
                // 執(zhí)行 filterType=error 的過濾器
                error(e);
                postRouting();
                return;
            }
            
            // Only forward onto to the chain if a zuul response is not being sent
            if (!RequestContext.getCurrentContext().sendZuulResponse()) {
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }
            
            try {
                  // 執(zhí)行 filterType=route 的過濾器
                routing();
            } catch (ZuulException e) {
                error(e);
                postRouting();
                return;
            }
            try {
                // 執(zhí)行 filterType=post 的過濾器
                postRouting();
            } catch (ZuulException e) {
                error(e);
                return;
            }
        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName()));
        } finally {
            // 清空線程變量
            RequestContext.getCurrentContext().unset();
        }
    }
}

以上方法核心步驟:

  • 初始化請(qǐng)求上下文RequestContext

  • 執(zhí)行 pre宽气、route肠缨、post過濾器逆趋,如果有錯(cuò),執(zhí)行error過濾器

ZuulRunner - 初始化RequestContext中的requests和responses并轉(zhuǎn)發(fā)Filter相關(guān)方法到FilterProcessor

public class ZuulRunner {

    private boolean bufferRequests;

    // 省略...

    /**
     * 初始化RequestContext:生成請(qǐng)求和響應(yīng)wapper保存
     * RequestContext:繼承了ConcurrentHashMap晒奕,是一個(gè)Map容器闻书,主要存放請(qǐng)求、響應(yīng)供ZuulFilters使用脑慧。
     */
    public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {

        RequestContext ctx = RequestContext.getCurrentContext();
        if (bufferRequests) {
            ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
        } else {
            ctx.setRequest(servletRequest);
        }

        ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
    }

    /**
     * executes "post" filterType  ZuulFilters
     *
     * @throws ZuulException
     */
    public void postRoute() throws ZuulException {
        FilterProcessor.getInstance().postRoute();
    }

   // 省略route() preRoute() error() 方法
}   

以上看出Zuul是通過ZuulServletFilter以filter的方式(或者以ZuulServlet以servlet的方式)攔截或者承接我們的請(qǐng)求魄眉,并在doFilter方法(service方法)中處理各種類型的ZuulFilters,并通過ZuulRunner轉(zhuǎn)發(fā)到FilterProcessor中找到對(duì)應(yīng)的filter并執(zhí)行相關(guān)邏輯闷袒。整個(gè)大致流程比較簡(jiǎn)單清晰坑律,類似于設(shè)計(jì)模式中的門面模式。

? 其中囊骤,RequestContext是存在ThreadLocal當(dāng)中晃择,可以注意到當(dāng)Zuul處理完畢之后,會(huì)清空線程變量RequestContext,以防止內(nèi)存泄露也物。

FilterProcessor怎么找到相應(yīng)ZuulFilters并執(zhí)行呢宫屠?

FilterProcessor - 執(zhí)行filters的核心類

/**
* 執(zhí)行對(duì)應(yīng)階段ZuulFilters
* sType:即為filterType,例如"post"滑蚯、"pre"激况、"route"、"error"
* 
*/
public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }
        boolean bResult = false;
              // 獲取已經(jīng)注冊(cè)了的ZuulFilters膘魄,根本是從FilterRegistry中獲取乌逐。并且list是已經(jīng)排好序的,
            // 根據(jù)給定的filterOrder
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
                // 執(zhí)行ZuulFilter邏輯并
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }

/**
* 執(zhí)行ZuulFilter创葡,并把執(zhí)行情況組合成ZuulFilterResult并返回
*/
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
  // 省略部分代碼...
  // 具體執(zhí)行在 ZuulFilter#runFilter
  ZuulFilterResult result = filter.runFilter();
  ExecutionStatus s = result.getStatus();

  switch (s) {
    case FAILED:
      t = result.getException();
      ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
      break;
    case SUCCESS:
      o = result.getResult();
      ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
    default:
      break;
  }
            
  if (t != null) throw t;

  // 統(tǒng)計(jì)每個(gè)filter的每次執(zhí)行情況
  usageNotifier.notify(filter, s);
  return o;
}

以上方法核心步驟:

  • 按序執(zhí)行各階段ZuulFilters

  • 記錄ZuulFilter執(zhí)行結(jié)果

  • 統(tǒng)計(jì)執(zhí)行情況

ZuulFilter - 最基本的Filter抽象類浙踢,自定義的Filter是繼承此Filter,FilterProcessor執(zhí)行Filter最終會(huì)轉(zhuǎn)發(fā)到此類的runFilter方法

 public ZuulFilterResult runFilter() {
   // 執(zhí)行結(jié)果以及執(zhí)行成功與否情況包裝成ZuulFilterResult返回
   ZuulFilterResult zr = new ZuulFilterResult();
   // 此filter是已被archaius禁用 「archaius是netflix開源的動(dòng)態(tài)屬性配置框架」
   if (!isFilterDisabled()) {
     // 執(zhí)行自定filter的shouldFilter方法判斷是否執(zhí)行此filter
     if (shouldFilter()) {
       Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
       try {
         Object res = run();
         zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
       } catch (Throwable e) {
         t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
         zr = new ZuulFilterResult(ExecutionStatus.FAILED);
         zr.setException(e);
       } finally {
         t.stopAndLog();
       }
     } else {
       zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
     }
   }
   return zr;
 }

Zuul把ZuulFilters存儲(chǔ)在類FilterLoader屬性名為hashFiltersByTypeConcurrentHashMap中灿渴,key為filterType(eg: pre洛波、route、post骚露、error或者自定義)

那么問題來了蹬挤,這些存在于FilterLoader的ZuulFilter是怎么加載進(jìn)來的呢?

ZuulFIlter的加載

通過層層搜索棘幸,找到類FilterFileManager 焰扳,在此類初始化的時(shí)候,會(huì)到指定路徑下讀取指定文件。同時(shí)在初始時(shí)吨悍,會(huì)創(chuàng)建守護(hù)線程來定時(shí)掃描加載文件扫茅。

public class FilterFileManager {
  // 省略代碼...

  public static void init(int pollingIntervalSeconds, String... directories) throws Exception, IllegalAccessException, InstantiationException {
    if (INSTANCE == null) INSTANCE = new FilterFileManager();
    INSTANCE.aDirectories = directories;
    // 守護(hù)線程的輪詢間隔時(shí)間
    INSTANCE.pollingIntervalSeconds = pollingIntervalSeconds;
    // 讀取并處理文件
    INSTANCE.manageFiles();
    // 開啟文件掃描的守護(hù)線程
    INSTANCE.startPoller();
  }
  
  void manageFiles() throws Exception, IllegalAccessException, InstantiationException {
        // 讀取文件
                List<File> aFiles = getFiles();
            // 通過FilterLoader來處理文件
        processGroovyFiles(aFiles);
    }
  
  // 掃描指定目錄下的指定類型文件
  List<File> getFiles() {
    List<File> list = new ArrayList<File>();
    for (String sDirectory : aDirectories) {
      if (sDirectory != null) {
        File directory = getDirectory(sDirectory);
        // Zuul有自帶類`GroovyFileFilter`是掃描 .groovy 文件.
        File[] aFiles = directory.listFiles(FILENAME_FILTER);
        if (aFiles != null) {
          list.addAll(Arrays.asList(aFiles));
        }
      }
    }
    return list;
  }
  
  // 開啟守護(hù)線程進(jìn)行輪詢
  void startPoller() {
    poller = new Thread("GroovyFilterFileManagerPoller") {
      public void run() {
        while (bRunning) {
          try {
            sleep(pollingIntervalSeconds * 1000);
            manageFiles();
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    };
    poller.setDaemon(true);
    poller.start();
  }
  
}
public class FilterLoader {
  
  // 處理文件
  public boolean putFilter(File file) throws Exception {
    String sName = file.getAbsolutePath() + file.getName();
    // 判斷如果文件被修改過,則刪除對(duì)應(yīng)已經(jīng)注冊(cè)的filter
    if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) {
      LOG.debug("reloading filter " + sName);
      filterRegistry.remove(sName);
    }
    
    ZuulFilter filter = filterRegistry.get(sName);
    if (filter == null) {
      // 編譯文件 - zuul自帶GroovyCompiler編譯groovy編寫的文件
      Class clazz = COMPILER.compile(file);
      // 如果不是抽象類即ZuulFilter育瓜,則進(jìn)行實(shí)例化并放入內(nèi)存
      if (!Modifier.isAbstract(clazz.getModifiers())) {
        filter = (ZuulFilter) FILTER_FACTORY.newInstance(clazz);
        List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
        if (list != null) {
          hashFiltersByType.remove(filter.filterType()); //rebuild this list
        }
        filterRegistry.put(file.getAbsolutePath() + file.getName(), filter);
        filterClassLastModified.put(sName, file.lastModified());
        return true;
      }
    }
    
    return false;
  } 
}

以上兩個(gè)類核心步驟 即為 FilterFileManager初始化過程

  • 掃描指定目錄下的groovy文件葫隙,通過FilterLoader編譯文件,并加載ZuulFilter
  • 開啟守護(hù)進(jìn)程躏仇,輪詢文件恋脚,動(dòng)態(tài)加載ZuulFilter

總結(jié)

  • Zuul的源碼執(zhí)行路徑:
Zuul源碼執(zhí)行流程
  • ZuulFilter的加載方式:是通過掃描.groovy文件來加載,并支持動(dòng)態(tài)加載焰手,具體可以看官方示例zuul-simple-webapp

  • Zuul的整個(gè)流程慧起,是基于servlet或filter方式在service或doFilter方法中銜接請(qǐng)求,并運(yùn)用類似門面模式編寫

? 至此册倒,Zuulfilter的加載以及各類型Filter的執(zhí)行都在源碼中找到了蚓挤。zuul-core的代碼還是很容易能看懂。下一篇驻子,會(huì)分析SpringCloud怎么整合Zuul灿意。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市崇呵,隨后出現(xiàn)的幾起案子缤剧,更是在濱河造成了極大的恐慌,老刑警劉巖域慷,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荒辕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡犹褒,警方通過查閱死者的電腦和手機(jī)抵窒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叠骑,“玉大人李皇,你說我怎么就攤上這事≈婕希” “怎么了掉房?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長慰丛。 經(jīng)常有香客問我卓囚,道長,這世上最難降的妖魔是什么诅病? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任哪亿,我火速辦了婚禮粥烁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锣夹。我一直安慰自己页徐,他們只是感情好苏潜,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布银萍。 她就那樣靜靜地躺著,像睡著了一般恤左。 火紅的嫁衣襯著肌膚如雪贴唇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天飞袋,我揣著相機(jī)與錄音戳气,去河邊找鬼。 笑死巧鸭,一個(gè)胖子當(dāng)著我的面吹牛瓶您,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纲仍,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼呀袱,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了郑叠?” 一聲冷哼從身側(cè)響起夜赵,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乡革,沒想到半個(gè)月后寇僧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沸版,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年嘁傀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片视粮。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡心包,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出馒铃,到底是詐尸還是另有隱情蟹腾,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布区宇,位于F島的核電站娃殖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏议谷。R本人自食惡果不足惜炉爆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芬首,春花似錦赴捞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至耀怜,卻和暖如春恢着,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背财破。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來泰國打工掰派, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人左痢。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓靡羡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親俊性。 傳聞我的和親對(duì)象是個(gè)殘疾皇子略步,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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

  • 前提 最近在項(xiàng)目中使用了SpringCloud,基于zuul搭建了一個(gè)提供加解密磅废、鑒權(quán)等功能的網(wǎng)關(guān)服務(wù)纳像。鑒于之前沒...
    zhrowable閱讀 2,061評(píng)論 0 8
  • 在Spring Cloud Zuul中介紹了Zuul 1.x的基本使用,本文從源碼角度介紹一下zuul的底層實(shí)現(xiàn)拯勉。...
    不知名的程序員閱讀 1,074評(píng)論 0 4
  • 回顧 Zuul是通過ZuulServletFilter或者 ZuulServlet接管我們的請(qǐng)求 Zuul整個(gè)流程...
    魚da王閱讀 386評(píng)論 0 2
  • zuul是spring cloud 微服務(wù)體系中的網(wǎng)關(guān)竟趾,可以路由請(qǐng)求到具體的服務(wù),同時(shí)做一些驗(yàn)簽宫峦,解密等的與業(yè)務(wù)無...
    數(shù)齊閱讀 2,169評(píng)論 0 3
  • 現(xiàn)在我們開始分析Zuul的源碼岔帽。首先來說一下Zuul是什么。zuul 是netflix開源的一個(gè)API Gatew...
    skyguard閱讀 680評(píng)論 0 0