为了解决前端 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)); }
分析流程解析如下问题:
- ResourceResovler 如何生效?
- 请求经过 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 的疑问,留在这里有时间继续往下写。