SpringMVC在调用了Controller的方法后会返回ModelAndView对象,这个对象会被传回DispatcherServlet的doDispatch方法中。接下来再调用以下的方法渲染页面:
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { //view是否被正确设置的控制变量 boolean errorView = false; //如果前面的过程发生异常 if (exception != null) { //如果是ModelAndViewDefiningException异常 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); } } //请求处理器是否返回了一个正确的view给渲染器 if (mv != null && !mv.wasCleared()) { //开始渲染页面 render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } //省略代码若干... }
由上面的代码可以看出SpringMVC在检查请求处理过程中没有发生什么异常后就开始渲染页面了。
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { //将用户使用的Locale(区域语言信息)传给response Locale locale = this.localeResolver.resolveLocale(request); response.setLocale(locale); View view; //如果用户的controller的方法返回的是字符串即view name if (mv.isReference()) { // 查找view name对应的view并返回View对象 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() + "'"); } } //如果用户的controller的方法返回的是View对象则直接获取 else { 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() + "'"); } } if (logger.isDebugEnabled()) { logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); } //调用view的渲染函数 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; } }
这里有两个地方我们需要注意下就是View对象的查找过程和View对象的render过程。这里我们以jsp的渲染举例说明:
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; }
看到这里我们立刻明白是在我们注册的ViewResolver中挨个试探性的处理这个viewName,试探的过程就是在IOC容器中找viewName对应的View类型的bean,如果类型匹配返回这个bean,如果不匹配则返回null。看到这个viewResolvers我们立刻想到DispatcherServlet中的initStrategies方法,里面用initViewResolvers(context)初始化了这个成员变量。那么什么事viewResolver呢?我们以jsp为例来看看:
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/jsp/" /> <property name="suffix" value=".jsp" /> <property name="order" value="1" /> </bean>
这就是一个viewResolver,再来看看对应的InternalResourceView是怎么渲染的,它的render函数是在它的父类AbstractView中实现的。
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //创建整合后需要返回给浏览器的Model Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); //设置一些Response的头部信息 prepareResponse(request, response); //渲染数据 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }
InternalResourceView覆盖了父类的renderMergedOutputModel方法。
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //将model中的数据设置到request(因为不见得所有数据都是通过request.setAttribute加入的) exposeModelAsRequestAttributes(model, request); //本类中的此函数是空函数,留给子类比如JstlView去实现自定义逻辑 exposeHelpers(request); //设置跳转目的页面路径 String dispatcherPath = prepareForRendering(request, response); //获取跳转控制器RequestDispatcher RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } //决定跳转到另一个控制器即forward操作还是返回用户请求资源, //如果用户设置或配置INCLUDE_REQUEST_URI_ATTRIBUTE //属性或response已经提交数据则直接返回资源给用户 if (useInclude(request, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.include(request, response); } //携带request和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(request, response); } }
这一步完成后SpringMVC就基本上完成自己的使命了,当找到对应的url路径,springmvc就继续访问url对应的servlet,剩下的渲染工作就交由Servlet去处理了,所以由此我们不难看出,springmvc实际是在tomcat和servlet中间加了一层,处理request将浏览器传递的参数封装成model再由应用来加工处理这些数据后,再将这些数据传递给servlet让servlet将这些经过应用处理的数据在页面上表现出来,从而完成web请求,目的在于页面和数据分离即页面表现和业务逻辑的分离,使开发中可以集中精力处理业务逻辑提高开发效率。
Spring Transation和Spring JDBC我们就不具体分析了,感兴趣的看官可以自己下载份源码研究。Spring Framework的源码分析至此结束。