美文网首页
(三)zuul源码-路径如何映射到对应的处理器

(三)zuul源码-路径如何映射到对应的处理器

作者: guessguess | 来源:发表于2021-10-30 16:27 被阅读0次

    在用zuul的时候,不太清楚是如何完成路由的。为什么输入指定路径会跳转到某个服务。所以在后面有空看到源码的时候暂时记录一下。

    在讲源码之前有部分关于springmvc的内容。之前这块也没有专门去看。但是有部分接口很重要。有必要先看一下。

    重要的接口以及结构

    HandlerInterceptor

    public interface HandlerInterceptor {
        default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
    
            return true;
        }
    
        default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                @Nullable ModelAndView modelAndView) throws Exception {
        }
    
        default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                @Nullable Exception ex) throws Exception {
        }
    
    }
    

    这个类就是对于请求的一个拦截器。
    结构如下,对执行前,执行后,操作完成后,做相关操作。


    image.png

    HandlerExecutionChain

    HandlerExecutionChain其实就是一个处理器执行链。其中包括俩部分。
    拦截器链,以及对应具体的handler(其实就是请求具体对应的执行方法)

    public class HandlerExecutionChain {
        private final Object handler;
    
        @Nullable
        private HandlerInterceptor[] interceptors;
    
        @Nullable
        private List<HandlerInterceptor> interceptorList;
    
        private int interceptorIndex = -1;
    }
    
    结构图

    HandlerMapping

    一个url的处理包括,拦截器的处理,以及自身的业务逻辑,HandlerExecutionChain恰好对此做了封装。
    所以HandlerMapping其实就是url与执行器的映射。

    public interface HandlerMapping {
        @Nullable
        HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
    }
    

    那么如何通过url来找到对应的handler呢?

    在DispatcherServlet中有一个很重要的方法,代码如下

    @SuppressWarnings("serial")
    public class DispatcherServlet extends FrameworkServlet {
        @Nullable
        private List<HandlerMapping> handlerMappings;
        @Nullable
        protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
            if (this.handlerMappings != null) {
                for (HandlerMapping mapping : this.handlerMappings) {
                    HandlerExecutionChain handler = mapping.getHandler(request);
                    if (handler != null) {
                        return handler;
                    }
                }
            }
            return null;
        }
    }
    

    从上面代码来看还是比较简单的。DispatcherServlet有一个成员变量handlerMappings用于保存请求与处理器的映射。获取处理器也是通过HandlerMapping的getHandler方法。
    zuul在运行的时候,有几个类,debug的结果如下。


    HandlerMapping的debug过程

    由于此处我们使用的是Zuul组件。所以需要关心怎么去找到映射的话,直接看ZuulHandlerMapping即可。

    ZuulHandlerMapping

    结构如下

    从结构上看,ZuulHandlerMapping是url与处理器的映射。
    那么究竟如何找?
    从结构上看,ZuulHandlerMapping的父类有3个。其中查询处理器的基本实现在父类中已经实现了。

    父类中对于查询处理器的实现,代码如下

    public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
    
        @Nullable
        protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
            ...基本的实现。    
        }
    
        @Override
        @Nullable
        protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
            将请求解析成字符串路径
            String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
            模板模式,如果子类不对子方法进行覆写,默认就是以下的执行流程。
            lookupHandler方法就是查找处理器的核心逻辑。
            Object handler = lookupHandler(lookupPath, request);
            。。。省略部分代码
            return handler;
        }
    
    }
    

    其中ZuulHandlerMapping对lookupHandler进行了覆写。所以直接看ZuulHandlerMapping对于lookupHandler的实现即可。查找处理器的步骤分为两步。一开始得先注册处理器,然后再查找处理器。

    对于处理器的注册

    public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
        private volatile boolean dirty = true;
        @Override
        protected Object lookupHandler(String urlPath, HttpServletRequest request)
                throws Exception {
            ...省略部分代码
            默认为ture,由于volatile不能保证原子性,所以需要结合同步锁使用。
            if (this.dirty) {
                synchronized (this) {
                    if (this.dirty) {
                        registerHandlers();
                        this.dirty = false;
                    }
                }
            }
            return super.lookupHandler(urlPath, request);
        }
    }
    

    从上面的代码来看,我们并没有看到具体如何查找,但是有一个注册处理器的方法,实现如下。

    public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
        private final ZuulController zuul;
        private void registerHandlers() {
            Collection<Route> routes = this.routeLocator.getRoutes();
            if (routes.isEmpty()) {
                this.logger.warn("No routes found from RouteLocator");
            }
            else {
                其实就是将路由规则转成对应的路径,同时为路径设置对应的处理器。
                处理器就是ZuulController.
                for (Route route : routes) {
                    registerHandler(route.getFullPath(), this.zuul);
                }
            }
        }
    }
    
    public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
        private final Map<String, Object> handlerMap = new LinkedHashMap<>();
        protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
            Assert.notNull(urlPath, "URL path must not be null");
            Assert.notNull(handler, "Handler object must not be null");
            Object resolvedHandler = handler;
            ...省略代码
            Object mappedHandler = this.handlerMap.get(urlPath);
            一个请求路径只能有一个对应的处理器,否则会抛异常
            if (mappedHandler != null) {
                if (mappedHandler != resolvedHandler) {
                    throw new IllegalStateException(
                            "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
                            "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
                }
            }
            else {
                if (urlPath.equals("/")) {
                      。。。省略
                }
                else if (urlPath.equals("/*")) {
                      。。。省略
                }
                else {
                    将路径与处理器保存在成员变量中
                    this.handlerMap.put(urlPath, resolvedHandler);
                }
            }
        }
    }
    

    再看看Debug的信息


    debug信息

    debug的信息里Route这个结构其实是我们配置文件里面写的一个路由规则,就是具体什么路径映射到什么服务,这个id就是注册中心里的application-name.

    如何根据请求路径查找处理器?

    在前面我们已经知道,ZuulHandlerMapping复写了lookupHandler方法。
    会首先将请求与handler的映射进行保存。保存在AbstractUrlHandlerMapping的成员变量handlerMap 中。
    url就是根据路由规则生成的全路径(/服务id/请求路径),而处理器就是ZuulController.那么相信大家也知道后面该如何找到处理器了。就是通过这个成员变量。

    public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
        private final Map<String, Object> handlerMap = new LinkedHashMap<>();
    

    那么究竟是如何实现的?还是回到源码

    public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
        @Override
        protected Object lookupHandler(String urlPath, HttpServletRequest request)
                throws Exception {
            省略代码
            if (this.dirty) {
                synchronized (this) {
                    if (this.dirty) {
                        注册请求与处理器
                        registerHandlers();
                        this.dirty = false;
                    }
                }
            }
            调用父类的lookupHandler方法
            return super.lookupHandler(urlPath, request);
        }
    }
    

    可以看到,在注册完处理器之后,又调用了父类的方法。具体代码如下

    public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
        private final Map<String, Object> handlerMap = new LinkedHashMap<>();
        @Nullable
        protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
            这里去获取处理器有俩种情况
            Object handler = this.handlerMap.get(urlPath);
            // 1.刚刚好路径是有对应的处理器的,比如在配置文件的路由规则中,写定的是具体的路由规则,而不是xx/**这种,确实是可以获取到对应处理器。
            if (handler != null) {
                。。。省略部分代码
                return buildPathExposingHandler(handler, urlPath, urlPath, null);
            }
              //2.那如果路由规则是xx/**这种呢?
            就会进行路径匹配,将handlerMap遍历一遍,看看有没有匹配的处理器。
            List<String> matchingPatterns = new ArrayList<>();
            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;
            Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
            if (!matchingPatterns.isEmpty()) {
                matchingPatterns.sort(patternComparator);
                if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
                    logger.trace("Matching patterns " + matchingPatterns);
                }
                bestMatch = 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 + "]");
                    }
                }
                // Bean name or resolved handler?
                if (handler instanceof String) {
                    String handlerName = (String) handler;
                    handler = obtainApplicationContext().getBean(handlerName);
                }
                validateHandler(handler, request);
                String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
    
                // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
                // for all of them
                Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
                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.isTraceEnabled() && uriTemplateVariables.size() > 0) {
                    logger.trace("URI variables " + uriTemplateVariables);
                }
                return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
            }
    
            没有匹配的处理器,则返回Null.
            return null;
        }
    }
    

    看看查询到的处理器。可以看到对应的handler就是ZuulController.


    debug结果
    查找的原理图

    相关文章

      网友评论

          本文标题:(三)zuul源码-路径如何映射到对应的处理器

          本文链接:https://www.haomeiwen.com/subject/zztyaltx.html