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);
}