SpringMVC
来源:互联网 发布:访问者模式 java 编辑:程序博客网 时间:2024/06/05 08:00
本篇主要讲述SpringMVC如何解析、渲染视图并转发返回结果对象。
请求处理方法执行完成后,最终返回一个ModelAndView对象。对于那些返回String,view或者ModelMap等类型的处理方法,SpringMVC也会在内部将他们装配成一个ModelAndView对象。
它包含了逻辑名和模型对象,其中的model可能为 { }空。
首先说明springmvc.xml中视图解析器配置注意事项:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"></property> <property name="suffix" value=".jsp"></property> </bean>
说明一下,如果前缀为 /WEB-INF/views/,那么后台return视图名”/success” or “success”均映射到 /WEB-INF/views/success.jsp ;
如果前缀 为 /WEB-INF/views,那么后台return视图名”/success”,将会映射到/WEB-INF/views/success.jsp;如果后台return视图名”success”,将会报错!
获取MV方法源码如下:
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; } ModelMap model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; }
SpringMVC借助视图解析器得到最终的视图对象,最终的视图对象可能是JSP或者Excel等等。
对于最终采用何种视图对象对模型数据进行渲染,处理器并不关心。处理器的工作重点聚焦在生产模型数据的工作上,从而实现MVC的充分解耦。
首先看几个概念:
【视图】
视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户;
为了实现视图模型和具体实现技术的解耦,Spring在org.springframwork.web.servlet包中定义了一个高度抽象的View接口
视图对象由视图解析器负责实例化,由于他们是无状态的,所以不存在线程安全的问题。
常见的视图实现类
JstlView extends InternalResourceView
【视图解析器】
SpringMVC为逻辑视图名的解析提供了不同的策略,可以在Spring Web 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。
每一种映射策略对应一个具体的视图解析器实现类。
视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。
所有的视图解析器都必须实现ViewResolver接口。
- 常见的视图解析器实现类
可以选择一种或多种视图解析器,可以通过其order属性指定解析器的优先顺序,order越小优先级越高。
SpringMVC会按照视图解析器顺序的优先次序进行解析,直到返回视图对象。若无,则抛出ServletException异常。
debug源码分析流程:
- 首先进入DispatcherServlet.doDispatch( )方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; //看这里,定义一个ModelAndView对象 Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //获取MV对象 } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } applyDefaultViewName(request, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } //处理转发结果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }
是不是觉得很简单?
只有三步,定义MV,实例化MV,转发!
看具体分析如下:
这里进行MV对象的实例化
// Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //获取MV对象
从上述方法到return SUCCESS
期间所经过的方法处理:
【就是为了实例化MV】
此时mv中model为空,view为success
进入转发结果处理方法,在该方法中进行视图渲染。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { //在这里进行视图渲染 render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isDebugEnabled()) { logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + "': assuming HandlerAdapter completed request handling"); } } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } }
进入视图渲染方法:
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 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() + "'"); } try { //这里进行真正的视图渲染 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'", ex); } throw ex; } }
进入视图对象解析方法:
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; }
可以看到视图解析器有两个(与SpringMVCxml配置一致):
SpringMVC.xml配置:
<!-- 配置视图解析器: 如何把 handler 方法返回值解析为实际的物理视图 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"></property> <property name="suffix" value=".jsp"></property> //其order属性默认为integer的最大值,故其他解析器只要定义值就可以 </bean> <!-- ****** 配置视图 BeanNameViewResolver 解析器: 使用视图的名字来解析视图 ************ --> <!-- 通过 order 属性来定义视图解析器的优先级, order 值越小优先级越高 --> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"> <property name="order" value="100"></property> </bean>
首先使用InternalResourceViewResolver:
此时得到的view对象:
可以看到拿到了name和转发的URL。
为何为JstlView而不是XML配置的InternalResourceViewResolver默认对应的InternlResourceView?
因为JSP页面使用fmt等JSTL标签!SpringMVC会自动使用InternlResourceView的子类 — JstlView !!!
拿到了视图之后,就该进行视图渲染了 !
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); }
如图上红线表明,先准备数据,再进行数据渲染。不过由于后台方法未传入model数据,所以此时mergedModel仍为空({ })!
不多说,进入renderMergedOutputModel( )方法!
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); // Expose the model object as request attributes. //这里很重要啊, exposeModelAsRequestAttributes(model, requestToExpose); //看到没,你的model在request中以("key":value)形式存在 // 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 InternalResourceView '" + getBeanName() + "'"); } rd.include(requestToExpose, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.forward(requestToExpose, response); } }
方法里面第二句:
exposeModelAsRequestAttributes(model, requestToExpose);
看说明如下:
Expose the model objects in the given map as request attributes. Names will be taken from the model Map. This method is suitable for all resources reachable by javax.servlet.RequestDispatcher.
意思是说,把map中的数据以(attr : vlaue)的形式曝光给request,你在页面可以使用 诸如${requestScope.attr}
的形式获取value!!!
拿到RequestDispatcher:
RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
转发!!!
rd.forward(requestToExpose, response);
之后的事情,JSP解析等等,就是服务器的事情了!!!
Tips:
视图的名字(success)是MV给的,视图里面的model(数据)也是MV给的,MV是Dispatcher给的,视图的URL是视图解析器给的(将逻辑视图success 转换为真实的物理视图 /WEB-INF/views/success.jsp)!!
- SpringMVC
- springmvc
- SpringMVC
- SpringMVC
- springMvc
- springMVC
- springmvc
- springMVC
- springMVC
- springmvc
- SpringMVC
- SpringMVC
- springMvc
- springmvc
- springmvc
- SpringMVC
- Springmvc
- springmvc
- VLC windows Cygwin 编译v2.2.1
- SoundPool定义音乐播放池
- C#开发微信门户及应用(1)--开始使用微信接口
- 区分clientX、offsetX、pageX、screenX
- MySQL 技巧
- SpringMVC
- SQL 结构化查询语言
- BZOJ 1968:[Ahoi2005]COMMON 约数研究
- 通过Application配置全局的Context
- 融云集成1-SDK导入
- Android之 DrawerLayout不覆盖主内容
- FreeSWITCH 限制音视频会议最大参会人数
- 每天一个 Linux 命令(6):rmdir 命令------学习笔记
- 畅通工程之最低成本建设问题