当我们使用 Spring MVC
开发Web
应用页面时,一般会使用某种视图模版引擎技术,比如FreeMarker
,Velocity
之类的,然后还会写很多控制器方法用来处理某个请求,这些控制器方法基本的套路是:
- 写一个视图模板,配置到合适的位置;
- 控制器方法接收处理来自客户端的参数接收处理来自客户端的参数;
- 控制器方法调用服务端逻辑生成将要渲染到客户端的数据
Model
对象; - 控制器方法返回所开发的视图模板的名称;
对于开发人员来讲,完成这几个步骤,针对某个请求的开发就可以认为是结束了,用户请求相应的url
,然后就能看到相应的页面。这个过程听起来很简单,但是,控制器方法返回的数据Model
对象和视图模板名称,又是怎样变成最终的视图返回给客户端的呢 ?
这篇文章我们就看一下这里背后发生的事情,以使用FreeMarker
为例。
首先看看我们所开发的控制器方法 :
@Controller // 定义一个控制器类
public class SampleController {
// 控制器方法定义
// 当用户请求/weather时使用这个控制器方法处理该请求
@RequestMapping("/weather")
public String weather(Model model) {
// 1.参数接受和处理
//// 该方法仅用于展示今天的天气情况,所以不用接收和处理什么参数
// 2.业务处理逻辑
//// 下面的代码模拟的是调用服务层逻辑完成业务处理病更新模型对象model
//// 用于渲染响应页面给客户端
model.addAttribute("today", LocalDate.now().toString());
int max = new Random().nextInt(100);
model.addAttribute("max", "" + max);
int min = max - new Random().nextInt(30);
model.addAttribute("min", "" + min);
// 3.返回视图模板名称
//// 上面的逻辑已经调用了服务层逻辑进行了相应的业务处理,
//// 现在将要返回给用户的数据,也就是上面的model对象也已经生成,
//// 现在返回用于渲染最终页面的视图名称
return "weather";
}
}
然后我们来看当这个控制器方法执行完成后发生的事情。
// Spring MVC 前端控制器Servlet DispatcherServlet 对一个请求处理的核心流程方法
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 {
// 是否是文件上传请求,如果是,会将参数request封装为另外一个
// MultipartHttpServletRequest对象request记到processedRequest
processedRequest = checkMultipart(request);
// 如果发现processedRequest和参数request对象不是同一个对象,
// 说明当前处于一个文件上传请求中
multipartRequestParsed = (processedRequest != request);
// 确定针对当前请求的 handler, 可以将一个 handler 理解成
// 开发人员实现的某个控制器方法(@Controller+@RequestMapping注解的某个方法),
// 该控制器方法会被封装成一个 HandlerExecutionChain 实例 (内含对应目标控制器
// 方法 handler 和一组相应的 pre/post/complete HandlerInterceptor)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
// 针对当前请求,如果没有找到HandlerExecutionChain,
// 则抛出异常NoHandlerFoundException或者向浏览器端返回响应 404
noHandlerFound(processedRequest, response);
return;
}
// 确定针对当前请求的 HandlerAdapter , DispatcherServlet 并不会直接调用
// HandlerExecutionChain 中的 handler ,而是通过一个叫做 HandlerAdapter
// 的一个中间层做隔离,这种隔离了 DispatcherServlet 主流程和具体的 handler
// 的实现细节,从而 DispatcherServlet 不需要关注业务处理细节,而只需要查找
// 和调用相应的 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;
}
}
// handler 前置拦截方法调用
//// 在目标HandlerAdapter处理请求之前,调用HandlerExecutionChain中各个
//// HandlerInterceptor的preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// handler 调用
// 调用目标HandlerAdapter,最终是一个开发人员提供的Web Controller方法,
// 返回结果是一个 ModelAndView 对象,View处理器会使用该对象渲染最终结果到 response
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 当 ModelAndView 对象存在但是没有指定view属性时,采用缺省view处理响应结果
applyDefaultViewName(processedRequest, mv);
// handler 后置拦截方法调用
//// 在目标HandlerAdapter处理请求之后,在view最终渲染之前,
//// 调用HandlerExecutionChain中各个HandlerInterceptor的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);
}
// 处理分发给HandlerAdapter,也就是目标Controller方法的请求的处理结果,这里面主要包含两部分信息 :
// 1. ModelAndView view -- 控制器方法成功处理完请求后返回的对象
// 2. Exception dispatchException -- 控制器方法处理遇到异常时的异常信息对象
// 该方法主要做以下事情 :
// 1. 如果有异常,处理异常
// 2. 如果没有异常,根据 ModelAndView ,结合view模板和model数据渲染响应结果
// 3. 调用HandlerExecutionChain中各个HandlerInterceptor的方法 afterCompletion
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 在 view 被渲染之后调用相应的拦截器方法 afterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// 在 view 被渲染之后调用相应的拦截器方法 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);
}
}
}
}
在上面的代码中,mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
这一行真正执行了开发人员的控制器方法,继续上面的例子,也就是方法SampleController.weather
,这行调用所返回的对象mv
是类ModelAndView
的一个实例,它持有控制器方法weather()
所构造的model
对象和视图名称weather
。
这一行代码调用的堆栈如下所示 :
这一行代码执行结束时,要渲染的数据model
和要渲染的视图都已经知道了,通过一个ModelAndView
对象的方式又回到了DispatcherServlet
方法的doDispatch
方法。那么拿到这些信息,又怎样到渲染成最终的视图呢 ?这时候就要看DispatcherServlet
的方法processDispatchResult
了。
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
* 处理 handler选择和调用的结果,该结果可能是一个ModelAndView对象,也可能是需要转换成一个
* ModelAndView对象的一个异常Exception。
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
// 先检查刚才的handler调用过程中是否出现了异常,根据异常的情况试图获取相应的ModelAndView 对象,
// 然后下面的逻辑就可以对正常和异常情况统一到 ModelAndView 这一种方式上来了。
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) {
// 不管 handler 执行过程中遇到了异常,还是正常返回了ModelAndView,
// 上面都将其统一按照处理ModelAndView的方式统一通过render()方法渲染
// 完相应的视图了,现在调用相应的拦截器的 afterCompletion方法。
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
上面的方法的主要逻辑是将异常Exception
和正常ModelAndView
的方式统一到处理ModelAndView
的这一种方式然后进行渲染。而具体渲染的逻辑则在方法render
中:
/**
* Render the given ModelAndView.
* 渲染给定的ModelAndView到最终的视图,渲染的结果视图直接写入response。
* This is the last stage in handling a request. It may involve resolving the view by name.
* 这是一个请求处理的最后一个阶段。可能涉及到根据视图名称分析出相应的视图模板。
* @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.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// 如果mv的view属性是一个字符串String,则将该属性理解为是一个视图模板的名称,
// 这里的代码需要根据改名称解析出相应的view对象。该任务由另外一个方法
// resolveViewName() 完成。resolveViewName()方法签名虽然需要model作为参数,
// 但实际上在解析相应的view时,model根本没有用,实质上只使用视图模板名称加上
// locale信息作为参数就够解析目标视图对象了。
// 对于FreeMarker,view对应o.s.w.servlet.view.freemarker.FreeMarkerView
// 对于Velocity,view对应o.s.w.servlet.view.velocity.VelocityView
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 {
// 如果mv的view属性不是一个字符串String,说明已经是一个view对象了
// 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
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;
}
}