本文基於Spring Cloud Edgware.SR6,Zuul版本1.3.1,解析Zuul的請求攔截機制,讓大家對Zuul的原理有個大概的認識和了解。如有不對的地方,歡迎指正。
spring boot啟動過程中,一系列spring管理的bean會被初始化,其中包括ZuulController,它通過繼承ServletWrappingController來初始化ZuulServlet
spring boot啟動完成後,通過瀏覽器發起閘道器請求,請求會到達DispatcherServlet.doDispatch(),此方法會查詢符合的Handler和HandlerAdapter來處理請求。我們來看下它是如何找到zuul的handler。
this.handlerMappings中包含了當前應用所有繼承HandlerMapping介面的實現類,通過遍歷它來查詢符合當前request請求的HandlerExecutionChain
進來發現呼叫的是AbstractHandlerMapping.getHandler(),內部先呼叫AbstractUrlHandlerMapping.getHandlerInternal(),查詢匹配的handler,如果沒有,則使用預設的handler,然後包裝成HandlerExecutionChain返回。
AbstractUrlHandlerMapping.getHandlerInternal()方法內部呼叫了lookupHandler()。
進來發現是ZuulHandlerMapping重寫的lookupHandler()。該方法首先判斷是否有異常,沒有的話再判斷是否是忽略的請求,不是的話就註冊handlers,然後呼叫父類的
lookupHandler()方法返回。
我們看下registerHandlers()做了什麼。this.routeLocator.getRoutes()就是獲取註冊在eureka的服務列表,然後遍歷,
依次儲存到AbstractUrlHandlerMapping.handlerMap中
再來看下super.lookupHandler(urlPath, request)。
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { // 這裡就從上面註冊好的handlerMap中獲取請求urlPath對應的handler Object handler = this.handlerMap.get(urlPath); if (handler != null) { // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); } validateHandler(handler, request); return buildPathExposingHandler(handler, urlPath, urlPath, null); } // 如果獲取不到,則進行正則匹配,如果還匹配不到的話,則返回null List<String> matchingPatterns = new ArrayList<String>(); for (String registeredPattern : this.handlerMap.keySet()) { if (getPathMatcher().match(registeredPattern, urlPath)) { matchingPatterns.add(registeredPattern); } else if (useTrailingSlashMatch()) { if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) { matchingPatterns.add(registeredPattern +"/"); } } } String bestMatch = null;// 匹配到之後,用請求urlPath對應的patternComparator,對所有匹配的url進行排序,之後獲取第一個匹配的url Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath); if (!matchingPatterns.isEmpty()) { Collections.sort(matchingPatterns, patternComparator); if (logger.isDebugEnabled()) { logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns); } bestMatch = matchingPatterns.get(0); } if (bestMatch != null) {// 先根據排序後的第一個url獲取對應的handler,如果沒有的話則用”/”再取一次handler = this.handlerMap.get(bestMatch); if (handler == null) { if (bestMatch.endsWith("/")) { handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); } if (handler == null) { throw new IllegalStateException( "Could not find handler for best pattern match [" + bestMatch + "]"); } } // 如果handler是String,則從應用上下文中獲取對應的bean if (handler instanceof String) { String handlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); } validateHandler(handler, request);// 解析對映url的後半段請求uriString pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath); // 最後再確認一次bestMatch是否是最匹配請求的路由 Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } } if (logger.isDebugEnabled()) { logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables); }// 構建HandlerExecutionChain並返回return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); } // No handler found... return null; }
至此,終於找到了zuul的handler,其中有些細節沒有提或是略過,有興趣的朋友可以自行下去翻閱。
總結一下:
1.請求執行到DispatcherServlet.doDispatch(),此方法中呼叫getHandler(),遍歷所有實現handlerMapping介面的實現類來查詢請求對應的HandlerExecutionChain
2.getHandler()內部是遍歷執行AbstractHandlerMapping.getHandler(),它的內部又是執行的AbstractUrlHandlerMapping.getHandlerInternal(),而AbstractUrlHandlerMapping內部呼叫的lookupHandler()實則是ZuulHandlerMapping重寫的lookupHandler(),目的是獲取註冊中心的消費者路由列表,
3.然後ZuulHandlerMapping呼叫父類AbstractUrlHandlerMapping的lookupHandler(),用請求url匹配路由列表,獲取最匹配的一個路由,包裝成HandlerExecutionChain返回