为了解决前端 SPA 与 Spring 的结合问题,引入了一段代码,主要功能是使所有非 /api/** 形式的请求都映射到 index.html。

基本逻辑是重写  WebMvcConfigurer 的 addResourceHandlers 方法。填入一个实现了 ResourceResolver 的类。此类重写了如下两个方法。

/* 此处解析静态路径 */
Resource resolveResource(@Nullable HttpServletRequest request, String requestPath,
			List<? extends Resource> locations, ResourceResolverChain chain);

String resolveUrlPath(String resourcePath, List<? extends Resource> locations, ResourceResolverChain chain);

对这两个方法内的请求路径分别进行拦截,如果不符合 /api/** 的形式,就返回对应的静态资源或者是 index.html 。

虽然是抄来的代码用起来也挺正常。但是后边发现如果 /api/** 的 rest 请求如果未找到对应方法,不返回 404,而是直接返回 index.html。这明显不符合预期的效果。

private Resource resolve(String requestPath, List<? extends Resource> locations) {
  if (isIgnored(requestPath)) {
    return null;
  }
  if (isHandled(requestPath)) {
    return locations.stream()
        .map(loc -> createRelative(loc, requestPath))
        .filter(resource -> resource != null && resource.exists())
        .findFirst()
        .orElse(null);
  }
  return index;
}

private Resource createRelative(Resource resource, String relativePath) {
  try {
    return resource.createRelative(relativePath);
  } catch (IOException e) {
    return null;
  }
}

private boolean isIgnored(String path) {
  return ignoredPaths.contains(path);
}

private boolean isHandled(String path) {
  String extension = StringUtils.getFilenameExtension(path);
  return handledExtensions.stream().anyMatch(ext -> ext.equals(extension));
}

分析流程解析如下问题:

  1. ResourceResovler 如何生效?
  2. 请求经过 RequestMapping 和 ResourceResolver 的顺序?

提出问题之后突然有更多的问题。

例如 Component 扫描的流程、各类配置的加载流程、Boot 配置方式、DispatcherServlet 实现等。只能通过日志分析一些细节。

在 ResourceHandlerRegistry 的 getHandlerMapping 这个方法中,返回了一个 SimpleUrlHandlerMapping 的实例。HandlerMapping 是 存在与 DispatcherServlet 中的一个接口,用于映射 Url 和 Handler。

这部分应该是初始化的一个路径。

另外通过 debug 日志的分析,一般基于 RequestMapping 注解绑定的 Handler,也就是 RequestMappingHandlerMapping,解析顺序早于以上的 SimpleUrlHandlerMapping。如果这两个 Mapping 走完没有返回 404 而是一个 Resource,那么问题应该就在上面的代码中。

打了两行日志发现其实 isIgnored 函数写的并不正确。实际上应该这样:

private boolean isIgnored(String path) {
  return ignoredPaths.stream()
      .map(path::startsWith).reduce((one, more) -> one || more).orElse(false);
}

前边留了一堆关于 Spring 的疑问,留在这里有时间继续往下写。



  • No labels