springmvc--9--静态资源的处理

springmvc–静态资源的处理

文章目录

1 简单介绍

对于web应用,静态资源处理是很重要的一环。springmvc提供了三种方式来处理静态资源

  • XML文件中配置<mvc:default-servlet-handler/>。这种情况的原理大概如下:检查所有请求,然后判断有没有对应的@RequestMapping映射,没有就将该请求转发给Tomcat默认的Servlet处理。Tomcat默认的Servlet只能处理项目根路径下的资源。

  • 直接配置资源请求的路径,这些请求特殊处理。此种方式配置可以由用户随意指定静态资源位置,十分方便,推荐使用。如下所示,springmvc会将以css开头的请求路径截取掉css/,得到剩下的路径,然后再去类路径下的/static/css/文件夹下匹配

     <mvc:resources mapping="/css/**"
                       location="classpath:/static/css/"
                       cache-period="31556926" />
    <!--一般一种静态资源类型配置一个标签-->
    <mvc:resources  mapping="/js/**"
                       location="classpath:/static/js/"
                       cache-period="31556926" />
    
  • DispatcherServlet的拦截路径修改为/*.do。此种方式本质上还是将静态资源请求交由Tomcat默认的Servlet处理。

2 转发给Tomcat默认的Servlet处理

我们来跟踪一下处理过程。在这种方式下,用户发送的所有请求还是先被DispatcherServlet处理,那么直接定位到doDispatch()方法

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
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 {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            /**
             * 仍然是要先获取处理器执行链
             * 不过此时,相比于以前就有些区别了,使用的不是RequestMappingHandlerMapping
             * 而是SimpleUrlHandlerMapping,见2.1
             */
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            //获取对应的处理器适配器,见2.2
            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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

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

            // Actually invoke the handler.
            //处理器适配器调用处理器处理请求,见2.3
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

            applyDefaultViewName(processedRequest, mv);
            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);
            }
        }
    }
}

2.1 获取能够处理静态资源请求的处理器执行链

/**
 * Return the HandlerExecutionChain for this request.
 * <p>Tries all handler mappings in order.
 * @param request current HTTP request
 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
 */
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) {
      for (HandlerMapping mapping : this.handlerMappings) {
         HandlerExecutionChain handler = mapping.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}

DispatcherServlet默认持有3HandlerMapping对象,按顺序分别如下所示

  • RequestMappingHandlerMapping
  • BeanNameUrlHandlerMapping
  • SimpleUrlHandlerMapping

第一种我们很了解,我们并没有配置静态资源请求的@RequestMapping映射,所以第一种不能处理,第二种我们也没有配置,那么就只剩下第三种了,执行getHandler()方法,会得到一个处理器为DefaultServletHttpRequestHandler对象的处理器执行链,内部结构如下所示

在这里插入图片描述

2.2 获取对应的处理器适配器

得到处理器执行链之后,下一步就是获得对应的处理器适配器了

/**
 * Return the HandlerAdapter for this handler object.
 * @param handler the handler object to find an adapter for
 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
 */
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
                               "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

DispatcherServlet默认持有3HandlerAdapter对象,按顺序分别如下所示

  • RequestMappingHandlerAdapterRequestMappingHandlerMapping对应
  • HttpRequestHandlerAdapter 只能处理实现了HttpRequestHandler接口的处理器
  • SimpleControllerHandlerAdapter 只能处理实现了Controller接口的处理器

DefaultServletHttpRequestHandler实现了HttpRequestHandler接口,所以此处得到的处理器适配器为HttpRequestHandlerAdapter

2.3 处理器适配器调用处理器处理请求

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

    //调用处理器的handleRequest()方法处理请求
   ((HttpRequestHandler) handler).handleRequest(request, response);
   return null;
}

处理器我们知道,它的真实类型是DefaultServletHttpRequestHandler,它实现了HttpRequestHandler接口,在接口方法中完成请求转发到转发给Tomcat默认的Servlet

/**
 * 默认为default,SimpleUrlHandlerMapping获取时给定的初始值
 * Tomcat默认Servlet的名字就是default
 * 可在XML中手动指定名字<mvc:default-servlet-handler default-servlet-name="default"/>
 * 应为你的Servlet容器可能不是Tomcat
 */
private String defaultServletName;


@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

    Assert.state(this.servletContext != null, "No ServletContext set");
    //获取转发给对应Servlet的请求转发器
    RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
    if (rd == null) {
        throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
                                        this.defaultServletName + "'");
    }
    //转发请求
    rd.forward(request, response);
}

通过执行DefaultServletHttpRequestHandlerhandleRequest()方法,请求就被转发到了Tomcat的默认Servlet处理

3 配置资源请求的路径,放行资源请求

一般我们会在springmvc的配置文件中配置如下所示的资源路径,当请求路径是以css/js/开头时,就表明这是一个静态资源请求,于是就会去location中指定的文件夹下匹配对应的静态资源

<mvc:resources mapping="/css/**"
               location="classpath:/static/css/"
               cache-period="31556926" />
<!--一般一种静态资源类型配置一个标签-->
<mvc:resources  mapping="/js/**"
               location="classpath:/static/js/"
               cache-period="31556926" />

接下来我们来跟踪一下处理过程,在这种方式下,用户发送的所有请求还是先被DispatcherServlet处理,那么直接定位到doDispatch()方法

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
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 {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            /**
             * 仍然是要先获取处理器执行链
             * 不过此时,相比于以前就有些区别了,使用的不是RequestMappingHandlerMapping
             * 而是SimpleUrlHandlerMapping,见3.1
             */
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            //获取对应的处理器适配器,见3.2
            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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

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

            // Actually invoke the handler.
            //处理器适配器调用处理器处理请求,见3.3
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

            applyDefaultViewName(processedRequest, mv);
            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);
            }
        }
    }
}

3.1 获取能够处理静态资源请求的处理器执行链

/**
 * Return the HandlerExecutionChain for this request.
 * <p>Tries all handler mappings in order.
 * @param request current HTTP request
 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
 */
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) {
      for (HandlerMapping mapping : this.handlerMappings) {
         HandlerExecutionChain handler = mapping.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}

DispatcherServlet持有4HandlerMapping对象,按顺序分别如下所示

  • RequestMappingHandlerMapping
  • BeanNameUrlHandlerMapping
  • SimpleUrlHandlerMapping 对应处理css开头的请求
  • SimpleUrlHandlerMapping 对应处理js开头的请求

这里为什么有两个SimpleUrlHandlerMapping呢?

  • 其实项目启动扫描springmvc配置文件的时候,扫描到一个<mvc:resources />标签以后,就会解析生成一个SimpleUrlHandlerMapping类的BeanDefinition,现在我们配置了两个<mvc:resources />标签,这就与上面有两个SimpleUrlHandlerMapping对象对应起来了。
  • refresh()方法调用结束之前,会发布一个ContextRefreshedEvent事件,表明上下文刷新成功,此时容器中的ContextRefreshListener就会监听到这个事件,然后将springmvc9大组件设置到DispatcherServlet

接下来我们来看一下这两个SimpleUrlHandlerMapping字段值

css对应的SimpleUrlHandlerMapping

在这里插入图片描述

js对应的SimpleUrlHandlerMapping

在这里插入图片描述

执行getHandler()方法,会得到一个处理器为ResourceHttpRequestHandler对象的处理器执行链,内部结构如下所示

在这里插入图片描述

3.2 获取对应的处理器适配器

得到处理器执行链之后,下一步就是获得对应的处理器适配器了

/**
 * Return the HandlerAdapter for this handler object.
 * @param handler the handler object to find an adapter for
 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
 */
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
                               "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

DispatcherServlet默认持有3HandlerAdapter对象,按顺序分别如下所示

  • RequestMappingHandlerAdapterRequestMappingHandlerMapping对应
  • HttpRequestHandlerAdapter 只能处理实现了HttpRequestHandler接口的处理器
  • SimpleControllerHandlerAdapter 只能处理实现了Controller接口的处理器

ResourceHttpRequestHandler实现了HttpRequestHandler接口,所以此处得到的处理器适配器为HttpRequestHandlerAdapter ,这个过程和2.2中的完全一样

3.3 处理器适配器调用处理器处理请求

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

    //调用处理器的handleRequest()方法处理请求
    ((HttpRequestHandler) handler).handleRequest(request, response);
    //返回一个空的模型视图对象,后面的处理就基本上跳过了
    return null;
}

处理器我们知道,它的真实类型是ResourceHttpRequestHandler,它实现了HttpRequestHandler接口,详细处理过程见5.2

总结一下这种方式的静态资源处理

  • 静态资源请求仍然是由DispatcherServlet处理
  • 具体的处理过程交给了ResourceHttpRequestHandlerhandleRequest()方法

4 SimpleUrlHandlerMapping

先来看一下它的类图

在这里插入图片描述

HandlerMapping最重要的方法就是getHandler(HttpServletRequest)方法,查看该方法源码发现,该方具有唯一的实现

/**
 * Look up a handler for the given request, falling back to the default
 * handler if no specific one is found.
 * @param request current HTTP request
 * @return the corresponding handler instance, or the default handler
 * @see #getHandlerInternal
 */
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    //获取处理本次请求的处理器,见4.1
    Object handler = getHandlerInternal(request);
    //获取默认的处理器,见4.2.3
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    // Bean name or resolved handler?
    /**
     * 未实例化就实例化
     * 此处必然不会执行,因为getHandlerInternal(request)方法中已经判断过一次
     * 并且它返回的是处理器执行器链,并非是一个单纯的处理器
     */
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }

    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

    if (logger.isTraceEnabled()) {
        logger.trace("Mapped to " + handler);
    }
    else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
        logger.debug("Mapped to " + executionChain.getHandler());
    }

    //cors相关处理,以后再说
    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
        CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        config = (config != null ? config.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    return executionChain;
}

这个方法流程应该不陌生吧,在springmvc–HandlerMapping处理器映射器这篇文章中详细剖析过方法源码,当时是RequestMappingHandlerMapping,现在是SimpleUrlHandlerMapping,接下来我们就来看一下具体流程吧

4.1 获取处理本次请求的处理器

/**
 * Look up a handler for the URL path of the given request.
 * @param request current HTTP request
 * @return the handler instance, or {@code null} if none found
 */
@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    //获取本次请求路径,去掉项目名之后的路径
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    /**
     * 保存到请求域org.springframework.web.servlet.HandlerMapping.lookupPath属性中
     * 后面在4.2中需要取出这个路径,然后根据这个路径去匹配拦截器
     */
    request.setAttribute(LOOKUP_PATH, lookupPath);
    //根据路径构建对应的处理器执行器链对象,见4.1.1
    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 (StringUtils.matchesCharacter(lookupPath, '/')) {
            //获取根路径的处理器,见4.1.2
            rawHandler = getRootHandler();
        }
        //否则使用默认的处理器
        if (rawHandler == null) {
            //获取默认的处理器,见4.1.3
            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);
            //构建处理器执行器链,见4.1.1.1
            handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
        }
    }
    return handler;
}

该方法根据请求路径获取到对应的处理器,然后包装为处理器执行器链

  • 直接根据<mvc:resources mapping="/xxx/**" >标签配置的进行匹配
  • 匹配不到再判断路径是否为根路径,根路径就使用根路径处理器(需要用户配置)
  • 也不是根路径,就使用默认的处理器(需要用户配置)
  • 所有的处理器均需要实现HttpHandleRequest接口

4.1.1 根据路径构建对应的处理器执行器链对象

/**
 * 我们在<mvc resource>标签中的配置会被解析,然后保存在此处
 * 比如我们配置mapping="/js/**" location="classpath:/static/js/"
 * 经过解析就会在这个集合中放入一个"/js/**"->ResourceHttpRequestHandler的映射
 */
private final Map<String, Object> handlerMap = new LinkedHashMap<>();


/**
 * 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);
        //构建一个处理器执行器对象,见4.1.1.1
        return buildPathExposingHandler(handler, urlPath, urlPath, null);
    }

    // Pattern match?
    //不能直接匹配
    List<String> matchingPatterns = new ArrayList<>();
    for (String registeredPattern : this.handlerMap.keySet()) {
        /**
         * getPathMatcher()方法,获取路径匹配器
         * match(registeredPattern, urlPath)方法,匹配两个路径
         */
        if (getPathMatcher().match(registeredPattern, urlPath)) {
            matchingPatterns.add(registeredPattern);
        }
        //不匹配,就判断是否开启尾部斜杠匹配,然后进行尾部斜杆匹配,见4.1.1.3
        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.isTraceEnabled() && matchingPatterns.size() > 1) {
            logger.trace("Matching patterns " + matchingPatterns);
        }
        bestMatch = matchingPatterns.get(0);
    }
    if (bestMatch != null) {
        //获取第一个匹配路径对应的ResourceHttpRequestHandler
        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);
        /**
         * 截取掉请求路径中匹配的路径
         * 例如js/**和js/test.js,就会得到test.js
         */
        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.isTraceEnabled() && uriTemplateVariables.size() > 0) {
            logger.trace("URI variables " + uriTemplateVariables);
        }
        //构建一个处理器执行器对象,见4.1.1.1
        return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
    }

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

总结:

  • 使用AntPathMatcher进行路径匹配
  • 可以解析路径上的模板变量,也就是说可以在<mvc resources mapping="/js/{xxx}">中配置模板变量
  • 匹配到处理器后,就将处理器包装为一个处理器执行器链,里面添加了两个默认的拦截器
    • PathExposingHandlerInterceptor:将解析好的请求路径保存到请求域中,见9
    • UriTemplateVariablesHandlerInterceptor
4.1.1.1 构建一个处理器执行器链对象
/**
 * Build a handler object for the given raw handler, exposing the actual
 * handler, the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}, as well as
 * the {@link #URI_TEMPLATE_VARIABLES_ATTRIBUTE} before executing the handler.
 * <p>The default implementation builds a {@link HandlerExecutionChain}
 * with a special interceptor that exposes the path attribute and uri template variables
 * @param rawHandler the raw handler to expose
 * @param pathWithinMapping the path to expose before executing the handler
 * @param uriTemplateVariables the URI template variables, can be {@code null} if no variables found
 * @return the final handler object
 */
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
                                          String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {

    //构建一个处理器执行器链
    HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
    //添加一个默认的拦截器,这个拦截器用来将解析好的请求路径保存到请求域中,见9
    chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
    //添加一个默认的拦截器,用来处理模板变量
    if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
        chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
    }
    return chain;
}
4.1.1.2 获取路径匹配器
//路径匹配器,实现为Ant样式路径匹配
private PathMatcher pathMatcher = new AntPathMatcher();


/**
 * Return the PathMatcher implementation to use for matching URL paths
 * against registered URL patterns.
 */
public PathMatcher getPathMatcher() {
    return this.pathMatcher;
}
4.1.1.3 是否开启尾部斜杠匹配
//默认为false,不开启尾部斜杠匹配
private boolean useTrailingSlashMatch = false;


/**
 * Whether to match to URLs irrespective of the presence of a trailing slash.
 */
public boolean useTrailingSlashMatch() {
    return this.useTrailingSlashMatch;
}

4.1.2 获取根路径的处理器

//默认是null,需要用户指定
@Nullable
private Object rootHandler;


/**
 * Return the root handler for this handler mapping (registered for "/"),
 * or {@code null} if none.
 */
@Nullable
public Object getRootHandler() {
    return this.rootHandler;
}
  • 默认是不存在的,需要用户指定

  • 处理器需要实现HttpHandleRequest接口

4.1.3 获取默认的处理器

//默认是null,需要用户指定
@Nullable
private Object defaultHandler;


/**
 * Return the default handler for this handler mapping,
 * or {@code null} if none.
 */
@Nullable
public Object getDefaultHandler() {
    return this.defaultHandler;
}
  • 默认是不存在的,需要用户指定

  • 处理器需要实现HttpHandleRequest接口

4.2 向处理器执行器链中添加拦截器

//保存着所有的拦截器对象
private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();


/**
 * Build a {@link HandlerExecutionChain} for the given handler, including
 * applicable interceptors.
 * <p>The default implementation builds a standard {@link HandlerExecutionChain}
 * with the given handler, the common interceptors of the handler mapping, and any
 * {@link MappedInterceptor MappedInterceptors} matching to the current request URL. Interceptors
 * are added in the order they were registered. Subclasses may override this
 * in order to extend/rearrange the list of interceptors.
 * <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a
 * pre-built {@link HandlerExecutionChain}. This method should handle those
 * two cases explicitly, either building a new {@link HandlerExecutionChain}
 * or extending the existing chain.
 * <p>For simply adding an interceptor in a custom subclass, consider calling
 * {@code super.getHandlerExecutionChain(handler, request)} and invoking
 * {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
 * @param handler the resolved handler instance (never {@code null})
 * @param request current HTTP request
 * @return the HandlerExecutionChain (never {@code null})
 * @see #getAdaptedInterceptors()
 */
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    //如果已经是处理器执行器链了就直接使用,否则将处理器构造为处理器执行器链
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                                   (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

    /**
     * 从请求域中拿到LOOKUP_PATH对应值,
     * 如果拿不到,就使用UrlPathHelper重新解析路径
     */
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
    //遍历所有的拦截器
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        /**
         * 设置了拦截路径的拦截器,会被适配为MappedInterceptor
         * 这个MappedInterceptor中有matches()方法用来判断拦截路径和请求路径是否匹配
         */
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        }
        //拦截所有
        else {
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

对于SimpleUrlHandlerMapping来说,此处不存在构建处理器执行器链(4.1中已经构建了),此方法仅将匹配的拦截器封装到处理器执行器链中

5 ResourceHttpRequestHandler

先来看一下它的类图

在这里插入图片描述

  • 核心接口是HttpRequestHandler,接口唯一方法handleRequest()完成对静态资源请求的处理
  • InitializingBean接口完成这个处理器的初始化工作,我们需要重点关注一下

先来看一下这个类的字段

public class ResourceHttpRequestHandler extends WebContentGenerator
    implements HttpRequestHandler, EmbeddedValueResolverAware, InitializingBean, CorsConfigurationSource {


    private static final String URL_RESOURCE_CHARSET_PREFIX = "[charset=";


    private final List<String> locationValues = new ArrayList<>(4);

    /**
     * 静态资源所在的位置
     * 我们配置的classpath:/static/js、classpath:/static/css最终会被解析为
     * Resource对象,代表该文件夹下的所有资源
     */
    private final List<Resource> locations = new ArrayList<>(4);

    //资源、编码之间的映射
    private final Map<Resource, Charset> locationCharsets = new HashMap<>(4);

    //资源解析器
    private final List<ResourceResolver> resourceResolvers = new ArrayList<>(4);

    //资源转换器
    private final List<ResourceTransformer> resourceTransformers = new ArrayList<>(4);

    //资源解析器链,见7
    @Nullable
    private ResourceResolverChain resolverChain;

    //资源转换器链,见8
    @Nullable
    private ResourceTransformerChain transformerChain;

    //http资源消息转换器,将资源写入到响应体中,所有资源
    @Nullable
    private ResourceHttpMessageConverter resourceHttpMessageConverter;

    //http资源消息转换器,将资源写入到响应体中,部分资源,断点下载
    @Nullable
    private ResourceRegionHttpMessageConverter resourceRegionHttpMessageConverter;

    //内容协商管理器,属性填充阶段自动容器中填充进来
    @Nullable
    private ContentNegotiationManager contentNegotiationManager;

    //文件扩展名->媒体类型映射
    private final Map<String, MediaType> mediaTypes = new HashMap<>(4);

    @Nullable
    private CorsConfiguration corsConfiguration;

    //路径处理帮助器,属性填充阶段自动填充一个
    @Nullable
    private UrlPathHelper urlPathHelper;

    //值解析器
    @Nullable
    private StringValueResolver embeddedValueResolver;
}

5.1 afterPropertiesSet()方法,处理器初始化

@Override
public void afterPropertiesSet() throws Exception {
    resolveResourceLocations();

    if (logger.isWarnEnabled() && CollectionUtils.isEmpty(this.locations)) {
        logger.warn("Locations list is empty. No resources will be served unless a " +
                    "custom ResourceResolver is configured as an alternative to PathResourceResolver.");
    }

    //初始化资源解析器
    if (this.resourceResolvers.isEmpty()) {
        this.resourceResolvers.add(new PathResourceResolver());
    }

    //初始化资源解析器允许解析的资源,见5.1.1
    initAllowedLocations();

    // Initialize immutable resolver and transformer chains
    //初始化一个资源解析器链,见7
    this.resolverChain = new DefaultResourceResolverChain(this.resourceResolvers);
    //初始化一个资源转换器链,见8
    this.transformerChain = new DefaultResourceTransformerChain(this.resolverChain, this.resourceTransformers);

    //两个http资源消息转换器,负责将静态资源写入响应中
    if (this.resourceHttpMessageConverter == null) {
        this.resourceHttpMessageConverter = new ResourceHttpMessageConverter();
    }
    if (this.resourceRegionHttpMessageConverter == null) {
        this.resourceRegionHttpMessageConverter = new ResourceRegionHttpMessageConverter();
    }

    //获取内容协商管理器,见5.1.2
    ContentNegotiationManager manager = getContentNegotiationManager();
    //将内容协商管理器中注册媒体类型映射注册到当前处理器中,见5.1.3
    if (manager != null) {
        setMediaTypes(manager.getMediaTypeMappings());
    }

    //空方法,直接忽略
    @SuppressWarnings("deprecation")
    org.springframework.web.accept.PathExtensionContentNegotiationStrategy strategy =
        initContentNegotiationStrategy();
    if (strategy != null) {
        setMediaTypes(strategy.getMediaTypes());
    }
}

总结一下该方法做的事情

  • 初始化一个默认的资源解析器PathResourceResolver,如果用户未指定的话
  • 设置资源解析器解析资源的位置
  • 根据目前拥有的资源解析器构造一个资源解析器链
  • 根据目前拥有的资源转换器构造一个资源转换器链
  • 初始化两个http资源消息转换器
    • ResourceHttpMessageConverter::写入完整资源
    • ResourceRegionHttpMessageConverter:写入部分资源
  • 将内容协商管理器中资源类型->注册媒体类型映射注册到当前处理器中

5.1.1 初始化资源解析器允许解析的资源

/**
 * Look for a {@code PathResourceResolver} among the configured resource
 * resolvers and set its {@code allowedLocations} property (if empty) to
 * match the {@link #setLocations locations} configured on this class.
 */
protected void initAllowedLocations() {
    //用户未在<mvc:resource >中配置静态资源的位置
    if (CollectionUtils.isEmpty(this.locations)) {
        return;
    }
    
    //遍历所有的资源解析器
    for (int i = getResourceResolvers().size() - 1; i >= 0; i--) {
        if (getResourceResolvers().get(i) instanceof PathResourceResolver) {
            PathResourceResolver pathResolver = (PathResourceResolver) getResourceResolvers().get(i);
            //设置资源解析器允许解析的资源
            if (ObjectUtils.isEmpty(pathResolver.getAllowedLocations())) {
                pathResolver.setAllowedLocations(getLocations().toArray(new Resource[0]));
            }
            if (this.urlPathHelper != null) {
                //设置资源编码
                pathResolver.setLocationCharsets(this.locationCharsets);
                //设置路径处理帮助器
                pathResolver.setUrlPathHelper(this.urlPathHelper);
            }
            break;
        }
    }
}

可以看到,这个方法将静态资源所在位置设置到了资源解析器中。这样,有对应静态资源请求过来之后,就会调用这个资源解析器去指定位置加载静态资源

5.1.2 获取内容协商管理器

/**
 * Return the configured content negotiation manager.
 * @since 4.3
 * @deprecated as of 5.2.4.
 */
@Nullable
@Deprecated
public ContentNegotiationManager getContentNegotiationManager() {
    return this.contentNegotiationManager;
}

5.1.3 将文件扩展名->媒体类型映射注册到处理器中

/**
 * Add mappings between file extensions, extracted from the filename of a
 * static {@link Resource}, and corresponding media type  to set on the
 * response.
 * <p>Use of this method is typically not necessary since mappings are
 * otherwise determined via
 * {@link javax.servlet.ServletContext#getMimeType(String)} or via
 * {@link MediaTypeFactory#getMediaType(Resource)}.
 * @param mediaTypes media type mappings
 * @since 5.2.4
 */
public void setMediaTypes(Map<String, MediaType> mediaTypes) {
    mediaTypes.forEach((ext, mediaType) ->
                       this.mediaTypes.put(ext.toLowerCase(Locale.ENGLISH), mediaType));
}

很少用,注释上面也说了,一般使用ServletContextgetMimeType(String)方法或者MediaTypeFactorygetMediaType(Resource)来得到资源的媒体类型,见5.2.4

5.2 handleRequest(HttpServletRequest request, HttpServletResponse response)方法,完成静态资源请求处理

/**
 * Processes a resource request.
 * <p>Checks for the existence of the requested resource in the configured list of locations.
 * If the resource does not exist, a {@code 404} response will be returned to the client.
 * If the resource exists, the request will be checked for the presence of the
 * {@code Last-Modified} header, and its value will be compared against the last-modified
 * timestamp of the given resource, returning a {@code 304} status code if the
 * {@code Last-Modified} value  is greater. If the resource is newer than the
 * {@code Last-Modified} value, or the header is not present, the content resource
 * of the resource will be written to the response with caching headers
 * set to expire one year in the future.
 */
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

    // For very general mappings (e.g. "/") we need to check 404 first
    //检查能否根据路径加载到静态资源,见5.2.1
    Resource resource = getResource(request);
    if (resource == null) {
        logger.debug("Resource not found");
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    //OPTIONS类型请求设置允许访问后直接跳过处理
    if (HttpMethod.OPTIONS.matches(request.getMethod())) {
        response.setHeader("Allow", getAllowHeader());
        return;
    }

    // Supported methods and required session
    //检查是否支持处理本次请求,见5.2.2
    checkRequest(request);

    // Header phase
    if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
        logger.trace("Resource not modified");
        return;
    }

    // Apply cache settings, if any
    //设置静态资源的缓存时间,见5.2.3
    prepareResponse(response);

    // Check the media type for the resource
    //获取资源对应的媒体类型,见5.2.4
    MediaType mediaType = getMediaType(request, resource);
    //设置响应资源的内容类型和大小,见5.2.5
    setHeaders(response, resource, mediaType);

    // Content phase
    ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
    //未设置Range请求头表示需要全部的静态资源
    if (request.getHeader(HttpHeaders.RANGE) == null) {
        Assert.state(this.resourceHttpMessageConverter != null, "Not initialized");
        //将资源写入过程委派给了ResourceHttpMessageConverter完成
        this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
    }
    /**
     * 设置了Range请求头表示只需要部分资源,即断点下载功能
     * Range请求头中会指定需要资源的开始和结束的索引位置
     * 此处只需要Range请求头中指定索引位置的静态资源
     */
    else {
        Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized");
        ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
        try {
            //得到Range请求头的内容
            List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
            //设置响应状态码为206
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            //只写入指定位置和大小的静态资源
            this.resourceRegionHttpMessageConverter.write(
                HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage);
        }
        catch (IllegalArgumentException ex) {
            //"Content-Range"
            response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
            //设置错误状态码416
            response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
        }
    }
}

通过执行DefaultServletHttpRequestHandlerhandleRequest()方法,请求就被转发到了Tomcat的默认Servlet处理

5.2.1 根据路径加载静态资源

@Nullable
protected Resource getResource(HttpServletRequest request) throws IOException {
    /**
     * org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping
     * 得到保存在请求域属性中的路径,这个路径是在PathExposingHandlerInterceptor中设置
     * 进去的,见9.1
     */
    String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
    if (path == null) {
        throw new IllegalStateException("Required request attribute '" +
                                        HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set");
    }

    /**
     * 解决路径中斜杠不正确的问题
     * 将路径中的双斜杠替换为单斜杠
     * 反斜杠替换为正斜杠
     */
    path = processPath(path);
    /**
     * isInvalidPath()方法,从3个方面检查请求路径是不是一个无效的路径
     * 1.路径中包含"WEB-INF"或者 "META-INF"不是一个无效路径
     * 2."../"这种的路径也不是无效路径
     * 3.ResourceUtils类isUrl()方法判断是不是一个无效路径
     * 经过这3个检验通过之后,就是一个无效路径了
     */
    if (!StringUtils.hasText(path) || isInvalidPath(path)) {
        return null;
    }
    //检查给定路径是否包含无效的转义序列
    if (isInvalidEncodedPath(path)) {
        return null;
    }

    Assert.notNull(this.resolverChain, "ResourceResolverChain not initialized.");
    Assert.notNull(this.transformerChain, "ResourceTransformerChain not initialized.");

    /**
     * 根据路径加载资源
     * getLocations()方法,获取统一资源描述,这里是ClassPathResource
     */
    Resource resource = this.resolverChain.resolveResource(request, path, getLocations());
    if (resource != null) {
        resource = this.transformerChain.transform(request, resource);
    }
    return resource;
}


//返回静态资源所在位置的统一资源描述
public List<Resource> getLocations() {
    return this.locations;
}

方法流程如下所示:

  • 替换路径中使用不正确的斜杠
  • 替换完成后检查路径是否有效
  • 路径中是否使用了无效的转义字符
  • 经过上述3步验证之后,资源解析器根据请求路径解析加载对应位置下的资源

5.2.2 检查处理器是否支持处理该请求

/** 
 * 支持处理的请求方式
 * 默认为GET,HEAD两种
 */
@Nullable
private Set<String> supportedMethods;

//是否需要验证session
private boolean requireSession = false;



/**
 * Check the given request for supported methods and a required session, if any.
 * @param request current HTTP request
 * @throws ServletException if the request cannot be handled because a check failed
 * @since 4.2
 */
protected final void checkRequest(HttpServletRequest request) throws ServletException {
    // Check whether we should support the request method.
    //获取本次请求的请求方式
    String method = request.getMethod();
    
    //不支持处理就抛出异常
    if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
        throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);
    }

    // Check whether a session is required.
    if (this.requireSession && request.getSession(false) == null) {
        throw new HttpSessionRequiredException("Pre-existing session required but none found");
    }
}

该方法从两个方面检查处理器是否支持处理该请求

  • 请求方式,仅限GETHEAD
  • session是否必须

5.2.3 设置静态资源的缓存时间

//这两个属性是WebContentGenerator的
/**
 * 缓存控制器,默认为null,一般都是使用cacheSeconds
 * 控制静态资源的缓存时间,
 * 会将缓存控制器的配置写入响应头中
 */
@Nullable
private CacheControl cacheControl;

//缓存秒数,可通过<mvc:resource />标签修改
private int cacheSeconds = -1;


/**
 * Prepare the given response according to the settings of this generator.
 * Applies the number of cache seconds specified for this generator.
 * @param response current HTTP response
 * @since 4.2
 */
protected final void prepareResponse(HttpServletResponse response) {
    //缓存控制器的配置写入响应头
    if (this.cacheControl != null) {
        applyCacheControl(response, this.cacheControl);
    }
    //缓存秒数写入响应头
    else {
        applyCacheSeconds(response, this.cacheSeconds);
    }
    if (this.varyByRequestHeaders != null) {
        for (String value : getVaryRequestHeadersToAdd(response, this.varyByRequestHeaders)) {
            response.addHeader("Vary", value);
        }
    }
}

在此处完成静态资源的缓存时间的控制

5.2.4 获取资源对应的媒体类型

/**
 * 默认支持两种类型,见5.1.3
 * json->application/json
 * xml->application/xml
 * 这两种是内容协商管理器中自带的
 */
private final Map<String, MediaType> mediaTypes = new HashMap<>(4);


/**
 * Determine the media type for the given request and the resource matched
 * to it. This implementation tries to determine the MediaType using one of
 * the following lookups based on the resource filename and its path
 * extension:
 * <ol>
 * <li>{@link javax.servlet.ServletContext#getMimeType(String)}
 * <li>{@link #getMediaTypes()}
 * <li>{@link MediaTypeFactory#getMediaType(String)}
 * </ol>
 * @param request the current request
 * @param resource the resource to check
 * @return the corresponding media type, or {@code null} if none found
 */
@Nullable
protected MediaType getMediaType(HttpServletRequest request, Resource resource) {
    MediaType result = null;
    /**
     * mimeType = application/javascript
     * resource.getFilename()方法, 获取当前请求资源的名字
     * 该方法会得到它要响应的资源的内容类型
     */
    String mimeType = request.getServletContext().getMimeType(resource.getFilename());
    if (StringUtils.hasText(mimeType)) {
        //转化为媒体类型
        result = MediaType.parseMediaType(mimeType);
    }

    /**
     * 如果Servlet上下文不能识别资源的内容类型
     * 那么就在此处进一步处理,根据文件的扩展名来获取最终响应的媒体类型
     */
    if (result == null || MediaType.APPLICATION_OCTET_STREAM.equals(result)) {
        MediaType mediaType = null;
        String filename = resource.getFilename();
        //文件扩展名
        String ext = StringUtils.getFilenameExtension(filename);
        if (ext != null) {
            //判断是不是xml或json
            mediaType = this.mediaTypes.get(ext.toLowerCase(Locale.ENGLISH));
        }
        //通过媒体类型工厂获取后缀名对应的媒体类型
        if (mediaType == null) {
            mediaType = MediaTypeFactory.getMediaType(filename).orElse(null);
        }
        if (mediaType != null) {
            result = mediaType;
        }
    }
    return result;
}

总结:

  • 先使用ServletContextgetMimeType(资源名)方法,直接获取资源的内容类型
  • 如果获取不到,那么尝试使用MediaTypeFactory获取资源后缀名对应的媒体类型
  • MediaTypeFactory中存储了所有可能会用到的资源类型->媒体类型的映射关系

5.2.5 设置响应资源的内容类型和大小

/**
 * Set headers on the given servlet response.
 * Called for GET requests as well as HEAD requests.
 * @param response current servlet response
 * @param resource the identified resource (never {@code null})
 * @param mediaType the resource's media type (never {@code null})
 * @throws IOException in case of errors while setting the headers
 */
protected void setHeaders(HttpServletResponse response, Resource resource, @Nullable MediaType mediaType)
    throws IOException {

    //设置资源的大小
    long length = resource.contentLength();
    if (length > Integer.MAX_VALUE) {
        response.setContentLengthLong(length);
    }
    else {
        response.setContentLength((int) length);
    }

    //设置响应的内容类型
    if (mediaType != null) {
        response.setContentType(mediaType.toString());
    }

    /**
     * HttpResource是Resource的扩展接口
     * 代表一个需要写入响应的资源,接口只有一个方法getResponseHeaders()
     * 代表为该资源设置的响应头
     */
    if (resource instanceof HttpResource) {
        HttpHeaders resourceHeaders = ((HttpResource) resource).getResponseHeaders();
        resourceHeaders.forEach((headerName, headerValues) -> {
            boolean first = true;
            for (String headerValue : headerValues) {
                if (first) {
                    response.setHeader(headerName, headerValue);
                }
                else {
                    response.addHeader(headerName, headerValue);
                }
                first = false;
            }
        });
    }

    /**
     * "Accept-Ranges"
     * 代表了该服务器可以接受范围请求,即我们常见的断点下载功能
     * 可以通过请求头Range来定义获取的字节范围,格式为 bytes=0-8,那么只会获取这个范围的字节数据
     */
    response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
}

该方法只是简单的将资源类型和资源大小设置在响应头中,并允许断点下载

6 HttpRequestHandlerAdapter

SimpleUrlHandlerMapping对应

public class HttpRequestHandlerAdapter implements HandlerAdapter {

    @Override
    public boolean supports(Object handler) {
        //只要处理器是HttpRequestHandler类型的,就使用该处理器适配器
        return (handler instanceof HttpRequestHandler);
    }

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

        //处理过程就是调用HttpRequestHandler的handleRequest()方法,并返回一个null
        ((HttpRequestHandler) handler).handleRequest(request, response);
        return null;
    }

    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        if (handler instanceof LastModified) {
            return ((LastModified) handler).getLastModified(request);
        }
        return -1L;
    }

}

该处理器适配器,将所有的处理过程交给了HttpRequestHandler来处理,处理过程见5

7 DefaultResourceResolverChain

该类只有一个唯一的接口ResourceResolverChain

public interface ResourceResolverChain {

    /**
     * 解析路径对应的资源
     */
    @Nullable
    Resource resolveResource(
        @Nullable HttpServletRequest request, String requestPath, List<? extends Resource> locations);

    /**
     * 解析请求路径
     */
    @Nullable
    String resolveUrlPath(String resourcePath, List<? extends Resource> locations);

}

下面是该类的完整实现

class DefaultResourceResolverChain implements ResourceResolverChain {

    //当前使用的资源解析器
    @Nullable
    private final ResourceResolver resolver;

    //下一个资源解析器所在位置
    @Nullable
    private final ResourceResolverChain nextChain;


    //构造方法
    public DefaultResourceResolverChain(@Nullable List<? extends ResourceResolver> resolvers) {
        resolvers = (resolvers != null ? resolvers : Collections.emptyList());
        DefaultResourceResolverChain chain = initChain(new ArrayList<>(resolvers));
        this.resolver = chain.resolver;
        this.nextChain = chain.nextChain;
    }

    //初始化链
    private static DefaultResourceResolverChain initChain(ArrayList<? extends ResourceResolver> resolvers) {
        //创建一个空的链
        DefaultResourceResolverChain chain = new DefaultResourceResolverChain(null, null);
        //得到这个list集合迭代器
        ListIterator<? extends ResourceResolver> it = resolvers.listIterator(resolvers.size());
        //反向遍历这个迭代器,将这些资源解析器构造为一个链条,典型的责任链模式
        while (it.hasPrevious()) {
            chain = new DefaultResourceResolverChain(it.previous(), chain);
        }
        return chain;
    }

    //构造方法
    private DefaultResourceResolverChain(@Nullable ResourceResolver resolver, @Nullable ResourceResolverChain chain) {
        Assert.isTrue((resolver == null && chain == null) || (resolver != null && chain != null),
                      "Both resolver and resolver chain must be null, or neither is");
        this.resolver = resolver;
        this.nextChain = chain;
    }

    /******************************内部最终调用它持有的资源解析*************************/

    //接口方法,解析请求路径对应的资源
    @Override
    @Nullable
    public Resource resolveResource(
        @Nullable HttpServletRequest request, String requestPath, List<? extends Resource> locations) {

        return (this.resolver != null && this.nextChain != null ?
                this.resolver.resolveResource(request, requestPath, locations, this.nextChain) : null);
    }

    //接口方法,解析请求路径
    @Override
    @Nullable
    public String resolveUrlPath(String resourcePath, List<? extends Resource> locations) {
        return (this.resolver != null && this.nextChain != null ?
                this.resolver.resolveUrlPath(resourcePath, locations, this.nextChain) : null);
    }

}

典型的责任链模式,它将一系列的资源解析器构造为一个链条,每一个节点除了持有一个资源解析器,还持有一个指向下一个节点的引用。

8 DefaultResourceTransformerChain

该类只有一个唯一的接口ResourceTransformerChain

public interface ResourceTransformerChain {

   /**
    * 获取资源解析器链
    */
   ResourceResolverChain getResolverChain();

   /**
    * 转换给定的资源
    */
   Resource transform(HttpServletRequest request, Resource resource) throws IOException;

}

下面是该类的完整实现

class DefaultResourceTransformerChain implements ResourceTransformerChain {

    //资源解析器链
    private final ResourceResolverChain resolverChain;

    //当前使用的资源转换器
    @Nullable
    private final ResourceTransformer transformer;

    //下一个资源转换器的应用
    @Nullable
    private final ResourceTransformerChain nextChain;


    //构造方法
    public DefaultResourceTransformerChain(
        ResourceResolverChain resolverChain, @Nullable List<ResourceTransformer> transformers) {

        Assert.notNull(resolverChain, "ResourceResolverChain is required");
        this.resolverChain = resolverChain;
        transformers = (transformers != null ? transformers : Collections.emptyList());
        //将资源转换器初始化为一个链条
        DefaultResourceTransformerChain chain = initTransformerChain(resolverChain, new ArrayList<>(transformers));
        this.transformer = chain.transformer;
        this.nextChain = chain.nextChain;
    }

    /**
     * 资源转换器初始化为一个链条
     * 其过程和初始化资源解析器链条差不多,都是反向遍历,然后得到一个正向的链条
     */
    private DefaultResourceTransformerChain initTransformerChain(ResourceResolverChain resolverChain,
                                                                 ArrayList<ResourceTransformer> transformers) {

        DefaultResourceTransformerChain chain = new DefaultResourceTransformerChain(resolverChain, null, null);
        ListIterator<? extends ResourceTransformer> it = transformers.listIterator(transformers.size());
        while (it.hasPrevious()) {
            chain = new DefaultResourceTransformerChain(resolverChain, it.previous(), chain);
        }
        return chain;
    }

    //构造方法
    public DefaultResourceTransformerChain(ResourceResolverChain resolverChain,
                                           @Nullable ResourceTransformer transformer, @Nullable ResourceTransformerChain chain) {

        Assert.isTrue((transformer == null && chain == null) || (transformer != null && chain != null),
                      "Both transformer and transformer chain must be null, or neither is");

        this.resolverChain = resolverChain;
        this.transformer = transformer;
        this.nextChain = chain;
    }

    /***********************************接口方法*********************************/

    @Override
    public ResourceResolverChain getResolverChain() {
        return this.resolverChain;
    }

    //借助资源转换器进行转换
    @Override
    public Resource transform(HttpServletRequest request, Resource resource) throws IOException {
        return (this.transformer != null && this.nextChain != null ?
                this.transformer.transform(request, resource, this.nextChain) : resource);
    }

}

典型的责任链模式,它将一系列的资源转换器构造为一个链条,每一个节点除了持有一个资源转换器,还持有一个指向下一个节点的引用。类似于Filter

9 PathExposingHandlerInterceptor

先来看一下它的类图

在这里插入图片描述

它就是一个拦截器,我们直接看源码,看看这个拦截器的作用

该类是AbstractUrlHandlerMapping类的内部类

/**
 * Special interceptor for exposing the
 * {@link AbstractUrlHandlerMapping#PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE} attribute.
 * @see AbstractUrlHandlerMapping#exposePathWithinMapping
 */
private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {

    /**
     * 对于静态资源请求来说,它就是资源匹配的路径
     * 例如<mvc:resources mapping="/js/**">
     * <mvc:resources mapping="/css/**">
     * 如果请求是以js开头的,那么它最匹配的路径就是"/js/**",也就是该字段的值
     */
    private final String bestMatchingPattern;

    /**
     * 对于静态资源请求来说,它就是截取掉请求路径中匹配的路径之后,剩下的路径
     * 比如js/**和js/test.js,那么它就是test.js
     */
    private final String pathWithinMapping;

    //构造方法,实例化对象的位置在4.1.1
    public PathExposingHandlerInterceptor(String bestMatchingPattern, String pathWithinMapping) {
        this.bestMatchingPattern = bestMatchingPattern;
        this.pathWithinMapping = pathWithinMapping;
    }

    //处理器执行之前调用
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        //将解析好的路径保存到请求域中,见9.1
        exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
        //将处理器对象也保存到请求域中
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, handler);
        //将是否支持类级别映射的结果也保存到请求域中
        request.setAttribute(INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
        return true;
    }

}

这个拦截器的主要作用就是将解析好的路径保存到请求域中,方便处理器使用

9.1 exposePathWithinMapping(String bestMatchingPattern, String pathWithinMapping,HttpServletRequest request)方法,将解析好的路径保存到请求域中

该方法在它的外部类AbstractUrlHandlerMapping

/**
 * Expose the path within the current mapping as request attribute.
 * @param pathWithinMapping the path within the current mapping
 * @param request the request to expose the path to
 * @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
 */
protected void exposePathWithinMapping(String bestMatchingPattern, String pathWithinMapping,
                                       HttpServletRequest request) {

    //最匹配的资源路径
    request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatchingPattern);
    //这个就是5.2.1中要用到的路径,非常重要
    request.setAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
}

该方法将和资源有关的两个路径都保存到请求域中,方便后续使用

9.2 supportsTypeLevelMappings()方法, 是否支持类级别映射

/**
 * Indicates whether this handler mapping support type-level mappings. Default to {@code false}.
 */
protected boolean supportsTypeLevelMappings() {
    return false;
}

默认是不支持类级别映射的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的springmvc-config.xml配置文件的例子: ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 开启注解扫描 --> <context:component-scan base-package="com.example.controller"/> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 配置静态资源处理 --> <mvc:resources mapping="/static/**" location="/static/"/> <!-- 配置RequestMappingHandlerAdapter --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/plain;charset=UTF-8</value> <value>text/html;charset=UTF-8</value> </list> </property> </bean> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> </list> </property> </bean> </list> </property> </bean> <!-- 配置RequestMappingHandlerMapping --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/> </beans> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值