Spring Boot--带着问题看源码2--实现WebMvcConfigurer接口addResourceHandlers方法,无法访问外部资源

环境

Spring Boot:2.2.6

代码实现

  • 实现类
@Configuration
public class StaticResourceConfiguration implements WebMvcConfigurer {
    @Value("${external.resource.path.type.a}")
    private String externalResourcePathTypeA;
    @Value("${external.resource.path.type.b}")
    private String externalResourcePathTypeB;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/typea/**").addResourceLocations(externalResourcePathTypeA);
        registry.addResourceHandler("/typeb/**").addResourceLocations(externalResourcePathTypeB);
    }
}
  • 配置文件 application.properties
external.resource.path.type.a=file:E:/test/typea
external.resource.path.type.b=file:E:/test/typeb
  • 本地文件夹
E:\test
├─typea
│  └─1
│          1.png
│
└─typeb
    └─04e417e185c64f00b58ef4b1f18defeb
            2.png

问题:无法访问到图片文件

image-20200408170200560

改错

  • 文件路径最后加上"/"即可
external.resource.path.type.a=file:E:/test/typea/
external.resource.path.type.b=file:E:/test/typeb/

原因调查

  • 控制台报错
[nio-8080-exec-3] o.s.w.s.DispatcherServlet                : GET "/typea/1/1.png", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'
[nio-8080-exec-3] o.s.w.s.h.SimpleUrlHandlerMapping        : Matching patterns [/typea/**, /**]
[nio-8080-exec-3] o.s.w.s.h.SimpleUrlHandlerMapping        : Mapped to HandlerExecutionChain with [ResourceHttpRequestHandler ["file:E:/test/typea"]] and 3 interceptors
[nio-8080-exec-3] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found
[nio-8080-exec-3] o.s.w.s.DispatcherServlet                : No view rendering, null ModelAndView returned.
  • 查看ResourceHttpRequestHandler源码,在idea中任意一个类粘贴这个类型,然后ctrl+鼠标左键

image-20200408171113366

  • 根据错误信息"Resource not found"查询关键代码,只匹配到一处
@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.debug("Resource not found");
      response.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
   }
   //...
}
  • 查看Resource resource = getResource(request); 为什么为null
@Nullable
protected Resource getResource(HttpServletRequest request) throws IOException {
   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);
   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.");

   Resource resource = this.resolverChain.resolveResource(request, path, getLocations());
   if (resource != null) {
      resource = this.transformerChain.transform(request, resource);
   }
   return resource;
}
  • 在Resource resource = this.resolverChain.resolveResource打断点,一路F7跟进去,到PathResourceResolver
@Nullable
private Resource getResource(String resourcePath, @Nullable HttpServletRequest request,
                             List<? extends Resource> locations) {
    for (Resource location : locations) {
        try {
            String pathToUse = encodeIfNecessary(resourcePath, request, location);
            Resource resource = getResource(pathToUse, location);
            if (resource != null) {
                return resource;
            }
        }
        catch (IOException ex) {
            if (logger.isDebugEnabled()) {
                String error = "Skip location [" + location + "] due to error";
                if (logger.isTraceEnabled()) {
                    logger.trace(error, ex);
                }
                else {
                    logger.debug(error + ": " + ex.getMessage());
                }
            }
        }
    }
    return null;
}
  • 进入Resource resource = getResource(pathToUse, location)还是在PathResourceResolver
@Nullable
protected Resource getResource(String resourcePath, Resource location) throws IOException {
   Resource resource = location.createRelative(resourcePath);
   if (resource.isReadable()) {
      if (checkResource(resource, location)) {
         return resource;
      }
      else if (logger.isWarnEnabled()) {
         Resource[] allowedLocations = getAllowedLocations();
         logger.warn("Resource path \"" + resourcePath + "\" was successfully resolved " +
               "but resource \"" +    resource.getURL() + "\" is neither under the " +
               "current location \"" + location.getURL() + "\" nor under any of the " +
               "allowed locations " + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]"));
      }
   }
   return null;
}
  • F7进入Resource resource = location.createRelative(resourcePath);直到UrlResource
protected URL createRelativeURL(String relativePath) throws MalformedURLException {
   if (relativePath.startsWith("/")) {
      relativePath = relativePath.substring(1);
   }
   // # can appear in filenames, java.net.URL should not treat it as a fragment
   relativePath = StringUtils.replace(relativePath, "#", "%23");
   // Use the URL constructor for applying the relative path as a URL spec
   return new URL(this.url, relativePath);
}
  • 打个断点看看返回值

image-20200408173352097

image-20200408173437025

  • 最后跟到URLStreamHandler,在parseURL方法中,判断path(E:/test/typea)最后一个"/"的位置并截取之前的字符串,然后拼接上"/"和spec(1/1.png),最后resource的path为E:/test/1/1.png,而实际文件路径为E:/test/typea/1/1.png
protected void parseURL(URL u, String spec, int start, int limit) {
	//...
    // Parse the file path if any
    if (start < limit) {
        if (spec.charAt(start) == '/') {
            path = spec.substring(start, limit);
        } else if (path != null && path.length() > 0) {
            isRelPath = true;
            int ind = path.lastIndexOf('/');
            String seperator = "";
            if (ind == -1 && authority != null)
                seperator = "/";
            path = path.substring(0, ind + 1) + seperator +
                spec.substring(start, limit);

        } else {
            String seperator = (authority != null) ? "/" : "";
            path = seperator + spec.substring(start, limit);
        }
    }
    //...
}

URL为什么会有这个操作?

image-20200408175136053

  • java.net.URL类的说明上最后一句, For a more detailed description of URL parsing, refer to RFC2396
a) All but the last segment of the base URI's path component is
   copied to the buffer.  In other words, any characters after the
   last (right-most) slash character, if any, are excluded.

猜你喜欢

转载自www.cnblogs.com/wxyzyu/p/12664043.html