SpringCloud解析之Zuul(一)

wangl110發表於2019-07-15

本文基於Spring Cloud Edgware.SR6,Zuul版本1.3.1,解析Zuul的請求攔截機制,讓大家對Zuul的原理有個大概的認識和了解。如有不對的地方,歡迎指正。

spring boot啟動過程中,一系列spring管理的bean會被初始化,其中包括ZuulController,它通過繼承ServletWrappingController來初始化ZuulServlet

image_thumb7

image_thumb4

image_thumb6

spring boot啟動完成後,通過瀏覽器發起閘道器請求,請求會到達DispatcherServlet.doDispatch(),此方法會查詢符合的Handler和HandlerAdapter來處理請求。我們來看下它是如何找到zuul的handler。

image_thumb8

this.handlerMappings中包含了當前應用所有繼承HandlerMapping介面的實現類,通過遍歷它來查詢符合當前request請求的HandlerExecutionChain

image_thumb9

進來發現呼叫的是AbstractHandlerMapping.getHandler(),內部先呼叫AbstractUrlHandlerMapping.getHandlerInternal(),查詢匹配的handler,如果沒有,則使用預設的handler,然後包裝成HandlerExecutionChain返回。

image_thumb11

image_thumb10

AbstractUrlHandlerMapping.getHandlerInternal()方法內部呼叫了lookupHandler()。

image_thumb12

image_thumb13

進來發現是ZuulHandlerMapping重寫的lookupHandler()。該方法首先判斷是否有異常,沒有的話再判斷是否是忽略的請求,不是的話就註冊handlers,然後呼叫父類的

lookupHandler()方法返回。

image_thumb15

image_thumb14

我們看下registerHandlers()做了什麼。this.routeLocator.getRoutes()就是獲取註冊在eureka的服務列表,然後遍歷,
依次儲存到AbstractUrlHandlerMapping.handlerMap中

image_thumb16

再來看下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的後半段請求uri
		String 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返回

相關文章