SpringMVC视图源码解析
2021/5/1 22:55:30
本文主要是介绍SpringMVC视图源码解析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
视图解析流程
任何方法的返回值,最终都会被包装成ModelAndView
对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
视图渲染就是将域中的数据在页面展示,页面就是用来渲染模型数据的
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
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) { // Exception (if any) is already handled.. mappedHandler.triggerAfterCompletion(request, response, null); } }
调用render(mv, request, response)
方法渲染页面
/** * Render the given ModelAndView. * <p>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 != 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. //viewName是目标方法的返回值,即页面地址 // mv.getModelInternal()是隐含模型中的所有数据 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; } }
ViewResolver
接口里面有resolveViewName()
方法,作用是根据视图名(方法的返回值)得到View
对象
public interface ViewResolver { /** * Resolve the given view by name. * <p>Note: To allow for ViewResolver chaining, a ViewResolver should * return {@code null} if a view with the given name is not defined in it. * However, this is not required: Some ViewResolvers will always attempt * to build View objects with the given name, unable to return {@code null} * (rather throwing an exception when View creation failed). * @param viewName name of the view to resolve * @param locale the Locale in which to resolve the view. * ViewResolvers that support internationalization should respect this. * @return the View object, or {@code null} if not found * (optional, to allow for ViewResolver chaining) * @throws Exception if the view cannot be resolved * (typically in case of problems creating an actual View object) */ @Nullable View resolveViewName(String viewName, Locale locale) throws Exception; }
resolveViewName()
方法得到View
对象
@Nullable protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { if (this.viewResolvers != null) { //循环遍历所有的视图解析器 for (ViewResolver viewResolver : this.viewResolvers) { //viewResolver视图解析器根据方法的返回值得到一个View对象 View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } } return null; }
视图解析器在初始化的时候会找是否有配置的视图解析器,如果没有就会使用默认的解析器
/** * Initialize the ViewResolvers used by this class. * <p>If no ViewResolver beans are defined in the BeanFactory for this * namespace, we default to InternalResourceViewResolver. */ private void initViewResolvers(ApplicationContext context) { this.viewResolvers = null; if (this.detectAllViewResolvers) { // Find all ViewResolvers in the ApplicationContext, including ancestor contexts. Map<String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.viewResolvers = new ArrayList<>(matchingBeans.values()); // We keep ViewResolvers in sorted order. AnnotationAwareOrderComparator.sort(this.viewResolvers); } } else { try { ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class); this.viewResolvers = Collections.singletonList(vr); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default ViewResolver later. } } // Ensure we have at least one ViewResolver, by registering // a default ViewResolver if no other resolvers are found. if (this.viewResolvers == null) { this.viewResolvers = getDefaultStrategies(context, ViewResolver.class); if (logger.isTraceEnabled()) { logger.trace("No ViewResolvers declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } } }
循环遍历时每一个viewResolver
调用resolveViewName(viewName, locale)
里面的createView(viewName, locale)
得到一个View
对象
public View resolveViewName(String viewName, Locale locale) throws Exception { if (!isCache()) { return createView(viewName, locale); } else { 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) { // Ask the subclass to create the View object. //根据方法的返回值创建出视图对象 view = createView(viewName, locale); if (view == null && this.cacheUnresolved) { view = UNRESOLVED_VIEW; } if (view != null && this.cacheFilter.filter(view, viewName, locale)) { this.viewAccessCache.put(cacheKey, view); this.viewCreationCache.put(cacheKey, view); } } } } else { if (logger.isTraceEnabled()) { logger.trace(formatKey(cacheKey) + "served from cache"); } } return (view != UNRESOLVED_VIEW ? view : null); } }
createView
创建View
对象时,首先判断是否可以能进行处理,然后判断是否以redirect
前缀或forward
前缀的试图名,会分别创建new RedirectView(redirectUrl,isRedirectContextRelative(), isRedirectHttp10Compatible())
对象和new InternalResourceView(forwardUrl)
对象,而InternalResourceViewResolver
解析器就是为了得到InternalResourceView
这个对象。如有没有任何前缀就会调用父类的构造器默认创建一个View
对象。
/** * Overridden to implement check for "redirect:" prefix. * <p>Not possible in {@code loadView}, since overridden * {@code loadView} versions in subclasses might rely on the * superclass always creating instances of the required view class. * @see #loadView * @see #requiredViewClass */ @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. 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. 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); } // Else fall back to superclass implementation: calling loadView. return super.createView(viewName, locale); }
最后返回创建的View
对象
整个视图解析器得到View
对象的流程就是,通过循环,所有配置的视图解析器都会尝试根据视图名(目标方法的返回值)得到View
视图对象,如果能得到就返回这个对象,得不到就换下一个视图解析器。
接着在render()
方法中调用得到的View
对象的view.render(mv.getModelInternal(), request, response)
方法
/** * Prepares the view given the specified model, merging it with static * attributes and a RequestContext attribute, if necessary. * Delegates to renderMergedOutputModel for the actual rendering. * @see #renderMergedOutputModel */ @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 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); //渲染要给页面输出的所有数据 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }
/** * Render the internal resource given the specified model. * This includes setting the model as request attributes. */ @Override 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 [" + 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() + "]"); } //转发页面 rd.forward(request, response); } }
通过源码可以看出,视图解析器只是为了得到视图对象,而视图对象可以将模型数据全部放在请求域中,并且转发或者重定向到页面。所以,只有视图对象才能真正的渲染视图。
这篇关于SpringMVC视图源码解析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-02Java管理系统项目实战入门教程
- 2024-11-02Java监控系统项目实战教程
- 2024-11-02Java就业项目项目实战:从入门到初级工程师的必备技能
- 2024-11-02Java全端项目实战入门教程
- 2024-11-02Java全栈项目实战:从入门到初级应用
- 2024-11-02Java日志系统项目实战:初学者完全指南
- 2024-11-02Java微服务系统项目实战入门教程
- 2024-11-02Java微服务项目实战:新手入门指南
- 2024-11-02Java项目实战:新手入门教程
- 2024-11-02Java小程序项目实战:从入门到简单应用