本文基於Spring Cloud Edgware.SR6,Zuul版本1.3.1,解析Zuul的請求攔截機制,讓大家對Zuul的原理有個大概的認識和了解。如有不對的地方,歡迎指正。
在上一期的SpringCloud解析之Zuul(一),我們瞭解了spring boot在接收一個閘道器請求後,是如何找到與之匹配的ZuulHandlerMapping。今天我們繼續探尋,這個ZuulHandlerMapping是如何處理閘道器請求的。
在開始之前,我們先了解一下,為什麼spring boot要如此大費周章的,只為找到ZuulHandlerMapping。
通過上一期文章,我們知道所有的瀏覽器請求都會到達DispatcherServlet,並且DispatcherServlet中有一個成員變數handlerMappings,會掃描所有繼承HandlerMapping介面的實現類,其實它裡面還有一個成員變數handlerAdapters,這個變數會掃描所有HandlerAdapter介面的實現類。從名字命名上可以看出,handlerAdapters包含了所有的處理器介面卡,所有的HandlerMapping都會通過其對應的HandlerAdapter來執行,目的是為了解耦。因為這樣設計之後,DispatcherServlet和HandlerMapping都不會包含特定的邏輯程式碼,也不需要關心是什麼型別的請求,只需要呼叫對應的HandlerAdapter的方法就可以了。
ZuulHandlerMapping裡面有什麼呢?ZuulHandlerMapping中有一個成員變數ZuulController,ZuulController繼承了ServletWrappingController,ServletWrappingController中有servletClass和servletInstance。
在上一期開頭,我粗略的提及,在spring boot啟動過程中會初始化ZuulController,繼而通過反射初始化ZuulServlet。詳細的說,是spring boot啟動過程中會通過org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration自動配置類來初始化zuul,其中包括ZuulHandlerMapping。所以,ZuulHandlerMapping中有ZuulController,ZuulController中有ZuulServlet,這些物件例項在spring boot啟動成功後就有了。
好,在瞭解了這些之後,我們繼續今天的內容。DispatcherServlet.doDispatch()在獲取到ZuulHandlerMapping之後,馬上呼叫方法getHandlerAdapter()獲取對應的HandlerAdapter。
可以看到,getHandlerAdapter()內部是在遍歷handlerAdapters,然後通過呼叫各自的supports()方法判斷哪一個handlerAdapter支援前面獲取的handler處理器。這裡取到的是SimpleControllerHandlerAdapter。
取到handlerAdapter之後,開始呼叫handlerAdapter的handle()方法,mappedHandler.getHandler()就是ZuulController。SimpleControllerHandlerAdapter.handle()內部,是ZuulController.handleRequest(),其內部又是呼叫父類的handleRequestInternal(),真正起作用的是servletInstance.service(),而servletInstance正是ZuulServlet(我們開頭講到的)
下面我們看下,ZuulServlet.service()做了什麼。
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try {
// 初始化,將servletRequest和servletResponse儲存到請求上下文RequestContext
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); // Marks this request as having passed through the "Zuul engine", as opposed to servlets // explicitly bound in web.xml, for which requests will not have the same data attached RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan();
// 順序執行ZuulFilter過濾器,也包括我們繼承ZuulFilter的自定義過濾器
try { preRoute(); } catch (ZuulException e) { error(e); postRoute(); return; } try { route(); } catch (ZuulException e) { error(e); postRoute(); return; } try { postRoute(); } catch (ZuulException e) { error(e); return; } } catch (Throwable e) { error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } }
簡直一目瞭然。我們往下看,它是如何會執行我們繼承ZuulFilter的自定義過濾器。(上文忘了說,在ZuulServlet例項化後,會呼叫init()方法初始化ZuulRunner)。
以preRoute()為例,不難看出,最終的執行者是FilterProcessor,在runFilters()方法中,獲取對應型別的List<ZuulFilter>,然後遍歷執行processZuulFilter(zuulFilter)
FilterLoader.getInstance().getFiltersByType(sType)內容如下,就是從hashFiltersByType中獲取filterType對應的List<ZuulFilter>
那麼新問題就來了,hashFiltersByType裡的值怎麼來的。FilterLoader還有一個putFilter(File file)方法,通過查詢原始碼很容易發現,這個方法是在FilterFileManager中被呼叫的。官方的解釋是,FilterFileManager類負責初始化和更新filter(但是我一直搞不懂它是如何呼叫執行的,因為我打斷點都沒有效果,猜測是Groovy,不甚瞭解,知道的小夥伴幫忙評論解釋下,感激不盡)。
INSTANCE.manageFiles()最終呼叫的就是FilterLoader.putFilter()方法,INSTANCE.startPoller()則是啟動一個守護執行緒,輪詢執行manageFiles()。
回過頭來,我們繼續看processZuulFilter(zuulFilter)幹了什麼。額,其實關鍵就下面一句,
ZuulFilter.runFilter()方法,首先判斷!isFilterDisabled(),shouldFilter(),同時滿足時才執行run()方法,即配置檔案中isFilter沒有禁用,並且自定義過濾器的shouldFilter()返回true,才會執行重寫ZuulFilter的run()方法。
至此,zuul閘道器執行過濾器和自定義過濾器的過程就講完了(啊,累死了)。
最後,總結一下:
1.spring boot通過介面卡設計模式,使用對應的HandlerAdapter來執行ZuulHandlerMapping,以達到解耦的目的。而ZuulHandlerMapping最終是呼叫的ZuulServlet.service()
2.ZuulServlet.service()裡面,也是取出filterType型別的所有ZuulFilter,然後遍歷執行ZuulFilter的run()方法,這也是為什麼我們寫自定義過濾器要繼承ZuulFilter的原因。
下一期,我們來聊聊Zuul對請求過濾處理後,是如何將請求轉發到對應的伺服器的。