1.先来看下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;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 为当前请求确定 Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获取到 HandlerAdapter
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()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用过滤器的 preHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用目标方法,拿到 ModelAndView 对象,此对象封装了视图名和响应数据
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 调用过滤器的 postHandle() 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 视图解析,此方法的最后将调用过滤器的 afterCompletion() 方法,下面将以此方法为入口深入
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 触发过滤器的 afterCompletion() 方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// 触发过滤器的 afterCompletion() 方法
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
2.接下来从DispatcherServlet类的processDispatchResult()方法深入
2.1 processDispatchResult()方法
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
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.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) {
// 触发过滤器的 afterCompletion() 方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
2.2 render()方法
/**
* 渲染给定的ModelAndView。
* 这是处理请求的最后阶段。它可能涉及按名称解析视图。
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws ServletException if view is missing or cannot be resolved
* @throws Exception if there's a problem rendering the view
*/
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) {
// 解析视图名, 获得视图对象 View
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.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// 调用视图 View 对象的 render() 方法渲染视图
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;
}
}
2.3 resolveViewName() 拿到所有的视图解析器尝试解析,得到View对象
/**
* 将给定的视图名称解析为View对象(要渲染)。默认实现会询问程序的所有ViewResolvers。
*/
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
// 拿到所有的视图解析器,解析视图,一单返回的view对象不为空,则停止解析,直接返回
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
3.解析视图
3.1 ViewResolver 接口
- 视图解析器均实现自ViewResolver 接口,其中有一个resolveViewName()方法
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
3.2下面以InternalResourceViewResolver来介绍下resolveViewName()方法
先看下继承关系
- InternalResourceViewResolver extends UrlBasedViewResolver
- UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered
- class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver
先看下AbstractCachingViewResolver中的方法resolveViewName()
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
return createView(viewName, locale);
}
else {
// 获取key
Object cacheKey = getCacheKey(viewName, locale);
// 先尝试从缓存中获取
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
// 如果没有获取到,则在同步代码块创建,并将其缓存
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// 调用子类重写的方法来创建 View 对象
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
下面接着看UrlBasedViewResolver 类中重写的createView()方法
@Override
protected View createView(String viewName, Locale locale) throws Exception {
//如果此解析程序不应处理给定视图,则返回null,由传递给链中的下一个视图解析器程序解析
if (!canHandle(viewName, locale)) {
return null;
}
// 检查是否 redirect: 前缀开头的视图名
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
// 获取到 redirect: 后面的重定向地址
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
// 创建一个重定向的视图
RedirectView view = new RedirectView(redirectUrl,
isRedirectContextRelative(), isRedirectHttp10Compatible());
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
}
// 检查是否是 forward: 开头的视图名
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
// 获取到forward:后面跟着的转发地址, 并创建一个InternalResourceView类型的视图返回
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// 否则回退到父类实现:调用loadView(大部分情况都是调的这行代码)
// 如 controller 中, return "/account/list";
return super.createView(viewName, locale);
}
那继续看看父类中的createView方法是怎么调用的吧
// 此类中其他方法省略了
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}
// 这是一个抽象方法,在子类中有实现,
protected abstract View loadView(String viewName, Locale locale) throws Exception;
}
看看子类UrlBasedViewResolver 中实现的loadView()方法
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
// 主要的代码逻辑在 buildView 方法
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
Class<?> viewClass = getViewClass();
Assert.state(viewClass != null, "No view class");
// 创建一个视图对象
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
// 设置转发地址; 正好和配置文件里写的配置对应上了
/*
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
*/
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
// 将需要渲染的数据模型放入视图中
view.setAttributesMap(getAttributesMap());
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
String[] exposedContextBeanNames = getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
view.setExposedContextBeanNames(exposedContextBeanNames);
}
return view;
}
4.渲染视图
4.1 View接口
- 视图对象均实现自View接口,其有一个主要的方法render()
public interface View {
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
@Nullable
default String getContentType() {
return null;
}
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
4.2 下面以 InternalResourceView来介绍下render方法的调用.
先看看其继承关系
- InternalResourceView extends AbstractUrlBasedView
- AbstractUrlBasedView extends AbstractView
- AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware
看下AbstractView实现的的render方法
public void render(@Nullable 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, getRequestToExpose(request), response);
}
下面看下在InternalResourceView类中重写的renderMergedOutputModel()方法
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 将ModelAndView 里面的数据遍历出来,放到request的attributes中
exposeModelAsRequestAttributes(model, request);
// 这是一个空方法,只在子类JstlView中有实现
exposeHelpers(request);
//获取到转发的路径,如果即将转发的路径和当前请求的路径相同,将抛出异常
String dispatcherPath = prepareForRendering(request, response);
//获取目标资源(通常是JSP)的 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!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(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() + "'");
}
//等同于 request.getRequestDispatcher(dispatcherPath).forward(request,response);
rd.forward(request, response);
}
}
再看看是如何将ModelAndView中的模型数据放到request域中的
// 此类中其他方法省略
public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
HttpServletRequest request) throws Exception {
model.forEach((modelName, modelValue) -> {
if (modelValue != null) {
request.setAttribute(modelName, modelValue);
if (logger.isDebugEnabled()) {
logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
"] to request in view with name '" + getBeanName() + "'");
}
}
else {
request.removeAttribute(modelName);
if (logger.isDebugEnabled()) {
logger.debug("Removed model object '" + modelName +
"' from request in view with name '" + getBeanName() + "'");
}
}
});
}
}