微信公众号: I am CR7
如有问题或建议, 请在下方留言;
最近更新: 2018-12-29
前言
继上一篇 Spring Cloud Netflix Zuul 源码分析之预热篇 https://mp.weixin.qq.com/s/csbqZ9HhLbkJWagBfP13dQ , 我们知道了两个重要的类: ZuulHandlerMapping 和 SimpleControllerHandlerAdapter. 今天, ZuulHandlerMapping 正式亮相, 我们来分析它是何时注册的路由信息.
项目背景
zuul 配置
- server:
- port: 8558
- zuul:
- routes:
- user-API:
- path: /user-API/**
- stripPrefix: true
- url: http://localhost:9091/
- role-API:
- path: /role-API/**
- stripPrefix: true
- url: http://localhost:9092/
- resource:
- path: /resource/**
- stripPrefix: true
- url: http://testi.phoenixpay.com/
测试请求
http://localhost:8558/role-API/info/7
资源
- jdk:1.8.0_51
- tomcat:8.5.34
tomcat 处理请求
时序图
image
高清大图请访问:
http 请求经由 tomcat 容器, 会进入到 StandardWrapperValve 类的 invoke() 方法里. 至于原因, 不是本文讨论内容, 后续会写 tomcat 源码分析做专门讲解, 这里不做展开, 请接着往下看.
简化版 invoke() 源码:
- public final void invoke(Request request, Response response)
- throws IOException, ServletException {
- StandardWrapper wrapper = (StandardWrapper) getContainer();
- Servlet servlet = null;
- // 第一篇文章中讲解的, DispatcherServlet 初始化处理, 返回 DispatcherServlet 实体
- servlet = wrapper.allocate();
- // Create the filter chain for this request
- ApplicationFilterChain filterChain =
- ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
- // 调用 ApplicationFilterChain 的 doFilter 方法, 处理请求
- filterChain.doFilter(request.getRequest(),
- response.getResponse());
- }
继续走进去, 看核心类 ApplicationFilterChain 的 doFilter 方法.
简化版 doFilter() 源码:
- @Override
- public void doFilter(ServletRequest request, ServletResponse response)
- throws IOException, ServletException {
- if( Globals.IS_SECURITY_ENABLED ) {
- final ServletRequest req = request;
- final ServletResponse res = response;
- java.security.AccessController.doPrivileged(
- new java.security.PrivilegedExceptionAction<Void>() {
- @Override
- public Void run()
- throws ServletException, IOException {
- internalDoFilter(req,res);
- return null;
- }
- }
- );
- } else {
- internalDoFilter(request,response);
- }
- }
- private void internalDoFilter(ServletRequest request,
- ServletResponse response)
- throws IOException, ServletException {
- // 遍历过滤器链, 依次执行过滤器的 doFilter 方法
- if (pos < n) {
- ApplicationFilterConfig filterConfig = filters[pos++];
- Filter filter = filterConfig.getFilter();
- filter.doFilter(request, response, this);
- }
- // 调用 DispatcherServlet 的 service 方法, 正式处理请求
- servlet.service(request, response);
- }
补充: ApplicationFilterChain 的设计采用职责链模式, 包含了一组过滤器, 逐个遍历执行 doFilter 方法, 最后在执行结束前会回调回 ApplicationFilterChain. 这组过滤器里 webMvcMetricsFilter 是我们本文讨论的重点, 至于其他过滤器您可以自行查看. 对于 DispatcherServlet 的 service 方法会在下一篇文章中进行详细讲解.
断点情况:
执行过滤器链
到这一刻, 本文讨论的主角, 时序图中的 WebMvcMetricsFilter, 正式亮相了.
WebMvcMetricsFilter
该过滤器继承 OncePerRequestFilter, 是用来统计 HTTP 请求在经过 SpringMVC 处理后的时长和结果. doFilter() 方法在父类中, 具体逻辑由子类覆盖 doFilterInternal 方法去处理. 我们来看下 doFilterInternal() 源码.
源码
- @Override
- protected void doFilterInternal(HttpServletRequest request,
- HttpServletResponse response, FilterChain filterChain)
- throws ServletException, IOException {
- filterAndRecordMetrics(request, response, filterChain);
- }
- private void filterAndRecordMetrics(HttpServletRequest request,
- HttpServletResponse response, FilterChain filterChain)
- throws IOException, ServletException {
- Object handler;
- try {
- handler = getHandler(request);
- }
- catch (Exception ex) {
- logger.debug("Unable to time request", ex);
- filterChain.doFilter(request, response);
- return;
- }
- filterAndRecordMetrics(request, response, filterChain, handler);
- }
- private Object getHandler(HttpServletRequest request) throws Exception {
- HttpServletRequest wrapper = new UnmodifiableAttributesRequestWrapper(request);
- // 从 ApplicationContext 里获取 HandlerMappingIntrospector 实体
- for (HandlerMapping mapping : getMappingIntrospector().getHandlerMappings()) {
- HandlerExecutionChain chain = mapping.getHandler(wrapper);
- if (chain != null) {
- if (mapping instanceof MatchableHandlerMapping) {
- return chain.getHandler();
- }
- return null;
- }
- }
- return null;
- }
断点情况
image
这里根据 HandlerMappingIntrospector 获取所有的 HandlerMapping, 逐个遍历, 获取请求匹配的 Handler, 封装进 HandlerExecutionChain, 交由 WebMvcMetricsFilter 进行相关的统计处理. 这一系列 HandlerMapping 里就包含了我们要看的 ZuulHandlerMapping. 我们继续往里看.
ZuulHandlerMapping
ZuulHandlerMapping 作为 MVC HandlerMapping 的实现, 用来将进入的请求映射到远端服务. 为了便于理解其 getHandler() 原理, 笔者画了一个类图.
类图
HandlerMapping 类图
调用 ZuulHandlerMapping 的 getHandler(), 最终会进入 lookupHandler(), 这是本文分析的重点, 往下看源码.
源码
- public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
- private final RouteLocator routeLocator;
- private final ZuulController zuul;
- private ErrorController errorController;
- private PathMatcher pathMatcher = new AntPathMatcher();
- private volatile boolean dirty = true;
- @Override
- protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
- if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
- return null;
- }
- if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
- RequestContext ctx = RequestContext.getCurrentContext();
- if (ctx.containsKey("forward.to")) {
- return null;
- }
- if (this.dirty) {
- synchronized (this) {
- if (this.dirty) {
- // 首次会注册路由信息
- registerHandlers();
- this.dirty = false;
- }
- }
- }
- // 调用父类方法根据 urlPath 查找 Handler
- return super.lookupHandler(urlPath, request);
- }
- private void registerHandlers() {
- Collection<Route> routes = this.routeLocator.getRoutes();
- if (routes.isEmpty()) {
- this.logger.warn("No routes found from RouteLocator");
- }
- else {
- // 遍历路由信息, 将 urlPath 和 ZuulController 注册到父类 handlerMap 里
- for (Route route : routes) {
- registerHandler(route.getFullPath(), this.zuul);
- }
- }
- }
如此一来, 请求 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
断点情况
image
总结
读到这里, 您可能会说, 又被骗了, 标题党, 还是没有提到 Zuul. 我只想说,"宅阴阴"(泰国旅游时学到的唯一一句). 凡事都有个循序渐进的过程, 本文依旧是在为后面 Zuul 分析做铺垫, 一口吃一个胖子, 往往容易消化不良. 希望笔者的良苦用心, 您能够明白, 当然更希望对您能有所帮助. 最后, 感谢您的支持!!! 祝进步, 2018 年 12 月 29 日, 祁琛.
来源: https://juejin.im/post/5c26e82f5188250918139074