美文网首页
(三)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