• spring源码学习之springMVC(二)


    接着上一篇。继续来看springMVC中最和我们开发中接近的一部分内容:

    DispatcherServlet的逻辑处理

    作者写到在DispatcherServlet类中存在doGet、doPost之类的方法,但是在我查看的这个spring版本中,并不是在这个类中,而是在其父类FrameworkServlet中,从FrameworkServlet开始看起!

    org.springframework.web.servlet.FrameworkServlet类中:

     1 @Override
     2 protected final void doGet(HttpServletRequest request, HttpServletResponse response)
     3         throws ServletException, IOException {
     4 
     5     processRequest(request, response);
     6 }
     7 
     8 @Override
     9 protected final void doPost(HttpServletRequest request, HttpServletResponse response)
    10         throws ServletException, IOException {
    11 
    12     processRequest(request, response);
    13 }
    14 
    15 // 对于不同的方法,spring并没有做特殊处理,而是统一将程序再一次地引导至processRequest(request, response);中
    16 org.springframework.web.servlet.FrameworkServlet类中
    17 protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    18         throws ServletException, IOException {
    19 
    20     // 记录当前时间,用于计算web请求的处理时间
    21     long startTime = System.currentTimeMillis();
    22     Throwable failureCause = null;
    23 
    24     LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    25     LocaleContext localeContext = buildLocaleContext(request);
    26 
    27     RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    28     ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    29 
    30     WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    31     asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    32 
    33     initContextHolders(request, localeContext, requestAttributes);
    34 
    35     try {
    36         doService(request, response);
    37     }
    38     catch (ServletException | IOException ex) {
    39         failureCause = ex;
    40         throw ex;
    41     }
    42     catch (Throwable ex) {
    43         failureCause = ex;
    44         throw new NestedServletException("Request processing failed", ex);
    45     }
    46 
    47     finally {
    48         resetContextHolders(request, previousLocaleContext, previousAttributes);
    49         if (requestAttributes != null) {
    50             requestAttributes.requestCompleted();
    51         }
    52 
    53         if (logger.isDebugEnabled()) {
    54             if (failureCause != null) {
    55                 this.logger.debug("Could not complete request", failureCause);
    56             }
    57             else {
    58                 if (asyncManager.isConcurrentHandlingStarted()) {
    59                     logger.debug("Leaving response open for concurrent processing");
    60                 }
    61                 else {
    62                     this.logger.debug("Successfully completed request");
    63                 }
    64             }
    65         }
    66 
    67         publishRequestHandledEvent(request, response, startTime, failureCause);
    68     }
    69 }

    函数中已经开始了对请求的处理,虽然把细节转移到了doService函数中实现,但是我们不难看出处理前后所做的工作
    1、为了保证当前线程的LocaleContext、RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性
    2、根据当前request创建对应的LocaleContext、RequestAttributes并绑定到当前线程上
    3、委托给doService方法进一步处理
    4、请求处理结束后恢复线程到原始状态
    5、请求处理结束后无论成功与否发布事件成功通知

    继续看org.springframework.web.servlet.DispatcherServlet.doService(HttpServletRequest, HttpServletResponse)

     1 @Override
     2 protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
     3     if (logger.isDebugEnabled()) {
     4         String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
     5         logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
     6                 " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
     7     }
     8 
     9     // Keep a snapshot of the request attributes in case of an include,
    10     // to be able to restore the original attributes after the include.
    11     Map<String, Object> attributesSnapshot = null;
    12     if (WebUtils.isIncludeRequest(request)) {
    13         attributesSnapshot = new HashMap<>();
    14         Enumeration<?> attrNames = request.getAttributeNames();
    15         while (attrNames.hasMoreElements()) {
    16             String attrName = (String) attrNames.nextElement();
    17             if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
    18                 attributesSnapshot.put(attrName, request.getAttribute(attrName));
    19             }
    20         }
    21     }
    22 
    23     // Make framework objects available to handlers and view objects.
    24     request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    25     request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    26     request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    27     request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
    28 
    29     if (this.flashMapManager != null) {
    30         FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    31         if (inputFlashMap != null) {
    32             request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    33         }
    34         request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    35         request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    36     }
    37 
    38     try {
    39         doDispatch(request, response);
    40     }
    41     finally {
    42         if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    43             // Restore the original attribute snapshot, in case of an include.
    44             if (attributesSnapshot != null) {
    45                 restoreAttributesAfterInclude(request, attributesSnapshot);
    46             }
    47         }
    48     }
    49 }

    spring将已经初始化的功能辅助工具变量,比如,localeResolver、themeResolver、themeSource等设置在request属性中,看一下处理的方法:
    org.springframework.web.servlet.DispatcherServlet.doDispatch(HttpServletRequest, HttpServletResponse):

    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 {
                // 如果是MultipartContext类型的request则转换request为MultipartHttpServletRequest类型的
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
    
                // Determine handler for the current request.
                // 根据request信息寻找对应的handler
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    // 如果没有找到handler,则通过response反馈错误信息
                    noHandlerFound(processedRequest, response);
                    return;
                }
    
                // Determine handler adapter for the current request.
                // 根据当前的Handler寻找对应的HandlerAdapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
                // Process last-modified header, if supported by the handler.
                // 如果当前handler支持last-modified头处理
                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;
                    }
                }
    
                // 拦截器的preHandler方法的调用
                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);
                // 应用所有拦截器的postHandle方法
                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);
            }
            // 处理异常视图,根据视图来跳转页面
            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请求处理所涉及的主要逻辑,下面是逻辑处理全过程的详细分析:

    1、MultipartContext类型的request处理

    对于请求的处理,spring首先考虑的是对于Multipart的处理,如果是MultipartContext类型的request请求,则将request转化成MultipartHttpServletRequest类型

    org.springframework.web.servlet.DispatcherServlet.checkMultipart(HttpServletRequest)中:

     1 protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
     2     if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
     3         if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
     4             logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
     5                     "this typically results from an additional MultipartFilter in web.xml");
     6         }
     7         else if (hasMultipartException(request)) {
     8             logger.debug("Multipart resolution previously failed for current request - " +
     9                     "skipping re-resolution for undisturbed error rendering");
    10         }
    11         else {
    12             try {
    13                 return this.multipartResolver.resolveMultipart(request);
    14             }
    15             catch (MultipartException ex) {
    16                 if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
    17                     logger.debug("Multipart resolution failed for error dispatch", ex);
    18                     // Keep processing error dispatch with regular request handle below
    19                 }
    20                 else {
    21                     throw ex;
    22                 }
    23             }
    24         }
    25     }
    26     // If not returned before: return original request.
    27     return request;
    28 }

    2、根据request信息查找对应的Handler

    在spring中最简单的映射处理器配置如下:

    1 <bean id="simpleUrlHandlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    2     <property name="mappings">
    3         <props>
    4             <prop key="/userlist.htm">userController</prop>
    5         </props>
    6     </property>
    7 </bean>

    在spring加载的过程中,spring会将类型为SimpleUrlHandlerMapping的实例加载到this.handlerMappings中,按照常理推断,根据request提取对应的Handler,无非就是提取当前实例中的userController,但是userController为继承自AbstractController类型实例,与HandlerExecutionChain并无任何关联,看下这一步如何封装的:

     1 @Nullable
     2 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
     3     if (this.handlerMappings != null) {
     4         for (HandlerMapping hm : this.handlerMappings) {
     5             if (logger.isTraceEnabled()) {
     6                 logger.trace(
     7                         "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
     8             }
     9             HandlerExecutionChain handler = hm.getHandler(request);
    10             if (handler != null) {
    11                 return handler;
    12             }
    13         }
    14     }
    15     return null;
    16 }

    getHandler方法的实现是在org.springframework.web.servlet.handler.AbstractHandlerMapping类中:
    spring 5.0 的版本和作者的版本不一样,这个逻辑处理是在统一的AbstractHandlerMapping抽象出来的类中,

     1 @Override
     2 @Nullable
     3 public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
     4     // 根据request获取对应的Handler
     5     Object handler = getHandlerInternal(request);
     6     if (handler == null) {
     7         // 如果没有找到对应的handler 则使用默认的
     8         handler = getDefaultHandler();
     9     }
    10     if (handler == null) {
    11         // 默认的也没有,则返回null
    12         return null;
    13     }
    14     // Bean name or resolved handler?
    15     if (handler instanceof String) {
    16         String handlerName = (String) handler;
    17         handler = obtainApplicationContext().getBean(handlerName);
    18     }
    19     // 加入拦截器到执行链
    20     HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    21     if (CorsUtils.isCorsRequest(request)) {
    22         CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
    23         CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
    24         CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
    25         executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    26     }
    27     return executionChain;
    28 }

    作者推测此方法提供的功能就是根据URL 找到匹配的Controller,最后通过getHandlerExecutionChain对返回的Handler进行封装,详细分析一下这个方法:

    (1)根据request获取对应的Handler
    org.springframework.web.servlet.handler.AbstractUrlHandlerMapping类中

      1 @Override
      2 @Nullable
      3 protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
      4     // 截取用于匹配的URL路径
      5     String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
      6     // 根据路径寻找handler
      7     Object handler = lookupHandler(lookupPath, request);
      8     if (handler == null) {
      9         // We need to care for the default handler directly, since we need to
     10         // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
     11         Object rawHandler = null;
     12         if ("/".equals(lookupPath)) {
     13             // 如果请求的路径仅仅是"/",那么使用RootHandler处理
     14             rawHandler = getRootHandler();
     15         }
     16         if (rawHandler == null) {
     17             // 无法使用handler 则使用默认的handler
     18             rawHandler = getDefaultHandler();
     19         }
     20         if (rawHandler != null) {
     21             // Bean name or resolved handler?
     22             if (rawHandler instanceof String) {
     23                 // 根据beanName获取对应的bean
     24                 String handlerName = (String) rawHandler;
     25                 rawHandler = obtainApplicationContext().getBean(handlerName);
     26             }
     27             validateHandler(rawHandler, request);
     28             // 模板方法
     29             handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
     30         }
     31     }
     32     if (handler != null && logger.isDebugEnabled()) {
     33         logger.debug("Mapping [" + lookupPath + "] to " + handler);
     34     }
     35     else if (handler == null && logger.isTraceEnabled()) {
     36         logger.trace("No handler mapping found for [" + lookupPath + "]");
     37     }
     38     return handler;
     39 }
     40 
     41 @Nullable
     42 protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
     43     // Direct match?
     44     // 直接匹配的情况处理
     45     Object handler = this.handlerMap.get(urlPath);
     46     if (handler != null) {
     47         // Bean name or resolved handler?
     48         if (handler instanceof String) {
     49             String handlerName = (String) handler;
     50             handler = obtainApplicationContext().getBean(handlerName);
     51         }
     52         validateHandler(handler, request);
     53         return buildPathExposingHandler(handler, urlPath, urlPath, null);
     54     }
     55 
     56     // Pattern match?
     57     // 通配符匹配的处理
     58     List<String> matchingPatterns = new ArrayList<>();
     59     for (String registeredPattern : this.handlerMap.keySet()) {
     60         if (getPathMatcher().match(registeredPattern, urlPath)) {
     61             matchingPatterns.add(registeredPattern);
     62         }
     63         else if (useTrailingSlashMatch()) {
     64             if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
     65                 matchingPatterns.add(registeredPattern + "/");
     66             }
     67         }
     68     }
     69 
     70     String bestMatch = null;
     71     Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
     72     if (!matchingPatterns.isEmpty()) {
     73         matchingPatterns.sort(patternComparator);
     74         if (logger.isDebugEnabled()) {
     75             logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
     76         }
     77         bestMatch = matchingPatterns.get(0);
     78     }
     79     if (bestMatch != null) {
     80         handler = this.handlerMap.get(bestMatch);
     81         if (handler == null) {
     82             if (bestMatch.endsWith("/")) {
     83                 handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
     84             }
     85             if (handler == null) {
     86                 throw new IllegalStateException(
     87                         "Could not find handler for best pattern match [" + bestMatch + "]");
     88             }
     89         }
     90         // Bean name or resolved handler?
     91         if (handler instanceof String) {
     92             String handlerName = (String) handler;
     93             handler = obtainApplicationContext().getBean(handlerName);
     94         }
     95         validateHandler(handler, request);
     96         String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
     97 
     98         // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
     99         // for all of them
    100         Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
    101         for (String matchingPattern : matchingPatterns) {
    102             if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
    103                 Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
    104                 Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
    105                 uriTemplateVariables.putAll(decodedVars);
    106             }
    107         }
    108         if (logger.isDebugEnabled()) {
    109             logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
    110         }
    111         return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
    112     }
    113 
    114     // No handler found...
    115     return null;
    116 }
    117 
    118 // 根据URL获取对应的Handler的匹配规则代码实现起来虽然很长,但是并不难理解,考虑了直接匹配与通配符两种情况,其中提及的是buildPathExposingHandler,
    119 // 他将Handler封装成HandlerExecutionChain类型
    120 
    121 protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
    122         String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {
    123 
    124     HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
    125     chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
    126     if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
    127         chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
    128     }
    129     return chain;
    130 }

    在函数中,我们看到了通过将Handler以参数的形式传入,并构建HandlerExecutionChain类型实例,加入了两个拦截器,链处理机制,是spring中非常常用的处理,是AOP中重要组成部分,可以方便的对目标对象进行扩展和拦截。

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

     1 protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
     2     HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
     3             (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
     4 
     5     String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
     6     for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
     7         if (interceptor instanceof MappedInterceptor) {
     8             MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
     9             if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
    10                 chain.addInterceptor(mappedInterceptor.getInterceptor());
    11             }
    12         }
    13         else {
    14             chain.addInterceptor(interceptor);
    15         }
    16     }
    17     return chain;
    18 }

    3、没找到对应的handler的错误处理

    每个请求都会对应着一个handler,因为每个请求都会在后台有相应的逻辑对应,而逻辑的实现就是在Handler中,所以一旦遇到没有找到Handler的情况(正常情况下,如果没有URL匹配的Handler,开发人员可以设置默认的Handler来处理请求,但是默认情况也没有设置,就会出现Handler为空的情况),就只能通过response向用户返回错误信息

     org.springframework.web.servlet.DispatcherServlet.noHandlerFound(HttpServletRequest, HttpServletResponse):

     1 protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
     2     if (pageNotFoundLogger.isWarnEnabled()) {
     3         pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
     4                 "] in DispatcherServlet with name '" + getServletName() + "'");
     5     }
     6     if (this.throwExceptionIfNoHandlerFound) {
     7         throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
     8                 new ServletServerHttpRequest(request).getHeaders());
     9     }
    10     else {
    11         response.sendError(HttpServletResponse.SC_NOT_FOUND);
    12     }
    13 }

    4、 根据当前的Handler寻找对应的HandlerAdapter

    org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(Object)中:

    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逻辑被封装在具体的适配器中,进一步查看SimpleControllerHandlerAdapter类中的supports方法
    
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof Controller);
    }
    
    // 分析到这里的话,SimpleControllerHandlerAdapter就是用来处理普通的web请求的,而且对于springMVC来说,我们会把逻辑封装在Controller的子类中,也就是开发人员自己定义的Controller类

    5、缓存处理
    在研究spring对缓存处理的功能支持前,我们先了解一个概念last-modified缓存机制
    (1)在客户端第一次输入URL时,服务器端会返回内容和状态码200,表示请求成功,同时会添加一个"Last-Modified"的响应头,表示此文件在服务器上的最后更新时间
    (2)客户端第二次请求URL时,客户端会向服务端发送请求头"If-Modified-Since",询问服务器该时间之后当前请求是否改变过,如果没有变化,则返回HTTP 304状态码

    6、HandlerInterceptor的处理

    spring API定义的servlet过滤器可以在servlet处理每个web请求的前后对它进行前置处理和后置处理,此外,有些时候,你可能只想处理由某些springMVC处理程序处理的web请求,
    并在这些处理程序返回的模型属性被传递到视图之前,对他们进行一些操作

    springMVC允许你通过处理拦截web请求,进行前置处理和后置处理。处理拦截是在spring的web应用程序上下文中配置的,因此他们可以利用各种容器特性。并引用容器中声明的各种bean,
    处理拦截是针对特殊的处理程序映射进行注册的,因此,他只拦截通过这些处理程序映射的请求。每个处理拦截都必须实现HandlerInterceptor接口,他包含三个需要实现的回调方法
    preHandle、postHandle、afterCompletion,第一个和第二个方法分别是在处理程序处理请求之前和之后被调用的,第二个方法还允许访问返回ModelAndView对象,因此可以在它里面操作模型属性

    7、 逻辑处理
    对于逻辑处理其实是通过适配器中 转调用Handler并返回视图的,看一下SimpleControllerHandlerAdapter中的实现:

    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter中

     1 @Override
     2 @Nullable
     3 public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
     4         throws Exception {
     5 
     6     return ((Controller) handler).handleRequest(request, response);
     7 }
     8 
     9 org.springframework.web.servlet.mvc.AbstractController中
    10 @Override
    11 @Nullable
    12 public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
    13         throws Exception {
    14 
    15     if (HttpMethod.OPTIONS.matches(request.getMethod())) {
    16         response.setHeader("Allow", getAllowHeader());
    17         return null;
    18     }
    19 
    20     // Delegate to WebContentGenerator for checking and preparing.
    21     checkRequest(request);
    22     prepareResponse(response);
    23 
    24     // Execute handleRequestInternal in synchronized block if required.
    25     // 如果需要session内的同步执行
    26     if (this.synchronizeOnSession) {
    27         HttpSession session = request.getSession(false);
    28         if (session != null) {
    29             Object mutex = WebUtils.getSessionMutex(session);
    30             synchronized (mutex) {
    31                 // 调用客户的逻辑
    32                 return handleRequestInternal(request, response);
    33             }
    34         }
    35     }
    36 
    37     // 调用客户的逻辑
    38     return handleRequestInternal(request, response);
    39 }

    8、异常视图的处理

     1 // processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
     2 
     3 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
     4         @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
     5         @Nullable Exception exception) throws Exception {
     6 
     7     boolean errorView = false;
     8 
     9     if (exception != null) {
    10         if (exception instanceof ModelAndViewDefiningException) {
    11             logger.debug("ModelAndViewDefiningException encountered", exception);
    12             mv = ((ModelAndViewDefiningException) exception).getModelAndView();
    13         }
    14         else {
    15             Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
    16             mv = processHandlerException(request, response, handler, exception);
    17             errorView = (mv != null);
    18         }
    19     }
    20 
    21     // Did the handler return a view to render?
    22     if (mv != null && !mv.wasCleared()) {
    23         // 根据视图页面
    24         render(mv, request, response);
    25         if (errorView) {
    26             WebUtils.clearErrorRequestAttributes(request);
    27         }
    28     }
    29     else {
    30         if (logger.isDebugEnabled()) {
    31             logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
    32                     "': assuming HandlerAdapter completed request handling");
    33         }
    34     }
    35 
    36     if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    37         // Concurrent handling started during a forward
    38         return;
    39     }
    40 
    41     if (mappedHandler != null) {
    42         mappedHandler.triggerAfterCompletion(request, response, null);
    43     }
    44 }
    45 
    46 // processHandlerException方法中异常视图的处理
    47 @Nullable
    48 protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
    49         @Nullable Object handler, Exception ex) throws Exception {
    50 
    51     // Check registered HandlerExceptionResolvers...
    52     ModelAndView exMv = null;
    53     if (this.handlerExceptionResolvers != null) {
    54         for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
    55             exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
    56             if (exMv != null) {
    57                 break;
    58             }
    59         }
    60     }
    61     if (exMv != null) {
    62         if (exMv.isEmpty()) {
    63             request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
    64             return null;
    65         }
    66         // We might still need view name translation for a plain error model...
    67         if (!exMv.hasView()) {
    68             String defaultViewName = getDefaultViewName(request);
    69             if (defaultViewName != null) {
    70                 exMv.setViewName(defaultViewName);
    71             }
    72         }
    73         if (logger.isDebugEnabled()) {
    74             logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
    75         }
    76         WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
    77         return exMv;
    78     }
    79 
    80     throw ex;
    81 }

    9、根据视图跳转页面

    processDispatchResult方法中有一个方法是实现页面跳转的逻辑的,就是render(mv, request, response);看这个方法的逻辑实现:

    org.springframework.web.servlet.DispatcherServlet类中:

     1 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
     2     // Determine locale for request and apply it to the response.
     3     Locale locale =
     4             (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
     5     response.setLocale(locale);
     6 
     7     View view;
     8     String viewName = mv.getViewName();
     9     if (viewName != null) {
    10         // We need to resolve the view name.
    11         // 解析视图名称
    12         view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    13         if (view == null) {
    14             throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
    15                     "' in servlet with name '" + getServletName() + "'");
    16         }
    17     }
    18     else {
    19         // No need to lookup: the ModelAndView object contains the actual View object.
    20         view = mv.getView();
    21         if (view == null) {
    22             throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
    23                     "View object in servlet with name '" + getServletName() + "'");
    24         }
    25     }
    26 
    27     // Delegate to the View object for rendering.
    28     if (logger.isDebugEnabled()) {
    29         logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
    30     }
    31     try {
    32         if (mv.getStatus() != null) {
    33             response.setStatus(mv.getStatus().value());
    34         }
    35         // 页面跳转
    36         view.render(mv.getModelInternal(), request, response);
    37     }
    38     catch (Exception ex) {
    39         if (logger.isDebugEnabled()) {
    40             logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
    41                     getServletName() + "'", ex);
    42         }
    43         throw ex;
    44     }
    45 }

    (1)解析视图名称
    在上文中我们提到DispatcherServlet会根据ModelAndView选择合适的视图来进行渲染,而这一功能就是在resolveViewName中完成的

      1 protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
      2         Locale locale, HttpServletRequest request) throws Exception {
      3 
      4     if (this.viewResolvers != null) {
      5         for (ViewResolver viewResolver : this.viewResolvers) {
      6             View view = viewResolver.resolveViewName(viewName, locale);
      7             if (view != null) {
      8                 return view;
      9             }
     10         }
     11     }
     12     return null;
     13 }
     14 // 我们以org.springframework.web.servlet.view.AbstractCachingViewResolver.resolveViewName(String, Locale)为例子分析ViewResolver逻辑的解析过程,
     15 
     16 @Override
     17 @Nullable
     18 public View resolveViewName(String viewName, Locale locale) throws Exception {
     19     if (!isCache()) {
     20         // 不存在缓存的情况下直接
     21         return createView(viewName, locale);
     22     }
     23     else {
     24         // 直接从缓存中提取
     25         Object cacheKey = getCacheKey(viewName, locale);
     26         View view = this.viewAccessCache.get(cacheKey);
     27         if (view == null) {
     28             synchronized (this.viewCreationCache) {
     29                 view = this.viewCreationCache.get(cacheKey);
     30                 if (view == null) {
     31                     // Ask the subclass to create the View object.
     32                     view = createView(viewName, locale);
     33                     if (view == null && this.cacheUnresolved) {
     34                         view = UNRESOLVED_VIEW;
     35                     }
     36                     if (view != null) {
     37                         this.viewAccessCache.put(cacheKey, view);
     38                         this.viewCreationCache.put(cacheKey, view);
     39                         if (logger.isTraceEnabled()) {
     40                             logger.trace("Cached view [" + cacheKey + "]");
     41                         }
     42                     }
     43                 }
     44             }
     45         }
     46         return (view != UNRESOLVED_VIEW ? view : null);
     47     }
     48 }
     49 
     50 // org.springframework.web.servlet.view.UrlBasedViewResolver中重写的createView方法
     51 @Override
     52 protected View createView(String viewName, Locale locale) throws Exception {
     53     // If this resolver is not supposed to handle the given view,
     54     // return null to pass on to the next resolver in the chain.
     55     // 如果当前解析器不支持当前解析器 如viewName为空
     56     if (!canHandle(viewName, locale)) {
     57         return null;
     58     }
     59 
     60     // Check for special "redirect:" prefix.
     61     // 处理前缀为redirect:x的情况
     62     if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
     63         String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
     64         RedirectView view = new RedirectView(redirectUrl,
     65                 isRedirectContextRelative(), isRedirectHttp10Compatible());
     66         String[] hosts = getRedirectHosts();
     67         if (hosts != null) {
     68             view.setHosts(hosts);
     69         }
     70         return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
     71     }
     72 
     73     // Check for special "forward:" prefix.
     74     // 处理前缀为forward:x的情况 
     75     if (viewName.startsWith(FORWARD_URL_PREFIX)) {
     76         String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
     77         return new InternalResourceView(forwardUrl);
     78     }
     79 
     80     // Else fall back to superclass implementation: calling loadView.
     81     return super.createView(viewName, locale);
     82 }
     83 
     84 //org.springframework.web.servlet.view.AbstractCachingViewResolver父类中的createView
     85 @Nullable
     86 protected View createView(String viewName, Locale locale) throws Exception {
     87     return loadView(viewName, locale);
     88 }
     89 
     90 // org.springframework.web.servlet.view.UrlBasedViewResolver子类中重写loadView
     91 @Override
     92 protected View loadView(String viewName, Locale locale) throws Exception {
     93     AbstractUrlBasedView view = buildView(viewName);
     94     View result = applyLifecycleMethods(viewName, view);
     95     return (view.checkResource(locale) ? result : null);
     96 }
     97 
     98 protected AbstractUrlBasedView buildView(String viewName) throws Exception {
     99     Class<?> viewClass = getViewClass();
    100     Assert.state(viewClass != null, "No view class");
    101 
    102     AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
    103     // 添加前缀以及后缀
    104     view.setUrl(getPrefix() + viewName + getSuffix());
    105 
    106     String contentType = getContentType();
    107     if (contentType != null) {
    108         // 设置ContentType
    109         view.setContentType(contentType);
    110     }
    111 
    112     view.setRequestContextAttribute(getRequestContextAttribute());
    113     view.setAttributesMap(getAttributesMap());
    114 
    115     Boolean exposePathVariables = getExposePathVariables();
    116     if (exposePathVariables != null) {
    117         view.setExposePathVariables(exposePathVariables);
    118     }
    119     Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
    120     if (exposeContextBeansAsAttributes != null) {
    121         view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
    122     }
    123     String[] exposedContextBeanNames = getExposedContextBeanNames();
    124     if (exposedContextBeanNames != null) {
    125         view.setExposedContextBeanNames(exposedContextBeanNames);
    126     }
    127 
    128     return view;
    129 }

    (2)页面跳转
    当viewName解析到对应的view之后,就可以进一步的处理跳转逻辑了

    org.springframework.web.servlet.view.AbstractView类重写了render方法

     1 @Override
     2 public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
     3         HttpServletResponse response) throws Exception {
     4 
     5     if (logger.isTraceEnabled()) {
     6         logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
     7             " and static attributes " + this.staticAttributes);
     8     }
     9 
    10     Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    11     prepareResponse(request, response);
    12     renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
    13 }
    14 
    15 就是把一些需要用到的属性放到request中,createMergedOutputModel就是完成这个操作的
    16 protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
    17         HttpServletRequest request, HttpServletResponse response) {
    18 
    19     @SuppressWarnings("unchecked")
    20     Map<String, Object> pathVars = (this.exposePathVariables ?
    21             (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
    22 
    23     // Consolidate static and dynamic model attributes.
    24     int size = this.staticAttributes.size();
    25     size += (model != null ? model.size() : 0);
    26     size += (pathVars != null ? pathVars.size() : 0);
    27 
    28     Map<String, Object> mergedModel = new LinkedHashMap<>(size);
    29     mergedModel.putAll(this.staticAttributes);
    30     if (pathVars != null) {
    31         mergedModel.putAll(pathVars);
    32     }
    33     if (model != null) {
    34         mergedModel.putAll(model);
    35     }
    36 
    37     // Expose RequestContext?
    38     if (this.requestContextAttribute != null) {
    39         mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
    40     }
    41 
    42     return mergedModel;
    43 }
  • 相关阅读:
    Token 分析
    maven导入依赖下载jar包速度太慢
    springboot 自动装配
    @ComponentScan
    mysql8.0忘记密码或出现Access denied for user 'root'@'localhost' (using password: YES)
    SpringBoot静态资源处理
    @RestController
    PythonGUI:Tkinter学习笔记01
    Python2和Python3有什么区别?
    Python的Random模块
  • 原文地址:https://www.cnblogs.com/ssh-html/p/11329977.html
Copyright © 2020-2023  润新知