12.SpringMVC 视图解析 - View

View

即视图,负责页面的渲染。接口定义如下:

public interface View {
    void render(Map<String, ?> model, HttpServletRequest request, 
        HttpServletResponse response) throws Exception;
}

再来看它的继承关系(部分显示):

这里写图片描述

AbstractView

该类是 View 接口的简单抽象类,重点来看 render 方法:

public void render(Map<String, ?> model, HttpServletRequest request, 
    HttpServletResponse response) throws Exception {

    // 省略代码...

    // 1.创建合并模型
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);

    // 2.准备响应,为了解决 IE 通过 HTTPS 下载 Bug
    prepareResponse(request, response);

    // 3.空方法,表示真正的视图渲染
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

createMergedOutputModel ,创建合并模型,并将相关属性都添加到该模型中,以便最后一起推送到页面。

String PATH_VARIABLES = View.class.getName() + ".pathVariables";

protected Map<String, Object> createMergedOutputModel(Map<String, ?> model,
    HttpServletRequest request,HttpServletResponse response) {

    // 取得 request 中 与 View 有关的指定属性 
    @SuppressWarnings("unchecked")
    Map<String, Object> pathVars = (this.exposePathVariables ?
        (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

    // 计算 mergedModel 的 size 
    int size = this.staticAttributes.size();
    size += (model != null ? model.size() : 0);
    size += (pathVars != null ? pathVars.size() : 0);

    // 创建 mergedModel 
    Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);

    // 添加 staticAttributes、pathVars、、ModelMap、requestContextAttribute 到集合
    mergedModel.putAll(this.staticAttributes);
    if (pathVars != null) {
        mergedModel.putAll(pathVars);
    }
    if (model != null) {
        mergedModel.putAll(model);
    }
    if (this.requestContextAttribute != null) {
        mergedModel.put(this.requestContextAttribute, 
            createRequestContext(request, response, mergedModel));
    }

    return mergedModel;
}

InternalResourceView

该类是 AbstractView 的子类。并重写了 renderMergedOutputModel 方法:

protected void renderMergedOutputModel(Map<String, Object> model, 
    HttpServletRequest request, HttpServletResponse response) throws Exception {

    // 1.将 model 添加到 request 的属性
    exposeModelAsRequestAttributes(model, request);

    // 2.将 helper 添加到 request 的属性
    exposeHelpers(request);

    // 3.取得要渲染的视图地址
    String dispatcherPath = prepareForRendering(request, response);

    // 4.创建 RequestDispatcher,用于 request 的 forward/include  
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
        // 抛出异常...
    }

    // 5.判断是 request 使用 forward/include
    if (useInclude(request, response)) {
        response.setContentType(getContentType());

        // 省略代码...

        rd.include(request, response);

    }else {
        // 省略代码...

        rd.forward(request, response);
    }
}

观察代码,它的整个流程如下:

  • 1.将 model 添加到 request 的属性
  • 2.将 helper 添加到 request 的属性
  • 3.取得要渲染的视图地址
  • 4.创建 RequestDispatcher
  • 5.判断是 request 使用 forward/include,并执行相应的动作

1.将 model 添加到 request 的属性

protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
    // 遍历 model 的 key
    for (Map.Entry<String, Object> entry : model.entrySet()) {
        String modelName = entry.getKey();
        Object modelValue = entry.getValue();

        // key 对应的 value 不为空,则添加到 request
        if (modelValue != null) {
            request.setAttribute(modelName, modelValue);

            // 省略部分代码...

        }else {
            // 为空,则从 request 中移除 
            request.removeAttribute(modelName);

            // 省略部分代码...
        }
    }
}

2.将 helper 添加到 request 的属性

exposeHelpers 在该类中是空方法,留给子类实现。


3.取得要渲染的视图地址

private boolean preventDispatchLoop = false;

protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
    throws Exception {

    // 页面文件存放地址
    String path = getUrl();

    if (this.preventDispatchLoop) {
        // 请求地址
        String uri = request.getRequestURI();
        if (path.startsWith("/") ? uri.equals(path) : 
            uri.equals(StringUtils.applyRelativePath(uri, path))) {
            // 抛出异常...
        }
    }

    return path;
}

4.创建 RequestDispatcher

创建 RequestDispatcher ,用于请求的重分发。

protected RequestDispatcher getRequestDispatcher(HttpServletRequest request, String path) {
    // 请求重分发
    return request.getRequestDispatcher(path);
}

来看下 RequestDispatcher 接口,它与 request 的 forward/include 方法有关。


public interface RequestDispatcher {

    public void forward(ServletRequest request, ServletResponse response)
        throws ServletException, IOException;

    public void include(ServletRequest request, ServletResponse response)
        throws ServletException, IOException;
}

我们知道 request 在执行重分发时,URL 地址不会改变。而 forward/include 之前的差异是:

  • 调用 forward,有关 response 对象的一切方法或者属性都会失去作用,只有 request 能被转向到下一个页面。

  • 调用 include,response 跟 request 都能被传递到转向的下一个页面。


5.判断是 request 使用 forward/include

private boolean alwaysInclude = false;

protected boolean useInclude(HttpServletRequest request, HttpServletResponse response) {
    return (this.alwaysInclude || 
        WebUtils.isIncludeRequest(request) || 
        response.isCommitted());
}

// WebUtils
public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = 
    "javax.servlet.include.request_uri";

public static boolean isIncludeRequest(ServletRequest request) {
    return (request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE) != null);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oxf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值