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