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的源码分析至此结束。