本文基於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返回