SpringMVC源码分析之视图解析器
因为视图解析器比较重要,所以单独开了一个随笔, 源码还是在doDispatch()方法中。在我之前的随笔中有纪录
1、任何方法的返回值,最终都会包装成ModelAndView对象
核心方法就是doDispatch() 中的 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
SpringMVC封装了一个 ModelAndViewContainer类,里面提供了 getModel() getView() getViewName() 等方法。
并且在我们拿到ModelAndView之前,利用ModelFactory工厂初始化了ModelAndViewContainer 初始化方法如下
public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception { Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request); container.mergeAttributes(sessionAttributes); invokeModelAttributeMethods(request, container); for (String name : findSessionAttributeArguments(handlerMethod)) { if (!container.containsAttribute(name)) { Object value = this.sessionAttributesHandler.retrieveAttribute(request, name); if (value == null) { throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name); } container.addAttribute(name, value); } } }
所以最后我们可以直接从ModelAndView容器中取得ModelAndView对象返回
2、视图渲染流程:将域中的数据在页面展示;页面就是来渲染模型数据的
这个过程调用的是doDispatch()方法中的 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 方法
这个方法的关键代码是
// 刚刚我们已经拿到了ModelAndView对象并赋值给了mv 所以这里mv不为空 if (mv != null && !mv.wasCleared()) {
// 进入render方法 渲染数据模型 render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } }
进入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;
// 方法返回值,路径名 就是我们在Controller写的方法,最后return的String String viewName = mv.getViewName(); if (viewName != null) { // viewResolver的作用是根据视图名(方法的返回值)得到view对象 ViewResolver是一个接口,里面只声明了一个resolveViewName方法 . 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; } }
1)、接下来先看看 ViewResolver(视图解析器) 是怎么解析的 即 resolveViewName()方法
说明:这个VIewResolver有很多实现,其中一个就是我们常用在spring-servlet.xml配置的 InternalResourceViewResolver
@Nullable protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { if (this.viewResolvers != null) {
// 这里遍历所有的ViewResolver,就会拿到上述InternalResourceViewResolver实现 因为我没在配置文件中配了,如果没有配,就用默认的 for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } } return null; }
resolveViewName一个核心方法就是 createView(viewName, locale);
解释:InternalResourceViewResolver 继承了 UrlBasedViewResolver 继承了 AbstractCachingViewResolver 声明了 createView() 方法
UrlBasedViewResolver 重写了 createView() 方法。 代码如下 重写这个方法的作用就是为了处理转发和重定向的请求
@Override protected View createView(String viewName, Locale locale) throws Exception { // If this resolver is not supposed to handle the given view, // return null to pass on to the next resolver in the chain. if (!canHandle(viewName, locale)) { return null; } // Check for special "redirect:" prefix. 如果是redirect:前缀,就执行重定向 if (viewName.startsWith(REDIRECT_URL_PREFIX)) { 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); } // Check for special "forward:" prefix. 如果是forward:前缀,就执行转发 if (viewName.startsWith(FORWARD_URL_PREFIX)) { String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); InternalResourceView view = new InternalResourceView(forwardUrl); return applyLifecycleMethods(FORWARD_URL_PREFIX, view); } // 如果没有前缀,就默认使用父类创建一个View对象 return super.createView(viewName, locale); }
源码看到这里,大概就知道View是怎么创建的了。
小结:
视图解析器得到view对象的流程就是,所有配置的视图解析器都来尝试根据视图名(就是自己自定义Controller类下个各个方法的返回值,String路径名)得到VIew(视图)对象
如果能得到就返回,得不到就换下一个视图解析器
2)、SpringMVC拿到了View对象后又做了什么?
调用render()方法,代码如下
@Override public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { logger.debug("View " + formatViewName() + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", 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 { // 将模型中的数据放入请求域中 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 [" + getUrl() + "]"); } rd.include(request, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to [" + getUrl() + "]"); }
// Servlet 原生的转发请求 rd.forward(request, response); } }
总结:
视图解析器只是为了得到视图对象;视图对象才能真正的转发(将模型数据全部放在请求域中)或重定向到页面
视图对象才能真正的渲染视图