- 前言
- 第一部分 核心实现
- 第 1 章 Spring 整体架构和环境搭建
- 第 2 章 容器的基本实现
- 第 3 章 默认标签的解析
- 第 4 章 自定义标签的解析
- 第 5 章 bean 的加载
- 第 6 章 容器的功能扩展
- 第 7 章 AOP
- 第二部分 企业应用
- 第 8 章 数据库连接 JDBC
- 第 9 章 整合 MyBatis
- 第 10 章 事务
- 第 11 章 SpringMVC
- 第 12 章 远程服务
- 第 13 章 Spring 消息
11.4.9 根据视图跳转页面
无论是一个系统还是一个站点,最重要的工作都是与用户进行交互,用户操作系统后无论下发的命令成功与否都需要给用户一个反馈,以便于用户进行下一步的判断。所以,在逻辑处理的最后一定会涉及一个页面跳转的问题。
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse
response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// We need to resolve the view name.
view =resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException(
"Could not resolve view with name '" + mv.getViewName() + "'
in servlet with name '" +
getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains
a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name
'" + getServletName() + "'");
}
view.render(mv.getModelInternal(), request, response);
}
1.解析视图名称
在上文中我们提到 DispatcherServlet 会根据 ModelAndView 选择合适的视图来进行渲染,而这一功能就是在 resolveViewName 函数中完成的。
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
我们以 org.Springframework.web.servlet.view.InternalResourceViewResolver 为例来分析 ViewResolver 逻辑的解析过程,其中 resolveViewName 函数的实现是在其父类 AbstractCaching ViewResolver 中完成的。
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
//不存在缓存的情况下直接创建视图
return createView(viewName, locale);
}
else {
//直接从缓存中提取
Object cacheKey = getCacheKey(viewName, locale);
synchronized (this.viewCache) {
View view = this.viewCache.get(cacheKey);
if (view == null && (!this.cacheUnresolved || !this.viewCache.
containsKey(cacheKey))) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view != null || this.cacheUnresolved) {
this.viewCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
return view;
}
}
}
在父类 UrlBasedViewResolver 中重写了 createView 函数。
protected View createView(String viewName, Locale locale) throws Exception {
//如果当前解析器不支持当前解析器如 viewName 为空等情况
if (!canHandle(viewName, locale)) {
return null;
}
//处理前缀为 redirect:xx 的情况
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContext
Relative(), isRedirectHttp10Compatible());
return applyLifecycleMethods(viewName, view);
}
//处理前缀为 forward:xx 的情况
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale);
}
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}
protected View loadView(String viewName, Locale locale) throws Exception {
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass
(getViewClass());
//添加前缀以及后缀
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
//设置 ContentType
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
if (this.exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
return view;
}
通读以上代码,我们发现对于 InternalResourceViewResolver 所提供的解析功能主要考虑到了几个方面的处理。
基于效率的考虑,提供了缓存的支持。
提供了对 redirect:xx 和 forward:xx 前缀的支持。
添加了前缀及后缀,并向 View 中加入了必需的属性设置。
2.页面跳转
当通过 viewName 解析到对应的 View 后,就可以进一步地处理跳转逻辑了。
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse
response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request,
response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, request, response);
}
在引导示例中,我们了解到对于 ModelView 的使用,可以将一些属性直接放入其中,然后在页面上直接通过 JSTL 语法或者原始的 request 获取。这是一个很方便也很神奇的功能,但是实现却并不复杂,无非是把我们将要用到的属性放入 request 中,以便在其他地方可以直接调用,而解析这些属性的工作就是在 createMergedOutputModel 函数中完成的。
protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest
request,
HttpServletResponse response) {
@SuppressWarnings("unchecked")
Map<String, Object> pathVars = this.exposePathVariables ?
(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null;
// Consolidate static and dynamic model attributes.
int size = this.staticAttributes.size();
size += (model != null) ? model.size() : 0;
size += (pathVars != null) ? pathVars.size() : 0;
Map<String, Object> mergedModel = new HashMap<String, Object>(size);
mergedModel.putAll(this.staticAttributes);
if (pathVars != null) {
mergedModel.putAll(pathVars);
}
if (model != null) {
mergedModel.putAll(model);
}
// Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request,
response, mergedModel));
}
return mergedModel;
}
//处理页面跳转
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse
response) throws Exception {
// Determine which request handle to expose to the RequestDispatcher.
HttpServletRequest requestToExpose = getRequestToExpose(request);
//将 model 中的数据以属性的方式设置到 request 中
exposeModelAsRequestAttributes(model, requestToExpose);
// Expose helpers as request attributes, if any.
exposeHelpers(requestToExpose);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(requestToExpose, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web
application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(requestToExpose, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in Internal
ResourceView '" + getBeanName() + "'");
}
rd.include(requestToExpose, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
exposeForwardRequestAttributes(requestToExpose);
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in
InternalResourceView '" + getBeanName() + "'");
}
rd.forward(requestToExpose, response);
}
}
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论