Spring 源码解析 —— DispatcherServlet

版本: SpringMVC 5.14

Servlet 是 Tomcat 的入口,而 DispatcherServlet 则是 Spring MVC 的入口,主要负责调度功能。

既然是 Servlet 的实现类,所以从实现的接口看起最好,而 Servlet 最重要的方法就是 doService,就从 DispatcherServlet 的 doService 看起。

doService 方法很复杂,下面会具体解析,这里说一下 Spring 代码的一种主要思路:

首先,他有前置的一些条件,比如验证参数合法性,Spring 会写在最前面,这里就是 logRequest 记录日志;

其次,开始执行该方法真正要做的事情;

最后,会做一些后置的事情,在有些时候就是资源清理。

这个思路在 Spring 中很常见,代码写多的人也会这样做,对于新人的建议就是,写代码先验证、在执行、后清理。不要一边验证、一边执行、一边清理,这样代码看起来很乱。

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        logRequest(request);

        // Keep a snapshot of the request attributes in case of an include,
        // to be able to restore the original attributes after the include.
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }

        // Make framework objects available to handlers and view objects.
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

        if (this.flashMapManager != null) {
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            if (inputFlashMap != null) {
                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
            }
            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
        }

        try {
            doDispatch(request, response);
        }
        finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // Restore the original attribute snapshot, in case of an include.
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }
    }

首先,logRequest 记录了请求日志。我用的是 Spring 5.14,可以看到大量方法使用了 Java 8 新增的 Stream 相关技术。函数式编程是一大趋势,需要掌握。这里记录了请求相关的参数。

private void logRequest(HttpServletRequest request) {
        LogFormatUtils.traceDebug(logger, traceOn -> {
            String params;
            if (isEnableLoggingRequestDetails()) {
                params = request.getParameterMap().entrySet().stream()
                        .map(entry -> entry.getKey() + ":" + Arrays.toString(entry.getValue()))
                        .collect(Collectors.joining(", "));
            }
            else {
                params = (request.getParameterMap().isEmpty() ? "" :  "masked");
            }

            String query = StringUtils.isEmpty(request.getQueryString()) ? "" : "?" + request.getQueryString();
            String dispatchType = (!request.getDispatcherType().equals(DispatcherType.REQUEST) ?
                    "\"" + request.getDispatcherType().name() + "\" dispatch for " : "");
            String message = (dispatchType + request.getMethod() + " \"" + getRequestUri(request) +
                    query + "\", parameters={" + params + "}");

            if (traceOn) {
                List<String> values = Collections.list(request.getHeaderNames());
                String headers = values.size() > 0 ? "masked" : "";
                if (isEnableLoggingRequestDetails()) {
                    headers = values.stream().map(name -> name + ":" + Collections.list(request.getHeaders(name)))
                            .collect(Collectors.joining(", "));
                }
                return message + ", headers={" + headers + "} in DispatcherServlet '" + getServletName() + "'";
            }
            else {
                return message;
            }
        });
    }

后面有一段代码是有关 JSP include 命令的,这里就不多说了,因为现在大多数地方都不用 JSP 了。

        // Keep a snapshot of the request attributes in case of an include,
        // to be able to restore the original attributes after the include.
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }

后面会看到在 request 上设置了很多属性,这里需要学习的一点就是,Spring 相关属性的名字都是以常量方式定义的,这么做的好处是,假如你很多地方用了一个值,这时候想修改该值,只需要修改常量的值即可;否则你需要每处都修改该值,容易出错又麻烦。

还有,这集 localResolver 等等,他都是以接口定义的。这里使用了策略模式,就比如 localResolver,这样各种业务就可以有不同的实现。这么做的原因,我举个不太恰当的例子:假如我是中国人,现在解析到请求属于台湾,那我觉得这个 locale 可以定义为中国;可是 X 独分子看到这段代码肯定不这么认为,这个 locale 属于台湾,他们就可以每个人自定一套 localeResolver。策略模式的有点就是,不同场景有不同的实现,这也是面向对象的灵活之处。后面你会发现很多地方都在使用这个技巧。

        // Make framework objects available to handlers and view objects.
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

下面的 flash 就不多说了,也是 JSP 的。

        if (this.flashMapManager != null) {
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            if (inputFlashMap != null) {
                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
            }
            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
        }

下面的 doDispatch 才是真正的“分发”。

try {
            doDispatch(request, response);
        }
        finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // Restore the original attribute snapshot, in case of an include.
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }

todo

猜你喜欢

转载自www.cnblogs.com/Piers/p/10404136.html