天天看點

12.SpringMVC 視圖解析 - View

View

即視圖,負責頁面的渲染。接口定義如下:

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

再來看它的繼承關系(部分顯示):

12.SpringMVC 視圖解析 - View

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() : );
    size += (pathVars != null ? pathVars.size() : );

    // 建立 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);
}
           

繼續閱讀