SpringBoot MVC(7)视图的处理

373 阅读2分钟

视图的返回

在前后端不分离的项目中,返回视图还是很常见的,虽然现在这样的项目不多了,但作为曾经的主流,还是有必要去了解一下的。

RequestMappingHandlerAdapter->invokeHandlerMethod():
    // 这一步是绑定参数、验证参数、调用controller方法、处理返回值的过程
    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    return getModelAndView(mavContainer, modelFactory, webRequest);
RequestMappingHandlerAdapter->getModelAndView():
    // 如果存在@ResponseBody注解的情况,这边直接返回了
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    ModelMap model = mavContainer.getModel();
    // 获取返回的视图
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    // 如果返回值不是字符串类型的
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    return mav;

视图的处理

DispatcherServlet->doDispatch():
    // 如果返回为空的话,设置视图名称为请求的url
    applyDefaultViewName(processedRequest, mv);
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
DispatcherServlet->processDispatchResult():
    // 这里就是对应视图的呈现,其中,mv即ModelAndView对象
    render(mv, request, response);

下面的resolveViewName方法中,不同的返回值对应的view类不一样,其render方法也不尽相同,下面会对常用的视图作举例,至于其他的可以自行研究

InternalResourceView

对应的是返回普通字符串或者类

DispatcherServlet->render():
    ......
    // 获取视图名称
    String viewName = mv.getViewName();
    if (viewName != null) {
        // 对视图名称进行解析,获取视图
        // 因为返回的是字符串,所以这里的view是InternalResourceView类,它是处理JSP的
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    }
    ......
    // InternalResourceView的render()方法是在其父类AbstractView实现的
    view.render(mv.getModelInternal(), request, response);
AbstractView->render():
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
InternalResourceView->renderMergedOutputModel():
    // 将model的属性放到request属性中,便于后续使用
    exposeModelAsRequestAttributes(model, request);
    // 获取分发的路径,需要注意的是,如果不以'/'符号开头,会截取同一路径下的
    String dispatcherPath = prepareForRendering(request, response);
    // 获取目标资源的RequestDispatcher(通常是JSP)
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    // 页面跳转,获取到相应的资源
    rd.forward(request, response);

从代码可以看出,不同的返回类型跳转的资源路径是不一样的,假设当前请求的是/hello/test,根据不同的返回跳转资源如下。另外跳转在同一个http请求中处理,因此request和response都是同一个对象

返回跳转资源
c.html/hello/c.html
/c.html/c.html
类类型/hello/hello/test

RedirectView

对应的是返回redirect:开头的字符串,意为重定向。重定向的状态码是302,同时它会在响应头中添加Location的属性,值一般为url,最终交由调用该url的返回结果决定。可见,重定向相当于又发送了一次http请求

RedirectView->renderMergedOutputModel():
    // 执行的是重定向的方法
    sendRedirect(request, response, targetUrl, this.http10Compatible);

FreemarkerView

当视图名称在配置文件下有相对应的ftl文件时,使用此视图,需要添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

配置的默认属性在FreeMarkerProperties下,默认后缀名是.ftl,默认资源路径在/templates目录下

如下图,在浏览器中输入"localhost:8080/hello/test"即可渲染test.ftl,并输出到页面