对于srpingmvc配置文件mvc:resources的处理解析

我们知道,当想要请求一个资源文件,而不是跳到controller中匹配映射时,我们需要在springmvc的xml文件中设置<mvc:resources/>这个标签。

本文将以<mvc:resources mapping="/images/**" location="file:///D:/images/"/>为例,来讲解springmvc对于资源文件的执行过程。

比如通过上面的设置,当我们在页面上通过http://localhost:8088/CipSystem/images/image/20200422/20200422091811_670.png来请求一个图片时,springmvc会到D:/images/下去找对应的图片进行显示。而不会跳转到controller中去进行映射,那么代码的处理过程如何呢,如下:

当请求发送时,实现会被ResourceHttpRequestHandler的handleRequest方法处理,如下:

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

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

    // For very general mappings (e.g. "/") we need to check 404 first
    Resource resource = getResource(request);
    if (resource == null) {
        logger.trace("No matching resource found - returning 404");
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

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

    // Supported methods and required session
    checkRequest(request);

    // 根据我们图片的请求,来设置一个图片的响应,如:
    if (new ServletWebRequest(
          request,response).checkNotModified(resource.lastModified())) {
        logger.trace("Resource not modified - returning 304");
        return;
    }

    // Apply cache settings, if any
    prepareResponse(response);

    // 根据请求的head,来设置响应的类型,如这里将响应的head设置image/png,
    // 表示将返回一个png图片
    MediaType mediaType = getMediaType(request, resource);
       
    // Content phase
    if (METHOD_HEAD.equals(request.getMethod())) {
        setHeaders(response, resource, mediaType);
        logger.trace("HEAD request - skipping content");
        return;
    }

    ServletServerHttpResponse outputMessage = new 
    ServletServerHttpResponse(response);
    // 将我们的图片作为响应返回到页面中
    if (request.getHeader(HttpHeaders.RANGE) == null) {
        // 设置response中head的内容,比如Content-Length,ContentType
        setHeaders(response, resource, mediaType);
        // 通过流的形式将图片输出到页面上
        this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
    }
    else {
        response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
        ServletServerHttpRequest inputMessage = new 
        ServletServerHttpRequest(request);
        try {
            List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            if (httpRanges.size() == 1) {
                ResourceRegion resourceRegion = httpRanges.get(0).
                                                toResourceRegion(resource);
                this.resourceRegionHttpMessageConverter.write(
                                      resourceRegion, mediaType, outputMessage);
            } else {
                this.resourceRegionHttpMessageConverter.write(
                    HttpRange.toResourceRegions(httpRanges, resource), 
                    mediaType, outputMessage);
            } catch (IllegalArgumentException ex) {
              response.setHeader("Content-Range", "bytes */" + 
                                         resource.contentLength());                
              response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
            }
        }
    }

protected Resource getResource(HttpServletRequest request) throws IOException {
    // 从请求中后去请求资源的path	,如image/20200422/20200422091811_670.png	
    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进行处理,将连续的/变为一个/
    path = processPath(path);
	if (!StringUtils.hasText(path) || isInvalidPath(path)) {
		return null;
	}
	if (path.contains("%")) {
		try {
			// Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
			if (isInvalidPath(URLDecoder.decode(path, "UTF-8"))) {
				return null;
			}
		}catch (IllegalArgumentException ex) {
				// ignore
			}
		}
		ResourceResolverChain resolveChain = new 
                               DefaultResourceResolverChain(getResourceResolvers());
        // 根据path和xml中配置的location通过PathResourceResolver类来成Resource,代码如下
		Resource resource = resolveChain.resolveResource(request, path, getLocations());
		if (resource == null || getResourceTransformers().isEmpty()) {
			return resource;
		}
		ResourceTransformerChain transformChain =
				new DefaultResourceTransformerChain(
                                resolveChain, getResourceTransformers());
		resource = transformChain.transform(request, resource);
		return resource;
	}
}

Resource是如何生成的呢?在调用Resource resource = resolveChain.resolveResource(request, path, getLocations());时,世界上是最终调用了PathResourceResolver的resolveResourceInternal方法,如下:

public class PathResourceResolver extends AbstractResourceResolver {

  private Resource[] allowedLocations;
    
  @Override
  protected Resource resolveResourceInternal(HttpServletRequest request,
                                             String requestPath,
                                             List<? extends Resource> locations, 
                                             ResourceResolverChain chain) {
	return getResource(requestPath, locations);
  }

/**
*
*/
private Resource getResource(String resourcePath, List<? extends Resource> locations) {
	for (Resource location : locations) {
		try {
			if (logger.isTraceEnabled()) {
				logger.trace("Checking location: " + location);
			}
			Resource resource = getResource(resourcePath, location);
		catch (IOException ex) {}
	}
		return null;
}

/**
* 
*/
protected Resource getResource(String resourcePath, Resource location) throws IOException {        
    // 通过url(我们的location指定的file:/D:/images/)和 
    // resourcePath(image/20200422/20200422091811_670.png)生成一个UrlResource对象
	// Resource resource = location.createRelative(resourcePath);
    // resource.exists():判断是不是file协议,如果是将new一个file,然后通过    
    // AbstractFileResolvingResource的exists和isReadable方法判断这个是否存在是否可读。
	if (resource.exists() && resource.isReadable()) {
         // 判断resource是否合法
      if (checkResource(resource, location)) {
		 return resource;
	  }else if (logger.isTraceEnabled()) {}
		return null;
	}

}

上面基本上就是关于设置<mvc:resource/>后,程序内部执行的核心逻辑。关于springboot内部是否是这样的,暂时未知。

总结:

还是以<mvc:resources mapping="/images/**" location="file:///D:/images/"/>为例,这个location到底可以写什么呢?看下图

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章