SpringMVC 绝对路径和相对路径源码分析
温馨提示:
本文最后更新于 2022年10月27日,已超过 964 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我。
1. 前言
我们在使用java做web开发,进行页面跳转时,一般都直接使用页面的名称进行匹配。springboot-web已经帮我们自动配置好了mvc相关的配置。
//测试接口
@RequestMapping
@Controller
public class TestController {
//此处返回index页面没有携带/
@GetMapping("/hello/kugou")
public String index(){
return "index";
}
}
#yml基础配置
spring:
mvc:
view:
suffix: .html #配置后缀
//pom只用了基础的spring-web
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
springboot版本2.x+,使用以上的基础配置,在浏览器进行访问127.0.0.1:8080/hello/kugou时,按照我们的想法应该是正常返回默认静态路径下的index页面。
classpath:/static
classpath:/public
classpath:/resources
classpath:/META-INF/resources
但是结果却是404!!!
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed Jun 29 15:38:03 CST 2022
There was an unexpected error (type=Not Found, status=404).
2.分析
我们都知道MVC的请求流程第一步,前端发起的请求都会被DispatcherServlet拦截,所以将断点打在doDispatch方法中
... if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler(实际调用处理器) //处理请求,得到模型视图 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); } ...
调用处理映射器,获取到对应的处理handler mappedHandler.getHandler() ==》 com.wu.springlearn.controller.TestController#index()
处理适配器ha(HandlerAdapter)进行处理,得到模型视图mv(ModelAndView) ha.handle ==》ModelAndView [view="index"; model={}]
得到模型视图后进行解析 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
由于解析视图后出现问题,所以直接进入解析视图的方法进行查找。
//1. org.springframework.web.servlet.DispatcherServlet#processDispatchResult private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { //渲染模型视图 render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isTraceEnabled()) { logger.trace("No view rendering, null ModelAndView returned."); } } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } if (mappedHandler != null) { // Exception (if any) is already handled.. mappedHandler.triggerAfterCompletion(request, response, null); } } //2. 渲染render(mv, request, response); org.springframework.web.servlet.DispatcherServlet#render protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine locale for request and apply it to the response. Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); View view; //获取当前视图名 String viewName = mv.getViewName(); if (viewName != null) { // We need to resolve the view name. //解析视图 ==》 org.springframework.web.servlet.view.InternalResourceView: name 'index'; URL [index.html] view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { // No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } // Delegate to the View object for rendering. if (logger.isTraceEnabled()) { logger.trace("Rendering view [" + view + "] "); } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } //视图渲染 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; } } //3. 视图渲染 org.springframework.web.servlet.view.AbstractView#render @Override public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { logger.debug("View " + formatViewName() + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes)); } //创建输出模型 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); //预处理返回结果 prepareResponse(request, response); //渲染合并输出模型 =》由于前两步都没问题,所以直接进入该方法继续 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); } //4. 渲染合并输出模型renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); //org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose the model object as request attributes. //将model里的值放到request中,这也是为什么我们在页面能直接使用model里面的参数的原因 exposeModelAsRequestAttributes(model, request); // Expose helpers as request attributes, if any. exposeHelpers(request); // Determine the path for the request dispatcher. //确认请求调度程序的路径 String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). //获取当前请求的调度器 //不带/ ==>requestURI=/hello/index.html 带/ ==>requestURI=/index.html RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); ...... } //5. 获取调度器 @Override public RequestDispatcher getRequestDispatcher(String path) { Context context = getContext(); if (context == null) { return null; } if (path == null) { return null; } int fragmentPos = path.indexOf('#'); if (fragmentPos > -1) { log.warn(sm.getString("request.fragmentInDispatchPath", path)); path = path.substring(0, fragmentPos); } // If the path is already context-relative, just pass it through //如果path是以/开头,则返回由当前path生成的调度器 if (path.startsWith("/")) { return context.getServletContext().getRequestDispatcher(path); } /* 绝对路径和相对路径的解释 * From the Servlet 4.0 Javadoc: * - The pathname specified may be relative, although it cannot extend * outside the current servlet context. * - If it is relative, it must be relative against the current servlet * * From Section 9.1 of the spec: * - The servlet container uses information in the request object to * transform the given relative path against the current servlet to a * complete path. * * It is undefined whether the requestURI is used or whether servletPath * and pathInfo are used. Given that the RequestURI includes the * contextPath (and extracting that is messy) , using the servletPath and * pathInfo looks to be the more reasonable choice. */ // Convert a request-relative path to a context-relative one //将请求相对路径转换为上下文相对路径 String servletPath = (String) getAttribute( RequestDispatcher.INCLUDE_SERVLET_PATH); if (servletPath == null) { servletPath = getServletPath(); } // Add the path info, if there is any String pathInfo = getPathInfo(); String requestPath = null; if (pathInfo == null) { requestPath = servletPath; } else { requestPath = servletPath + pathInfo; } int pos = requestPath.lastIndexOf('/'); String relative = null; if (context.getDispatchersUseEncodedPaths()) { if (pos >= 0) { relative = URLEncoder.DEFAULT.encode( requestPath.substring(0, pos + 1), StandardCharsets.UTF_8) + path; } else { relative = URLEncoder.DEFAULT.encode(requestPath, StandardCharsets.UTF_8) + path; } } else { if (pos >= 0) { relative = requestPath.substring(0, pos + 1) + path; } else { relative = requestPath + path; } } //返回相对路径生成的调度器 return context.getServletContext().getRequestDispatcher(relative); }
3. 总结
当我们的视图(index)返回带/ 即 “/index”,调度器返回的是绝对路径。
当我们的视图(index)返回不带/ 即 “index”,调度器返回的是相对路径,会将我们的请求的URI进行拼接重组。
正文到此结束
- 本文标签: Spring Boot SpringMVC
- 本文链接: https://www.tianyajuanke.top/article/41
- 版权声明: 本文由吴沛芙原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权