之前我们完成了springmvc各组件的初始化,然后进入doDispatch()的方法,通过handlerMappings迭代出order优先级最高的handlerMapping,然后调用handlerMapping的gethandler()方法获取handlerExcutionChain对象,接着我们通过getHandlerAdapter()方法,传入handler作为参数,获得匹配的handlerAdapter,获取到handlerExcutionChain和handlerAdapter后,我们调用适配器的handler(req,res,handler)的方式获取ModelAndView对象,这是前几天完成的部分。以下引用链接https://blog.csdn.net/wangbiao007/article/details/50510274 来展示整个流程
![](https://img.haomeiwen.com/i13278348/5e1e89fa45cad26f.png)
通过ModelAndView我们获取到了model和viewName,程序接着往下走,到方法applyDefaultViewName(processedRequest, mv)的调用,这个方法内部如下:
private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
}
如果先前我们获取到mv,并且mv是有viewName的,那么这个方法没有用,因为这个方法就是在我们有mv但是没有viewName的情况下,给我们设置一个默认的viewName
程序接着往下走,到方法mappedHandler.applyPostHandle(processedRequest, response, mv)的调用,这个内部如下:
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
获取到项目的拦截器,然后判断拦截器是否存在,存在则对该请求进行测试(postHandler),如果有一个拦截器不成功,则请求失败。
程序接着往下走,到本次最重要的视图解析和渲染部分,也就是调用方法processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)
该方法的代码如下:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, 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) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
首先判断一下是否异常,如果出现异常,判断异常是否是ModelAndViewDefiningException的实例,如果是就直接调用getModelAndView方法返回一个mv,否则调用processHanlderException方法初始化mv对象
如果没有异常出现,那么判断mv是否为空,为空抛出异常,不为空则判断mv,wasCleared()方法返回的值是否为真,我们来看下这个方法的实现:
public boolean wasCleared() {
return (this.cleared && isEmpty());
}
首先这个方法存在于ModelAndView类中,cleared的默认值设置为false,如果执行了clear()方法,则cleared的值置为true,接着是isEmpty()方法判断:
public boolean isEmpty() {
return (this.view == null && CollectionUtils.isEmpty(this.model));
}
判断完成后,调用DispatcherServlet的render(mv,req,res)方法,代码如下:
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()) {
// We need to resolve the view name.
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 {
// 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(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
首先介绍一下Locale对象,设置整个系统的运行语言,然后根据系统的运行语言来展示对应语言的页面,平时我们总会出现乱码的情况,那么可以看下response的语言类型到底是否是我们设置的情况。
这个方法中有两部分,第一部分是解析视图,在if(mv.isReference()){}里头,第二部分是视图渲染,在try{}catch{}代码块中的,view.render(mv.getModelInternal(),req,res)
首先介绍一下第一部分解析视图,mv.isReference()是判断viewName是否存在,存在,则通过resolveViewName(mv.getViewName,mv.getModelInternal,locale,request)方法进行解析,该方法的代码如下:
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;
}
迭代viewResolverss对象,然后调用viewResolver的resolverViewName(viewName,locale)方法,springMVC给我们提供了多种的resolverViewName的方法实现
![](https://img.haomeiwen.com/i13278348/1c4d7637577cd620.png)
通过这些实现来返回我们的view对象。
如果之前判断isReference为false,则直接调用view.getView的方法获取view。
解析成功view之后,就是执行渲染操作了,渲染操作调用刚刚解析获得的view的render方法,该方法的内部实现如下:
public void render(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);
}
首先先获取到需要加入到request的model的map集合,调用方法createMergedOutputModel方法,然后调用prepareResponse(request,response),该方法是设置响应请求头的两个参数,代码如下:
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
if (generatesDownloadContent()) {
response.setHeader("Pragma", "private");
response.setHeader("Cache-Control", "private, must-revalidate");
}
}
最后一步就是调用renderMergedOutputModel()方法去将mergeModel合到请求里,该方法的代码实现如下:
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
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() + "'");
}
rd.forward(request, response);
}
}
前面四行都是配置requestDispatcher用的,我主要看了下方法exposeModelAsRequestAttributes(),代码如下:
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
for (Map.Entry<String, Object> entry : model.entrySet()) {
String modelName = entry.getKey();
Object modelValue = entry.getValue();
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() + "'");
}
}
}
}
发现了什么,我们不使用框架的时候,如果想要将model传到前端,就是需要使用request.setAttribute()的方法,也就是这个方法封装了我们对于model到前端的操作。
RequestDispatcher 对象配置完成后,就是选择它要进行的操作了,是使用include还是forward进行跳转。至此,视图的解析与渲染工作都完成了,并且也发送到了前端页面,完成了一个请求的整个流程。
总结
在获取到ModelAndView对象后,先判断在mv存在的情况下viewName是否存在,不存在就给定一个初始值,之后对请求进行拦截判断,如果有一个不通过则请求失败,都通过则进行下一步,调用DispatcherServlet.render()方法进行视图的解析和渲染,调用resolverViewName()方法迭代viewResolvers,如果viewResolver.resolveViewName(viewName, locale)的返回结果不为空,则返回该结果,解析成功。
然后再进行视图的渲染工作,通过前面获取到的view,调用该对象的render(model,req,res)方法,方法内先获取该请求的Controller方法中设置的model,然后设置响应请求头,最后调用renderMergedOutputModel将model放到request中通过requestDispatcher.include(req,res)或者requestDispatcher.forward(req,res)的方式传到前端,展现给用户。
参考链接:
https://www.cnblogs.com/solverpeng/p/5743609.html
https://blog.csdn.net/qq924862077/article/details/52878507
https://www.jianshu.com/p/5f91b6d7591a
https://blog.csdn.net/prince2270/article/details/5891085
网友评论