View
即視圖,負責頁面的渲染。接口定義如下:
public interface View {
void render(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception;
}
再來看它的繼承關系(部分顯示):
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM2gTM0QTNxIjNxgDM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
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);
}