1. 程式人生 > >Spring Cloud Netflix Zuul原始碼分析之路由註冊篇

Spring Cloud Netflix Zuul原始碼分析之路由註冊篇

微信公眾號:I am CR7
如有問題或建議,請在下方留言;
最近更新:2018-12-29

前言

繼上一篇Spring Cloud Netflix Zuul原始碼分析之預熱篇,我們知道了兩個重要的類:ZuulHandlerMapping和SimpleControllerHandlerAdapter。今天,ZuulHandlerMapping正式亮相,我們來分析它是何時註冊的路由資訊。

專案背景

zuul配置

 1server:
2    port: 8558
3
4zuul:
5    routes:
6
        user-api:
7            path: /user-api/**
8            stripPrefix: true
9            url: http://localhost:9091/

10        role-api:
11            path: /role-api/**
12            stripPrefix: true
13            url: http://localhost:9092/

14        resource:
15            path: /resource/**
16            stripPrefix: true
17            url: http://testi.phoenixpay.com/
複製程式碼

測試請求

http://localhost:8558/role-api/info/7

資源

  • jdk:1.8.0_51
  • tomcat:8.5.34

tomcat處理請求

時序圖

image
image

高清大圖請訪問: user-gold-cdn.xitu.io/2018/12/29/…

http請求經由tomcat容器,會進入到StandardWrapperValve類的invoke()方法裡。至於原因,不是本文討論內容,後續會寫tomcat原始碼分析做專門講解,這裡不做展開,請接著往下看。

簡化版invoke()原始碼:

 1public final void invoke(Request request, Response response)
2    throws IOException, ServletException 
{
3    StandardWrapper wrapper = (StandardWrapper) getContainer();
4    Servlet servlet = null;
5    // 第一篇文章中講解的,DispatcherServlet初始化處理,返回DispatcherServlet實體
6    servlet = wrapper.allocate();
7    // Create the filter chain for this request
8    ApplicationFilterChain filterChain =
9        ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
10    // 呼叫ApplicationFilterChain的doFilter方法,處理請求
11    filterChain.doFilter(request.getRequest(),
12        response.getResponse());
13}
複製程式碼

繼續走進去,看核心類ApplicationFilterChain的doFilter方法。

簡化版doFilter()原始碼:

 1@Override
2public void doFilter(ServletRequest request, ServletResponse response)
3    throws IOException, ServletException 
{
4
5    if( Globals.IS_SECURITY_ENABLED ) {
6        final ServletRequest req = request;
7        final ServletResponse res = response;
8        java.security.AccessController.doPrivileged(
9            new java.security.PrivilegedExceptionAction<Void>() {
10                @Override
11                public Void run()
12                    throws ServletException, IOException 
{
13                    internalDoFilter(req,res);
14                    return null;
15                }
16            }
17        );
18    } else {
19        internalDoFilter(request,response);
20    }
21}
22
23private void internalDoFilter(ServletRequest request,
24                              ServletResponse response)

25    throws IOException, ServletException 
{
26
27    // 遍歷過濾器鏈,依次執行過濾器的doFilter方法
28    if (pos < n) {
29        ApplicationFilterConfig filterConfig = filters[pos++];
30        Filter filter = filterConfig.getFilter();
31        filter.doFilter(request, response, this);
32    }
33    // 呼叫DispatcherServlet的service方法,正式處理請求
34    servlet.service(request, response);
35}
複製程式碼

補充:ApplicationFilterChain的設計採用職責鏈模式,包含了一組過濾器,逐個遍歷執行doFilter方法,最後在執行結束前會回調回ApplicationFilterChain。這組過濾器裡WebMvcMetricsFilter是我們本文討論的重點,至於其他過濾器您可以自行檢視。對於DispatcherServlet的service方法會在下一篇文章中進行詳細講解。

斷點情況:

執行過濾器鏈
執行過濾器鏈

到這一刻,本文討論的主角,時序圖中的WebMvcMetricsFilter,正式亮相了。

WebMvcMetricsFilter

該過濾器繼承OncePerRequestFilter,是用來統計HTTP請求在經過SpringMVC處理後的時長和結果。doFilter()方法在父類中,具體邏輯由子類覆蓋doFilterInternal方法去處理。我們來看下doFilterInternal()原始碼。

原始碼

 1@Override
2protected void doFilterInternal(HttpServletRequest request,
3        HttpServletResponse response, FilterChain filterChain)

4        throws ServletException, IOException 
{
5    filterAndRecordMetrics(request, response, filterChain);
6}
7
8private void filterAndRecordMetrics(HttpServletRequest request,
9        HttpServletResponse response, FilterChain filterChain)

10        throws IOException, ServletException 
{
11    Object handler;
12    try {
13        handler = getHandler(request);
14    }
15    catch (Exception ex) {
16        logger.debug("Unable to time request", ex);
17        filterChain.doFilter(request, response);
18        return;
19    }
20    filterAndRecordMetrics(request, response, filterChain, handler);
21}
22
23private Object getHandler(HttpServletRequest request) throws Exception {
24    HttpServletRequest wrapper = new UnmodifiableAttributesRequestWrapper(request);
25    // 從ApplicationContext裡獲取HandlerMappingIntrospector實體
26    for (HandlerMapping mapping : getMappingIntrospector().getHandlerMappings()) {
27        HandlerExecutionChain chain = mapping.getHandler(wrapper);
28        if (chain != null) {
29            if (mapping instanceof MatchableHandlerMapping) {
30                return chain.getHandler();
31            }
32            return null;
33        }
34    }
35    return null;
36}
複製程式碼

斷點情況

image
image

這裡根據HandlerMappingIntrospector獲取所有的HandlerMapping,逐個遍歷,獲取請求匹配的Handler,封裝進HandlerExecutionChain,交由WebMvcMetricsFilter進行相關的統計處理。這一系列HandlerMapping裡就包含了我們要看的ZuulHandlerMapping。我們繼續往裡看。

ZuulHandlerMapping

ZuulHandlerMapping作為MVC HandlerMapping的實現,用來將進入的請求對映到遠端服務。為了便於理解其getHandler()原理,筆者畫了一個類圖。

類圖

HandlerMapping類圖
HandlerMapping類圖

呼叫ZuulHandlerMapping的getHandler(),最終會進入lookupHandler(),這是本文分析的重點,往下看原始碼。

原始碼

 1public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
2
3    private final RouteLocator routeLocator;
4    private final ZuulController zuul;
5    private ErrorController errorController;
6    private PathMatcher pathMatcher = new AntPathMatcher();
7    private volatile boolean dirty = true;
8
9    @Override
10    protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
11        if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
12            return null;
13        }
14        if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
15        RequestContext ctx = RequestContext.getCurrentContext();
16        if (ctx.containsKey("forward.to")) {
17            return null;
18        }
19        if (this.dirty) {
20            synchronized (this) {
21                if (this.dirty) {
22                    // 首次會註冊路由資訊
23                    registerHandlers();
24                    this.dirty = false;
25                }
26            }
27        }
28        //呼叫父類方法根據urlPath查詢Handler
29        return super.lookupHandler(urlPath, request);
30    }
31
32    private void registerHandlers() {
33        Collection<Route> routes = this.routeLocator.getRoutes();
34        if (routes.isEmpty()) {
35            this.logger.warn("No routes found from RouteLocator");
36        }
37        else {
38            // 遍歷路由資訊,將urlPath和ZuulController註冊到父類handlerMap裡
39            for (Route route : routes) {
40                registerHandler(route.getFullPath(), this.zuul);
41            }
42        }
43    }
複製程式碼

如此一來,請求http://localhost:8558/role-api/info/7,就會由AbstractUrlHandlerMapping的lookupHandler方法,找到ZuulController。雖然WebMvcMetricsFilter對找到的ZuulController只是做統計相關的處理,但是這為後面講述DispatcherServlet正式處理請求,由Zuul轉發到後端微服務,打下了很好的基礎。

日誌

 12018-12-28 14:32:11.939 [http-nio-8558-exec-9] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - route matched=ZuulRoute{id='user-api'path='/user-api/**', serviceId='null', url='http://localhost:9091/', stripPrefix=true, retryable=null, sensitiveHeaders=[], customSensitiveHeaders=false, }
22018-12-28 14:32:11.940 [http-nio-8558-exec-9] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - route matched=ZuulRoute{id='role-api'path='/role-api/**', serviceId='null', url='http://localhost:9092/', stripPrefix=true, retryable=null, sensitiveHeaders=[], customSensitiveHeaders=false, }
32018-12-28 14:32:11.940 [http-nio-8558-exec-9] DEBUG o.s.c.n.z.filters.SimpleRouteLocator - route matched=ZuulRoute{id='resource'path='/resource/**', serviceId='null', url='http://testi.phoenixpay.com/', stripPrefix=true, retryable=null, sensitiveHeaders=[], customSensitiveHeaders=false, }
4
52018-12-28 14:36:27.630 [http-nio-8558-exec-9] INFO  o.s.c.n.zuul.web.ZuulHandlerMapping - Mapped URL path [/user-api/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
62018-12-28 14:36:38.358 [http-nio-8558-exec-9] INFO  o.s.c.n.zuul.web.ZuulHandlerMapping - Mapped URL path [/role-api/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
72018-12-28 14:36:44.478 [http-nio-8558-exec-9] INFO  o.s.c.n.zuul.web.ZuulHandlerMapping - Mapped URL path [/resource/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
8
92018-12-28 14:38:36.556 [http-nio-8558-exec-9] DEBUG o.s.c.n.zuul.web.ZuulHandlerMapping - Matching patterns for request [/role-api/info/7] are [/role-api/**]
102018-12-28 14:39:11.325 [http-nio-8558-exec-9] DEBUG o.s.c.n.zuul.web.ZuulHandlerMapping - URI Template variables for request [/role-api/info/7] are {}
112018-12-28 14:39:56.557 [http-nio-8558-exec-9] DEBUG o.s.c.n.zuul.web.ZuulHandlerMapping - Mapping [/role-api/info/7] to HandlerExecutionChain with handler [[email protected]399337and 1 interceptor
複製程式碼

斷點情況

image
image

總結

讀到這裡,您可能會說,又被騙了,標題黨,還是沒有提到Zuul。我只想說,"宅陰陰"(泰國旅遊時學到的唯一一句)。凡事都有個循序漸進的過程,本文依舊是在為後面Zuul分析做鋪墊,一口吃一個胖子,往往容易消化不良。希望筆者的良苦用心,您能夠明白,當然更希望對您能有所幫助。最後,感謝您的支援!!!祝進步,2018年12月29日,祁琛。