Zuul源碼分析
- 2020 年 5 月 8 日
- 筆記
- Spring源碼解析
先上一張流程圖:
我們Zuul的使用如下:
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class);
}
}
application.properties配置:
zuul.routes.study-trade.service-id=study-trade
zuul.routes.study-trade.path=/orderservice/**
-
從我們的開啟Zuul註解@EnableZuulProxy開始看起,這個比較簡單,就是引入了ZuulProxyMarkerConfiguration.Marker這個Bean。
-
再找到Zuul這個包下面的spring.factories這個文件,裡面有兩個類,我們看一下
-
有一個是ZuulServerAutoConfiguration類,它裡面初始化了ZuulHandlerMapping,ZuulController,ZuulServlet。還有一個zuulProperties變數,它會將我們application.yml文件里配置的路由映射規則讀進來
-
而另一個類ZuulProxyAutoConfiguration,他重要的一點是會初始化RibbonRoutingFilter,PreDecorationFilter,SimpleHostRoutingFilter
-
以上就是應用初始化相關的準備
-
當我們請求過來時會怎麼樣呢?
我使用orderservice前綴來訪問study-trade服務://localhost:8000/orderservice/trade/testTrade/3
7. 請求發送過來走到ZuulHandlerMapping,並調用到registerHandlers方法,routes就是我application.yml里配置的映射關係
private void registerHandlers() {
Collection<Route> routes = this.routeLocator.getRoutes();
if (routes.isEmpty()) {
this.logger.warn("No routes found from RouteLocator");
}
else {
for (Route route : routes) {
//註冊到
registerHandler(route.getFullPath(), this.zuul);
}
}
}
- 在走到DispatcherServlet里的doDispatch方法,然後使用ZuulController取處理請求
//mappedHandler.getHandler()就是ZuulController
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
- ZuulController接收到請求,會使用ZuulServlet來處理請求,他的service方法如下:
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
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();
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();
}
}
可以看出他裡面調用了preRoute,postRoute,route。我們自己定義的Filter也是通過這裡的程式碼得以執行的。
10. 點開ZuulServlet的preRoute等這幾個方法時,看到他其實又是使用ZuulRunner來處理的
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
zuulRunner.init(servletRequest, servletResponse);
}
- 以preRoute()方法為例,它裡面又是使用了FilterProcessor.getInstance()來處理方法,看 FilterProcessor.getInstance()樣子,感覺是一個單例,點進去看了下,確實是使用了餓漢模式的單例。
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
- 接下來一起探究下FilterProcessor類吧
public void preRoute() throws ZuulException {
try {
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
//找到相應sType的ZuulFilter集合,然後執行run方法
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
相當於是FilterProcessor從filterloader中獲取zuulfilter。而zuulfilter是被filterFileManager所載入,並支援groovy熱載入,採用了輪詢的方式熱載入。有了這些filter之後,zuulservelet首先執行的Pre類型的過濾器,再執行route類型的過濾器,最後執行的是post類型的過濾器,如果在執行這些過濾器有錯誤的時候則會執行error類型的過濾器。執行完這些過濾器,最終將請求的結果返回給客戶端。 RequestContext就是會一直跟著整個請求周期的上下文對象,filters之間有什麼資訊需要傳遞就set一些值進去就行了。
- 那麼是怎麼調用其他服務的呢?其中有一個RibbonRoutingFilter,就實現了調用其他服務的方法,具體內容不表
- 附上一張更全的架構設計圖