1. 程式人生 > >Spring 源碼解析 —— DispatcherServlet

Spring 源碼解析 —— DispatcherServlet

nag 模式 實現類 framework 源碼解析 throws dos 實現 不同

版本: 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

Spring 源碼解析 —— DispatcherServlet