SPRINGMVC源码5.0: DispatcherServlet

DispatcherServlet的逻辑处理

根据之前可以知道HttpServlet类中分别提供了相应的服务方法。他们是doDelete,doGet(),doPost(),doOptions(),doPut(),doTrace()。其根据请求的不同形式将程序引导至对应的函数进行处理。
在FrameworkServlet中

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   processRequest(request, response);
}

其实现是统一引导至processRequest中

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
//记录时间,用于计算web请求的处理时间
   long startTime = System.currentTimeMillis();
   Throwable failureCause = null;

   LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
   LocaleContext localeContext = buildLocaleContext(request);

   RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
   ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
   asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

   initContextHolders(request, localeContext, requestAttributes);

   try {
      doService(request, response);
   }
   catch (ServletException | IOException ex) {
      failureCause = ex;
      throw ex;
   }
   catch (Throwable ex) {
      failureCause = ex;
      throw new NestedServletException("Request processing failed", ex);
   }

   finally {
      resetContextHolders(request, previousLocaleContext, previousAttributes);
      if (requestAttributes != null) {
         requestAttributes.requestCompleted();
      }

      if (logger.isDebugEnabled()) {
         if (failureCause != null) {
            this.logger.debug("Could not complete request", failureCause);
         }
         else {
            if (asyncManager.isConcurrentHandlingStarted()) {
               logger.debug("Leaving response open for concurrent processing");
            }
            else {
               this.logger.debug("Successfully completed request");
            }
         }
      }

      publishRequestHandledEvent(request, response, startTime, failureCause);
   }
}

函数对请求的处理,把细节转移到了doService函数中实现。但是可以看到处理请求前后所做的准备与处理工作。

  • 为了保证当前request线程的LocaleContext以及RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性。
  • 根据当前request创建对应的LocaleContext和RequestAttributes,并绑定到当前线程。
  • 委托给doService方法进一步处理
  • 请求处理结束后恢复线程到初始状态
  • 请求处理结束后无论成功与否发布时间通知。(publishRequestHandledEvent)

doService

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
   if (logger.isDebugEnabled()) {
      String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
      logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
            " processing " + request.getMethod() + " request for [" + getRequestUri(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);
         }
      }
   }
}

doService中没有包括诸如寻找Handler并跳转页面之类的处理逻辑。但是相反的是准备工作,这些准备工作必不可少。
SPring将已经初始化的功能复制工具变量,如localeResolver,themeResolver等设置在request属性中,而这些属性会在接下来的处理中牌上用场。

doDispatch函数中,可以看到完整的处理请求处理过程。


protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
//如果是MultipartContent类型的request则转换request为MultipartHttpServletRequest类型的request。
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);
//根据request信息寻找对应的Handler
         // Determine handler for the current request.
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
//没有找到对应的handler则通过response反馈错误信息
            noHandlerFound(processedRequest, response);
            return;
         }

//根据当前handler寻找对应的HandlerAdapter
         // Determine handler adapter for the current request.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (logger.isDebugEnabled()) {
               logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
            }
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }

         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // Actually invoke the handler.
//真正的激活handler并返回视图
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

//视图名称转换应用于需要添加前后缀的情况
         applyDefaultViewName(processedRequest, mv);
//应用所有拦截器的postHandler方法
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
//在这个函数中。有一个处理if(mv!=null)下,如果在Handler的实例中返回了view,那么需要做页面逻辑的处理,render(mv,processedRequest,response)他是处理页面跳转的
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
//完成处理激活触发器
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}

doDispatch函数中展示了Spring请求处理所设计的主要逻辑,而我们之前设置在request中的各种辅助属性也都有派上用场。回顾一下逻辑处理的全过程。

MultipartContent类型的request处理

对于请求的处理,Spring首先考虑对于Multipart的处理,如果是MultipartContent类型的request,则转换request为MultipartHttpServletRequest类型的request。


protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
   if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
      if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
         logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
               "this typically results from an additional MultipartFilter in web.xml");
      }
      else if (hasMultipartException(request)) {
         logger.debug("Multipart resolution previously failed for current request - " +
               "skipping re-resolution for undisturbed error rendering");
      }
      else {
         try {
            return this.multipartResolver.resolveMultipart(request);
         }
         catch (MultipartException ex) {
            if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
               logger.debug("Multipart resolution failed for error dispatch", ex);
               // Keep processing error dispatch with regular request handle below
            }
            else {
               throw ex;
            }
         }
      }
   }
   // If not returned before: return original request.
   return request;
}

根据request信息寻找对于handler

Spring中最简单因素处理器配置如下

<bean id="simpleUrlMapping"
class = "org.Springframework.web.servlet.handler.SimpleUrlHandlerMapping:>
<property name="mapping">
<props>
<prop key="/userlist.html">userController</prop>
<props>
</peroperty>
</bean>

Spring加载过程中,会将类型SimpleUrlHandlerMapping的实例加载到this.handlerMappings中。按照常理推断,根据request提取对应的Handler,无非就是提取当前实例中的userControler,但是user Controller为继承自AbstractController类型实例,与HandlerExecutionChain并无任何关联。那么封装是怎么处理的。

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) {
      for (HandlerMapping hm : this.handlerMappings) {
         if (logger.isTraceEnabled()) {
            logger.trace(
                  "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
         }
         HandlerExecutionChain handler = hm.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}

在系统启动时,Spring会将所有的映射类型bean注册到this.handlerMappings变量中,所以此函数的目的就是遍历所有的HandlerMapping,并调用其getHandler方法进行封装处理,以SimpleUrlHandlerMapping为例查看其getHandler方法如下

AbstractHandlerMapping.class

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根据request获取对应的handler  
 Object handler = getHandlerInternal(request);
   if (handler == null) {
//如果没有对应request的handler则用默认handler
      handler = getDefaultHandler();
   }
   if (handler == null) {
      return null;
   }
   // Bean name or resolved handler?
   if (handler instanceof String) {
      String handlerName = (String) handler;
      handler = obtainApplicationContext().getBean(handlerName);
   }

   HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
   if (CorsUtils.isCorsRequest(request)) {
      CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
      CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
      CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
      executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
   }
   return executionChain;
}

函数会使用getHandlerInternal方法根据request信息获取对应的Handler。
SimpleUrlHandlerMapping为例,其根据Url找到匹配的Controller并返回,如果没找到则尝试去寻找配置中的默认处理器。如果查找的controller为String类型,那么意味返回的配置的bean名称,需要根据bean名称查找对应bean。最后还要通过getHandlerExecutionChain方法对返回的Handler进行封装,以保证满足返回类型的匹配。

  • 根据request查找对应的Handler

AbstractUrlHandlerMapping.class

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
  //截取用于匹配的url有效路径
   String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//根据路径寻找handler
   Object handler = lookupHandler(lookupPath, request);
   if (handler == null) {
      // We need to care for the default handler directly, since we need to
      // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
      Object rawHandler = null;
      if ("/".equals(lookupPath)) {
//如果请求的路径仅仅是"/",那么使用RootHandler进行处理
         rawHandler = getRootHandler();
      }
      if (rawHandler == null) {
       //无法找到handler则使用默认对应的bean  
rawHandler = getDefaultHandler();
      }
      if (rawHandler != null) {
         // Bean name or resolved handler?
         if (rawHandler instanceof String) {
            String handlerName = (String) rawHandler;
            rawHandler = obtainApplicationContext().getBean(handlerName);
         }
//模板方法
         validateHandler(rawHandler, request);
         handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
      }
   }
   if (handler != null && logger.isDebugEnabled()) {
      logger.debug("Mapping [" + lookupPath + "] to " + handler);
   }
   else if (handler == null && logger.isTraceEnabled()) {
      logger.trace("No handler mapping found for [" + lookupPath + "]");
   }
   return handler;
}

/**
 * Look up a handler instance for the given URL path.
 * <p>Supports direct matches, e.g. a registered "/test" matches "/test",
 * and various Ant-style pattern matches, e.g. a registered "/t*" matches
 * both "/test" and "/team". For details, see the AntPathMatcher class.
 * <p>Looks for the most exact pattern, where most exact is defined as
 * the longest path pattern.
 * @param urlPath the URL the bean is mapped to
 * @param request current HTTP request (to expose the path within the mapping to)
 * @return the associated handler instance, or {@code null} if not found
 * @see #exposePathWithinMapping
 * @see org.springframework.util.AntPathMatcher
 */
@Nullable
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
   // Direct match?直接匹配
   Object handler = this.handlerMap.get(urlPath);
   if (handler != null) {
      // Bean name or resolved handler?
      if (handler instanceof String) {
         String handlerName = (String) handler;
         handler = obtainApplicationContext().getBean(handlerName);
      }
      validateHandler(handler, request);
      return buildPathExposingHandler(handler, urlPath, urlPath, null);
   }

   // Pattern match?通配符
   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.isDebugEnabled()) {
         logger.debug("Matching patterns for request [" + urlPath + "] are " + 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.isDebugEnabled()) {
         logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
      }
      return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
   }

   // No handler found...
   return null;
}

根据URL获取对应Handler的匹配规则代码实现虽然很长,考虑了直接匹配和通配符两种情况。这里的buildPathExposingHandler函数,它将Handler封装成了HandlerExecutionChain类型。

protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
      String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {

   HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
   chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
   if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
      chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
   }
   return chain;
}

通过将Handler以参数形式传入,并构建HandlerExecutionChain类型实例,加入了两个拦截器。链处理机制,是Spring非常常用的处理反射,是AOP的重要组成部分,可以方便的对目标对象进行扩展及拦截,十分优秀的设计。

  • 加入拦截器到执行链

getHandlerExecutionChain函数是最主要的目的将配置中对应拦截器加入到执行链中。以保证这些拦截器可以有效地作用于目标对象。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
   HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
         (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

   String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
   for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
      if (interceptor instanceof MappedInterceptor) {
         MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
         if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
            chain.addInterceptor(mappedInterceptor.getInterceptor());
         }
      }
      else {
         chain.addInterceptor(interceptor);
      }
   }
   return chain;
}

没有找到对应Handler的错误处理

每个请求对应着一Handler,因为每个请求都会在后台有相应的逻辑对应,而逻辑对应的实现就是在Handler中,所以一旦遇到没找到Handler情况(可默认,如果默认未设置则为空),只能通过response向用户返回错误信息。

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
   if (pageNotFoundLogger.isWarnEnabled()) {
      pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
            "] in DispatcherServlet with name '" + getServletName() + "'");
   }
   if (this.throwExceptionIfNoHandlerFound) {
      throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
            new ServletServerHttpRequest(request).getHeaders());
   }
   else {
      response.sendError(HttpServletResponse.SC_NOT_FOUND);
   //也就是在这里返回404 response
    }
}

根据当前Handler寻找对应的HandlerAdapter

在WebApplicationContext的初始化过程中我们讨论了HandlerAdapters的初始化。在默认情况下普通的Web请求会交给SimpleControllerHandlerAdapter去处理。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
   if (this.handlerAdapters != null) {
      for (HandlerAdapter ha : this.handlerAdapters) {
         if (logger.isTraceEnabled()) {
            logger.trace("Testing handler adapter [" + ha + "]");
         }
         if (ha.supports(handler)) {
            return ha;
         }
      }
   }
   throw new ServletException("No adapter for handler [" + handler +
         "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

对于获取适配器,无非就是遍历所有适配器选择合适的适配器返回他。而某个适配器是否适用于当前的Handler逻辑被封装在具体适配器中。

public boolean supports(Object handler) {
   return (handler instanceof Controller);
}

到此,SimpleContollerHandlerAdapter就是用于处理普通的Web请求的。而对于SpringMVC而言,我们把逻辑封装至Controller的子类中,如UserController就是继承自AbstractController,而AbstractController实现Controller接口

缓存处理

Last-Modified缓存机制。

  1. 在客户端第一次输入URL时,服务端返回内容和状态码200,表示请求成功。同时添加Last-Modified的相应头,表示此文件咋i服务器上的最后更新时间
  2. 客户端第二次请求该URL时,客户端会向服务器发送请求头,If-Modifed-Since,询问服务器该事件后当前请求内容是否被修改过,如果没有变化,则304状态码。(只要响应头内容空,这样就节省了网络带宽)
    Spring提供对Last-Modified机制的支持,只需要实现LastModified接口。

HandlerInterceptor的处理

ServletAPI定义的servlet过滤器可以在servlet处理每个Web请求的前后分别对他进行前置处理和后置处理。
如此,Spring允许你通过拦截Web请求,进行前置和后置处理。处理拦截是在Spring的Web应用程序上下文中配置的。因此可以利用各种容器特性,并引用容器声明的任何bean。

  • 处理拦截是针对特殊的处理程序映射进行注册的。因此它只拦截通过这些处理程序映射的请求。
  • 每个处理拦截器必须实现HandlerInterceptor接口。
  • 其接口包含了3个需要你实现的回调方法,preHandle(),postHandle(),afterHandle()。
  • 一个和一二分别是在处理程序处理请求之前和之后被调用的。第二个方法还允许你返回访问的ModelAndView对象,因此可以在它里面操作模型属性。最后一个方法实在所有请求处理完成之后被调用的(如视图呈现后)。

逻辑处理

对于逻辑处理其实是通过适配器中转调用Handler并返回视图的。

mv = ha.handle(processedRequest,response,mappedHandler.getHandler());

对于普通Web请求,Spring默认SimpleControllerHandlerAdapter类进行处理。
SimpleControllerHandlerAdapter.class

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {

   return ((Controller) handler).handleRequest(request, response);
}

AbstractController.class

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
      throws Exception {

   if (HttpMethod.OPTIONS.matches(request.getMethod())) {
      response.setHeader("Allow", getAllowHeader());
      return null;
   }

   // Delegate to WebContentGenerator for checking and preparing.
   checkRequest(request);
   prepareResponse(response);

   // Execute handleRequestInternal in synchronized block if required.
//如果需要在session中同步
   if (this.synchronizeOnSession) {
      HttpSession session = request.getSession(false);
      if (session != null) {
         Object mutex = WebUtils.getSessionMutex(session);
         synchronized (mutex) {
//调用用户的逻辑
            return handleRequestInternal(request, response);
         }
      }
   }
//调用用户逻辑
   return handleRequestInternal(request, response);
}

异常视图的处理

系统允许过程出现异常,而不希望中断该服务,告知错误和原因。Spring异常中断机制帮我们完成了这个工作。
其实这里Spring的主要工作就是将逻辑引导至HandlerExceptionResolver类的resolveException方法。而HandlerExceptionResolver的使用,在WebApplicationContext初始化便说过了

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
      @Nullable Object handler, Exception ex) throws Exception {

   // Success and error responses may use different content types
   request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

   // Check registered HandlerExceptionResolvers...
   ModelAndView exMv = null;
   if (this.handlerExceptionResolvers != null) {
      for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
         exMv = resolver.resolveException(request, response, handler, ex);
         if (exMv != null) {
            break;
         }
      }
   }
   if (exMv != null) {
      if (exMv.isEmpty()) {
         request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
         return null;
      }
      // We might still need view name translation for a plain error model...
      if (!exMv.hasView()) {
         String defaultViewName = getDefaultViewName(request);
         if (defaultViewName != null) {
            exMv.setViewName(defaultViewName);
         }
      }
      if (logger.isTraceEnabled()) {
         logger.trace("Using resolved error view: " + exMv, ex);
      }
      if (logger.isDebugEnabled()) {
         logger.debug("Using resolved error view: " + exMv);
      }
      WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
      return exMv;
   }

   throw ex;
}

根据视图跳转页面

无论是系统还是一个站点,最重要的就是与用户进行交互。用户操作系统后无论下 发的命令成功与否都需要给用户一个反馈,以便于用户进行下一步的判断,所以在逻辑处理的最后一定会涉及一个页面跳转的问题。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   // Determine locale for request and apply it to the response.
   Locale locale =
         (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
   response.setLocale(locale);

   View view;
   String viewName = mv.getViewName();
   if (viewName != null) {
      // We need to resolve the view name.
      view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
      if (view == null) {
         throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
               "' in servlet with name '" + getServletName() + "'");
      }
   }
   else {
      // No need to lookup: the ModelAndView object contains the actual View object.
      view = mv.getView();
      if (view == null) {
         throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
               "View object in servlet with name '" + getServletName() + "'");
      }
   }

   // Delegate to the View object for rendering.
   if (logger.isDebugEnabled()) {
      logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
   }
   try {
      if (mv.getStatus() != null) {
         response.setStatus(mv.getStatus().value());
      }
      view.render(mv.getModelInternal(), request, response);
   }
   catch (Exception ex) {
      if (logger.isDebugEnabled()) {
         logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
               getServletName() + "'", ex);
      }
      throw ex;
   }
}

解析视图名称

DispatcherServlet会根据ModelAndView选择合适的视图进行渲染,而此功能会在resolveViewName函数中完成。

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
      Locale locale, HttpServletRequest request) throws Exception {

   if (this.viewResolvers != null) {
      for (ViewResolver viewResolver : this.viewResolvers) {
         View view = viewResolver.resolveViewName(viewName, locale);
         if (view != null) {
            return view;
         }
      }
   }
   return null;
}

我们以InternalResourceViewResolver为例分析ViewResolver逻辑的鸡西过程,其中resolveViewName函数的实现在其父类AbstractCachingViewResolver中完成的。

public View resolveViewName(String viewName, Locale locale) throws Exception {
   if (!isCache()) {
//不存在缓存的情况下直接创建视图
      return createView(viewName, locale);
   }
   else {
//直接从缓存中获取
      Object cacheKey = getCacheKey(viewName, locale);
      View view = this.viewAccessCache.get(cacheKey);
      if (view == null) {
         synchronized (this.viewCreationCache) {
            view = this.viewCreationCache.get(cacheKey);
            if (view == null) {
               // Ask the subclass to create the View object.
               view = createView(viewName, locale);
               if (view == null && this.cacheUnresolved) {
                  view = UNRESOLVED_VIEW;
               }
               if (view != null) {
                  this.viewAccessCache.put(cacheKey, view);
                  this.viewCreationCache.put(cacheKey, view);
                  if (logger.isTraceEnabled()) {
                     logger.trace("Cached view [" + cacheKey + "]");
                  }
               }
            }
         }
      }
      return (view != UNRESOLVED_VIEW ? view : null);
   }
}

在父类UrlBasedViewResolver中重写了createView函数

protected View createView(String viewName, Locale locale) throws Exception {
   // If this resolver is not supposed to handle the given view,
   // return null to pass on to the next resolver in the chain.
//如果当前解析器不支持当前解析器如viewName为空等情况   
if (!canHandle(viewName, locale)) {
      return null;
   }
//处理redirect:xxx的情况
   // Check for special "redirect:" prefix.
   if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
      String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
      RedirectView view = new RedirectView(redirectUrl,
            isRedirectContextRelative(), isRedirectHttp10Compatible());
      String[] hosts = getRedirectHosts();
      if (hosts != null) {
         view.setHosts(hosts);
      }
      return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
   }
//forward:xx
   // Check for special "forward:" prefix.
   if (viewName.startsWith(FORWARD_URL_PREFIX)) {
      String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
      return new InternalResourceView(forwardUrl);
   }

   // Else fall back to superclass implementation: calling loadView.
   return super.createView(viewName, locale);
}
protected View createView(String viewName, Locale locale) throws Exception {
   return loadView(viewName, locale);
}
protected View loadView(String viewName, Locale locale) throws Exception {
   AbstractUrlBasedView view = buildView(viewName);
   View result = applyLifecycleMethods(viewName, view);
   return (view.checkResource(locale) ? result : null);
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
   Class<?> viewClass = getViewClass();
   Assert.state(viewClass != null, "No view class");

   AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
   view.setUrl(getPrefix() + viewName + getSuffix());

   String contentType = getContentType();
   if (contentType != null) {
      view.setContentType(contentType);
   }

   view.setRequestContextAttribute(getRequestContextAttribute());
   view.setAttributesMap(getAttributesMap());

   Boolean exposePathVariables = getExposePathVariables();
   if (exposePathVariables != null) {
      view.setExposePathVariables(exposePathVariables);
   }
   Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
   if (exposeContextBeansAsAttributes != null) {
      view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
   }
   String[] exposedContextBeanNames = getExposedContextBeanNames();
   if (exposedContextBeanNames != null) {
      view.setExposedContextBeanNames(exposedContextBeanNames);
   }

   return view;
}

通过以上代码,可以发现对于InternalResourceViewResolver所提供的解析功能主要考虑到了

  • 基于效率的考虑,提供了缓存的支持
  • 提供了对redirectLxx和forward:xx 前缀的支持
  • 添加了前缀及后缀,并向View中加入了必须的属性设置

页面跳转

当通过了viewName解析到对应的View后,就可以进一步处理跳转逻辑

public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
      HttpServletResponse response) throws Exception {

   if (logger.isTraceEnabled()) {
      logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
         " and static attributes " + this.staticAttributes);
   }

   Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
   prepareResponse(request, response);
   renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

对于ModelView的使用,可以将一些属性直接放入其中然后在页面直接通过JSTL语法or
原始的request获取,这是一个很方便也是很神奇的功能,无非就是把我们要用到的属性放入request中,以便在其他地方可以直接调用,而解析这些属性的工作就是在createMergedOutputModel函数中完成的。
AbstractView.class

protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
      HttpServletRequest request, HttpServletResponse response) {

   @SuppressWarnings("unchecked")
   Map<String, Object> pathVars = (this.exposePathVariables ?
         (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

   // Consolidate static and dynamic model attributes.
   int size = this.staticAttributes.size();
   size += (model != null ? model.size() : 0);
   size += (pathVars != null ? pathVars.size() : 0);

   Map<String, Object> mergedModel = new LinkedHashMap<>(size);
   mergedModel.putAll(this.staticAttributes);
   if (pathVars != null) {
      mergedModel.putAll(pathVars);
   }
   if (model != null) {
      mergedModel.putAll(model);
   }

   // Expose RequestContext?
   if (this.requestContextAttribute != null) {
      mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
   }

   return mergedModel;
}

//页面处理跳转
InternalResourceView.class

protected void renderMergedOutputModel(
      Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

   // Expose the model object as request attributes.
   exposeModelAsRequestAttributes(model, request);

   // Expose helpers as request attributes, if any.
   exposeHelpers(request);

   // Determine the path for the request dispatcher.
   String dispatcherPath = prepareForRendering(request, response);

   // Obtain a RequestDispatcher for the target resource (typically a JSP).
   RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
   if (rd == null) {
      throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
            "]: Check that the corresponding file exists within your web application archive!");
   }

   // If already included or response already committed, perform include, else forward.
   if (useInclude(request, response)) {
      response.setContentType(getContentType());
      if (logger.isDebugEnabled()) {
         logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
      }
      rd.include(request, response);
   }

   else {
      // Note: The forwarded resource is supposed to determine the content type itself.
      if (logger.isDebugEnabled()) {
         logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
      }
      rd.forward(request, response);
   }
}

整体小结

  • web.xml先配置ContextLoaderListener-----获取param----->ServletContextListener

  • ServletContextListener 初始化WebApplicationContext实例并存至ServletContext中

  • ContextLoaderListener为辅助功能,用于创建WebApplicationContext类型实例,真正逻辑实现在DispatcherServlet

  • ContextLoader 静态代码块,读取属性文件,提取将要实现的WebApplicationContext接口实现类

  • 接下来

  • DispatcherServlet ->FrameworkServlet ->HttpServletBean,ApplicationContextAware
    HttpServletBean -> Httpservlet ->重写其init方法

  • inti方法->servletBean初始化->FrameworkServlet->覆盖initServletBean(对WebApplicationContext进一步初始化)—关键初始化–>initWebApplicationContext()

  • initWebApplicationContext()-----寻找or创建对应的WebApplicationContext实例 ------调用onRefresh()进行配置文件加载

  • onRefresh刷新Spring在Web功能实现的全局变量,如果没有手动配置bean--------------------- DispatcherServlet静态代码块初始化本身对应的各种init**属性

  • 这一章节

  • FrameworkServlet----重写doGet() doPost() 方法------>processRequest函数-------->其中的doService初始化------->其中的doDispatch根据request寻找handler并处理页面跳转,并加入拦截器到执行链

  • 细化doDispatch(根据找Handler)----->getHandler----->getHandlerInternal----->lookupHandler----->buildPathExposingHandler ----------/doDispath-----getHandler----------(配置中的对应拦截器加入到执行链)------>getHandlerExectuinChain

  • 根据当前Handler寻找对应HandlerAdapter/没找到对应的Handler错误处理/HandlerInterceptor前后置处理器/逻辑处理/视图异常的处理

  • 根据视图跳转页面:render—解析视图名称–>resolveViewName选择合适的视图渲染—>resolveViewName–>createView
    render—页面跳转—>同过viewName解析到对于的View后进行逻辑跳转—>render------->解析属性/存放属性Map --createMergedOutputModel---->放置属性/页面跳转renderMergedOutputModel

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值