微信公众号:如有问题或建议,请在下方留言;
最近更新:2018-12-29
继上一篇 Spring Cloud Netflix Zuul源码分析之预热篇 ,我们知道了两个重要的类:ZuulHandlerMapping和SimpleControllerHandlerAdapter。今天,ZuulHandlerMapping正式亮相,我们来分析它是何时注册的路由信息。
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
http请求经由tomcat容器,会进入到StandardWrapperValve类的invoke()方法里。至于原因,不是本文讨论内容,后续会写tomcat源码分析做专门讲解,这里不做展开,请接着往下看。
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方法。
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,正式亮相了。
该过滤器继承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} 复制代码
这里根据HandlerMappingIntrospector获取所有的HandlerMapping,逐个遍历,获取请求匹配的Handler,封装进HandlerExecutionChain,交由WebMvcMetricsFilter进行相关的统计处理。这一系列HandlerMapping里就包含了我们要看的ZuulHandlerMapping。我们继续往里看。
ZuulHandlerMapping作为MVC HandlerMapping的实现,用来将进入的请求映射到远端服务。为了便于理解其getHandler()原理,笔者画了一个类图。
调用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 [org.springframework.cloud.netflix.zuul.web.ZuulController@399337] and 1 interceptor 复制代码
读到这里,您可能会说,又被骗了,标题党,还是没有提到Zuul。我只想说,"宅阴阴"(泰国旅游时学到的唯一一句)。凡事都有个循序渐进的过程,本文依旧是在为后面Zuul分析做铺垫,一口吃一个胖子,往往容易消化不良。希望笔者的良苦用心,您能够明白,当然更希望对您能有所帮助。最后,感谢您的支持!!!祝进步,2018年12月29日,祁琛。