Zuul 生命周期 应用说明
1. 概述
- Zuul的请求引入还是来自于DispatcherServlet,然后交由ZuulHandlerMapping(由AbstractUrlHandlerMapping继承得来,它是SpringMVC对request进行分发的一个重要的抽象类)处理由初始化得来的路由定位器RouteLocator,为后续的请求分发做好准备,同时整合了基于事件从服务中心拉取服务列表的机制;
- 进入ZuulController,它的主要职责是初始化ZuulServlet以及继承ServletWrapping-Controller,通过重写handleRequest方法来将ZuulServlet引入生命周期,之后所有的请求都会经过ZuulServlet;
- 当请求进入ZuulServlet后,第一次调用会初始化ZuulRunner,非第一次调用就按Filter链order顺序执行;
- ZuulRunner中将请求和响应初始化为RequestContext,包装成FilterProcessor转化为调用preRoute()、route()、postRoute()和error()方法;
- 最后便是我们的各个Filter执行逻辑了,原始请求在Filter中经过种种变换,最后得到预期的结果。

2. 详细
2.1. 初始化阶段
初始化(或是路由刷新后的第一次访问的监听事件阶段)阶段,通过SimpleRouteLocator的getRoute方法获取路由的路径信息,将路径信息作为key放到handlerMap中,spring mvc的servlet在分发阶段会在zuul预先注册好的mapping中查找路径对应的key,如果匹配则由zuul的servlet处理。(后续阶段还会调用SimpleRouteLocator的getRoute方法,是为了得到真正的转发地址)
Zuulhandlermapping类中,初始化的时候将路由定位器中获取的路由集合注册到map中,然后在Servlet分发阶段获取当前注册的hanlerMapping,并在HandlerMapping里持有的map中查找是否满足当前request地址(通过macher判断,而不是简单的判等,zuul中是用的AntPathMacher)的路由信息。如下。
private void registerHandlers() { Collection<Route> routes = this.routeLocator.getRoutes(); if (routes.isEmpty()) { this.logger.warn("No routes found from RouteLocator"); } else { Iterator var2 = routes.iterator(); while(var2.hasNext()) { Route route = (Route)var2.next(); this.registerHandler(route.getFullPath(), this.zuul); } } }
如果路由更改,调用了路由刷新事件,在下一次任意controller被调用的时候,会执行红色代码块部分,重新将路由的url信息注册到servlet的map中。
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) { return null; } else if (this.isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) { return null; } else { RequestContext ctx = RequestContext.getCurrentContext(); if (ctx.containsKey("forward.to")) { return null; } else { if (this.dirty) { synchronized(this) { if (this.dirty) { this.registerHandlers(); this.dirty = false; } } } return super.lookupHandler(urlPath, request); } } }
Super.lookupHandler调用调用抽象父类中AbstractUrlHandlerMapping中的lookupHandler方法,查找当前请求地址对应的handler。
@Nullable protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { Object handler = this.handlerMap.get(urlPath); if (handler != null) { if (handler instanceof String) { String handlerName = (String)handler; handler = this.obtainApplicationContext().getBean(handlerName); } this.validateHandler(handler, request); return this.buildPathExposingHandler(handler, urlPath, urlPath, (Map)null); } else { List<String> matchingPatterns = new ArrayList(); Iterator var5 = this.handlerMap.keySet().iterator(); while(var5.hasNext()) { String registeredPattern = (String)var5.next(); if (this.getPathMatcher().match(registeredPattern, urlPath)) { matchingPatterns.add(registeredPattern); } else if (this.useTrailingSlashMatch() && !registeredPattern.endsWith("/") && this.getPathMatcher().match(registeredPattern + "/", urlPath)) { matchingPatterns.add(registeredPattern + "/"); } } String bestMatch = null; Comparator<String> patternComparator = this.getPathMatcher().getPatternComparator(urlPath); if (!matchingPatterns.isEmpty()) { matchingPatterns.sort(patternComparator); if (this.logger.isTraceEnabled() && matchingPatterns.size() > 1) { this.logger.trace("Matching patterns " + matchingPatterns); } bestMatch = (String)matchingPatterns.get(0); } if (bestMatch != null) { 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 + "]"); } } String pathWithinMapping; if (handler instanceof String) { pathWithinMapping = (String)handler; handler = this.obtainApplicationContext().getBean(pathWithinMapping); } this.validateHandler(handler, request); pathWithinMapping = this.getPathMatcher().extractPathWithinPattern(bestMatch, urlPath); Map<String, String> uriTemplateVariables = new LinkedHashMap(); Iterator var9 = matchingPatterns.iterator(); while(var9.hasNext()) { String matchingPattern = (String)var9.next(); if (patternComparator.compare(bestMatch, matchingPattern) == 0) { Map<String, String> vars = this.getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); Map<String, String> decodedVars = this.getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } } if (this.logger.isTraceEnabled() && uriTemplateVariables.size() > 0) { this.logger.trace("URI variables " + uriTemplateVariables); } return this.buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); } else { return null; } } }
继承关系如下图:

备注:
Pathmach使用的是AntPath

2.2. Spring Mvc 请求分发
根据request请求的地址,匹配到zuulHandlerMapping中有已经注册的地址,所以交给zuulhandlermapping处理。


2.3. Zuul网关Pre阶段
Zuul流程简化图如下:

pre阶段步骤2,先是在初始化阶段中生成的map中查找是否有匹配当前request请求地址的key,如果有,则取出对应key的zuulRoute对象,再
调用SimpleRouteLocator类中的getRoute方法
再次(之前初始化的时候也调用过getRoute方法,不过得到的对象放到了Path的handlerMapping中了,用于spring mvc的路径匹配)获取路由
Route route = this.routeLocator.getMatchingRoute(requestURI);
protected Route getRoute(ZuulRoute route, String path) { if (route == null) { return null; } else { if (log.isDebugEnabled()) { log.debug("route matched=" + route); } String targetPath = path; String prefix = this.properties.getPrefix(); if (prefix.endsWith("/")) { prefix = prefix.substring(0, prefix.length() - 1); } if (path.startsWith(prefix + "/") && this.properties.isStripPrefix()) { targetPath = path.substring(prefix.length()); } if (route.isStripPrefix()) { int index = route.getPath().indexOf("*") - 1; if (index > 0) { String routePrefix = route.getPath().substring(0, index); targetPath = targetPath.replaceFirst(routePrefix, ""); prefix = prefix + routePrefix; } } Boolean retryable = this.properties.getRetryable(); if (route.getRetryable() != null) { retryable = route.getRetryable(); } return new Route(route.getId(), targetPath, route.getLocation(), prefix, retryable, route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null, route.isStripPrefix()); } }
pre阶段步骤3,为路由阶段的最后的转发地址做准备。
最后的转发地址=路由中的location+经过计算的path。路由中的location就是路由表中的location,直接读取即可。经过计算的path值则需要根据是否替换前缀以及zuul的prefix来确定,例如请求地址为/query/user/getUserInfo?userId=1,路由表中的原始配置为path=/query/**,location为http://127.0.0.1:8080/authserver。如果替换前缀则根据getRoute方法获取的路由对象的path为“/user/getUserInfo”(这和初始化时候的path不同,初始化时为/**,因为入参不同,初始化时候的入参为/query/**,而此时的入参为/query/user/getUserInfo,初始化时是为了servlet分发过程匹配/query/**后得到handler,进而才能进入到zuul的controller中),location不变,则最终的转发地址为http://127.0.0.1:8080/authserver/user/getUserInfo
如果不替换前缀,此时获取的path为/query/user/getUserInfo,那么最终的转发地址则为http://127.0.0.1:8080/authserver/query/user/getUserInfo
ctx.put("requestURI", route.getPath());
ctx.setRouteHost(this.getUrl(location));
2.4. Zuul网关Route阶段
根据上下文中的requestURI和RouteHost,构建新的http请求发送到目的地
String contextURI = (String)context.get("requestURI");
URL host = RequestContext.getCurrentContext().getRouteHost(); public URL getRouteHost() { return (URL)this.get("routeHost"); }
3. 扩展
在pre阶段给path赋值,创建要转发的route信息的时候,当路由配置为替换前缀的情况下, 源代码中是按照“*”号的位置来进行逻辑处理,但是在某些情况下不能采用通配符的方式配置路由转发规则,所以扩展源代码,继承SimpleRouteLocator
//如果替换path前缀,会根据字符串中的*号来判断从哪替换,但是如果path前缀没有*号,那么就会出现错误。因此,修改这段代码如下: if (route.isStripPrefix()) { int index = route.getPath().indexOf("*") - 1; if (index > 0) { String routePrefix = route.getPath().substring(0, index); targetPath = targetPath.replaceFirst(routePrefix, ""); prefix = prefix + routePrefix; } else{ //源码中没有这个分支逻辑。这部分代码的目的是处理zuul中path没有ant匹配表达式的情形, //例如如下配置: /* zuul: routes: authserver: path: /user/getInfo url: http://localhost:8098/auth/api/getInfo stripPrefix: true */ /* 星号的替换规则是将星号之前的替换成空,变成“/**”的格式。 而这里是前端请求的地址需要被完全替换掉,转换成路由对象始终的location地址 */ targetPath = ""; prefix = prefix + route.getPath(); } }

浙公网安备 33010602011771号