接着来继续解开我们一开始的疑问:DispatcherServlet为什么是唯一的入口呢?它是如何被设计为唯一入口的?这就是我所抱有的疑问,为什么。。。
进过三篇文章的了解,我们是不是也对doDispatch这个方法有了一个新的认识。
那么,你有没有注意到在这个非常重要的方法中有两个异常处理,而且是嵌套的。有想过它为什么这样设计吗?
我们了解下这两个异常处理的作用,第一个异常是处理渲染页面抛出,第二个异常也就是内部异常,它把执行请求处理时的异常设置到dispatchException变量,然后在proccessDispatchResult方法中进行处理。而外层异常则处理processDispatchResult方法抛出的异常。
那么我们看下processDispatchResult方法。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable 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.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
观察上面的代码,我们可以看到其实它处理异常的方式就是将相应的错误页面设置到View中。而它的渲染页面在render方法中执行。
接着看下render方法,
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 != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, 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.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
通过仔细看上面的代码,你就会发现render是首先对response设置了Local,过程中使用到了LocalResolver,然后判断View如果是String类型则调用resolveViewName方法使用ViewResolve得到实际的View,最后调用View的render方法对页面进行具体渲染,渲染过程中使用到了ThemeResolver。
好了,到了这里,我们返回上一个方法processDispatchResult,接着render下去。
最后到了通过mappedHandler的triggerAfterCompletion方法触发Interceptor的afterCompletion方法,注意这里的Interceptor也是按反方向执行的。
接着返回上一个方法doDispatch,到了finally方法块。这里也就是判断是否请求异步处理,如果启动了则调用相应异步处理的拦截器,否则如果是上传请求则删除上传请求过程中产生的临时资料。
到了这里,我们的doDispatch的处理流程也就讲完了。我们也看到Spring MVC的处理方式是先在顶层设计好整体结构,之后将具体的处理交给不同的组件具体去实现。
那么到了这里,我们也就走完了两个嵌套循环。你是否发现了什么呢?
渲染页面、执行请求。。。
我们看下doDispatch的执行过程吧,
我们了解到嵌套的异常处理是由内向外的,也就是说代码是有顺序性。至于其他的考虑因素应该也有,但奈何知识浅薄,只能分析到此。
好了,到了这里,我们已经分析完了doDispatch。但是我们的疑问:DispatcherServlet为什么是唯一的入口呢?它是如何被设计为唯一入口的?这就是我所抱有的疑问,为什么。。。
我们解开了吗。。。
如果那里不对希望多多指教,不胜感激。
参考资料:
看透springmvc源代码分析与实践 第10章 Spring MVC之用