第五章 SpringMVC之ViewResolver和View解析

配置文件springController.xml中和ViewResolve有关的部分
         
  1. <!--视图解析器-->    
  2. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">    
  3.     <!-- webroot到一指定文件夹文件路径 -->    
  4.     <property name="prefix" value="/"/>    
  5.     <!-- 视图名称后缀  -->    
  6.     <property name="suffix" value=".jsp"/>    
  7. </bean>  
         自己写的处理器处理用户的请求,在处理完请求后返回一个ModelAndView对象

        

  1. package com.wangbiao.springMVC;  
  2.   
  3. import javax.servlet.ServletContext;  
  4. import javax.servlet.http.HttpServletRequest;  
  5. import javax.servlet.http.HttpServletResponse;  
  6.   
  7. import org.springframework.web.servlet.ModelAndView;  
  8. import org.springframework.web.servlet.mvc.multiaction.MultiActionController;  
  9.   
  10. public class HelloWorld extends  MultiActionController{  
  11.   
  12.     public ModelAndView sayHelloWorld(HttpServletRequest request, HttpServletResponse response) {  
  13.         String param = request.getParameter("param");  
  14.         System.out.println("springMVC测试:helloWorld;"+param);  
  15.         ModelAndView mv = new ModelAndView();  
  16.         mv.addObject("content""springMVC HelloWorld:"+param);  
  17.         mv.setViewName("springMVC/helloWorld");  
  18.         ServletContext ctx = this.getServletContext();    
  19.         return mv;  
  20.     }  
  21.   
  22. }  
可以看出处理器返回MdoelAndView对象并向此对象中设一个viewName,ViewName是逻辑视图名,还向MdoelAndView中传入了数值,其实就是向MdoelAndView传入了一个Map对象,这个Map对象就是我们常说的数据模型Modle。

一.ViewResolve和View的作用

     1. ViewResolve的作用就是通过解析MdoelAndView,将MdoelAndView中的逻辑视图名变为一个真正的View对象,并将MdoelAndView中的Model取出。

      2.View的作用就是在获取到ViewResolve传来的View和Model,对Model进行渲染,通过View对象找到要展示给用户的物理视图,将渲染后的视图展示给用户。用很直白的话将就是将数据通过request存储起来,找到要展示给用户的页面,将这些数据放在页面中,并将页面呈现给用户。

二.ViewResolve源码介绍

     ViewResolve和前面的HandlerMapping,HandlerAdapter一样,首先是在启动服务的时候,IOC容器会根据配置文件里面的ViewResolve相关信息对ViewResolve进行实例化,并存储到DispatcherServlet的 List<ViewResolver> viewResolvers属性中。当要解析MdoelAndView对象的时候,会遍历viewResolvers,从中取出一个viewResolver对进行解析,要是解析出View对象,就不再进行遍历,要是解析出的View对象是空的,接着从viewResolvers中取出viewResolver对MdoelAndView对象进行解析。

下面是DispatcherServlet类里面取出IOC容器里面的viewResolver相关对象,可以看成是DispatcherServlet对viewResolver的注册。

  1. private void initViewResolvers(ApplicationContext context) {  
  2.         this.viewResolvers = null;  
  3.   
  4.         if (this.detectAllViewResolvers) {  
  5.             // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.  
  6.             Map<String, ViewResolver> matchingBeans =  
  7.                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.classtruefalse);  
  8.             if (!matchingBeans.isEmpty()) {  
  9.                 this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());  
  10.                 // We keep ViewResolvers in sorted order.  
  11.                 OrderComparator.sort(this.viewResolvers);  
  12.             }  
  13.         }  
  14.         else {  
  15.             try {  
  16.                 ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);//从IOC容器里面的viewResolver相关对象并存储  
  17.                 this.viewResolvers = Collections.singletonList(vr);  
  18.             }  
  19.             catch (NoSuchBeanDefinitionException ex) {  
  20.                 // Ignore, we'll add a default ViewResolver later.  
  21.             }  
  22.         }  
  23.   
  24.         // Ensure we have at least one ViewResolver, by registering  
  25.         // a default ViewResolver if no other resolvers are found.  
  26.         if (this.viewResolvers == null) {  
  27.             this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);  
  28.             if (logger.isDebugEnabled()) {  
  29.                 logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");  
  30.             }  
  31.         }  
  32.     }  
再看看 viewResolver拿到了ModelAndView对象后是怎么处理的

DispatcherServlet的doDispatch方法中有一个render(mv, processedRequest, response);render就是对ModelAndView对象进行解析

DispatcherServlet的render方法

  1. protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {  
  2.         // Determine locale for request and apply it to the response.  
  3.         Locale locale = this.localeResolver.resolveLocale(request);  
  4.         response.setLocale(locale);  
  5.   
  6.         View view;  
  7.         if (mv.isReference()) {  
  8.             // 获取<span style="font-size: 13.3333339691162px;">ModelAndView对象中的视图名和数据模型,通过逻辑视图名获取到真正的view对象。</span>  
  9.             view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);  
  10.             if (view == null) {  
  11.                 throw new ServletException(  
  12.                         "Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" +  
  13.                                 getServletName() + "'");  
  14.             }  
  15.         }  
  16.         else {  
  17.             // No need to lookup: the ModelAndView object contains the actual View object.  
  18.             view = mv.getView();  
  19.             if (view == null) {  
  20.                 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +  
  21.                         "View object in servlet with name '" + getServletName() + "'");  
  22.             }  
  23.         }  
  24.   
  25.         // Delegate to the View object for rendering.  
  26.         if (logger.isDebugEnabled()) {  
  27.             logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");  
  28.         }  
  29.         view.render(mv.getModelInternal(), request, response);//View对象对Model进行渲染后将视图展示给用户  
  30.     }  
在render方法中主要看两段代码:

  1. view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);  
  1. view.render(mv.getModelInternal(), request, response);  
先看resolveViewName方法

  1. protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,  
  2.         HttpServletRequest request) throws Exception {  
  3.   
  4.     for (ViewResolver viewResolver : this.viewResolvers) {  
  5.         View view = viewResolver.resolveViewName(viewName, locale);  
  6.         if (view != null) {  
  7.             return view;  
  8.         }  
  9.     }  
  10.     return null;  
  11. }  
resolveViewName方法中我们看到了通过遍历viewResolers,从中选出合适的ViewResolver对视图名进行解析,如果解析出的View不为空,就直接返回了,结合上面的配置文件,viewResolers中只有一个InternalResourceViewResolver对象。
我们再看V iewResoler的resolveViewName方法。InternalResourceViewResolver的祖先类AbstractCachingViewResolver中有resolveViewName方法。

AbstractCachingViewResolver是实现了ViewResolver接口的抽象方法

AbstractCachingViewResolver中的resolveViewName方法,该方法首先会判断有没有缓存,要是有缓存,它会先去缓存中通过viewName查找是否有View对象的存在,要是没有,它会通过viewName创建一个新的View对象,并将View对象存入缓存中,这样再次遇到同样的视图名的时候就可以直接在缓存中取出View对象了

  1. public View resolveViewName(String viewName, Locale locale) throws Exception {  
  2.         if (!isCache()) {  
  3.             return createView(viewName, locale);  
  4.         }  
  5.         else {  
  6.             Object cacheKey = getCacheKey(viewName, locale);  
  7.             synchronized (this.viewCache) {  
  8.                 View view = this.viewCache.get(cacheKey);  
  9.                 if (view == null) {  
  10.                     // Ask the subclass to create the View object.  
  11.                     view = createView(viewName, locale);  
  12.                     this.viewCache.put(cacheKey, view);  
  13.                     if (logger.isTraceEnabled()) {  
  14.                         logger.trace("Cached view [" + cacheKey + "]");  
  15.                     }  
  16.                 }  
  17.                 return view;  
  18.             }  
  19.         }  
  20.     }  

AbstractCachingViewResolver中的createView方法
  1. protected View createView(String viewName, Locale locale) throws Exception {  
  2.     return loadView(viewName, locale);  
  3. }  
AbstractCachingViewResolver中的loadView方法是一个抽象方法,它通过AbstractCachingViewResolver的子类UrlBasedViewResolver方法实现

UrlBasedViewResolver中的loadView方法

  1. protected View loadView(String viewName, Locale locale) throws Exception {  
  2.     AbstractUrlBasedView view = buildView(viewName);  
  3.     View result = (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);  
  4.     return (view.checkResource(locale) ? result : null);  
  5. }  
UrlBasedViewResolver中的buildView 方法

我觉得buildView方法是创建View对象最核心的方法,buildView方法会获取一个View对象,这个对象会将视图以什么格式呈现给用户,例如如果是jsp显示呈现给用户的话,那这个view对象就是JstlView,默认的是JstlView。在这个方法中我们看到了getPrefix() + viewName + getSuffix()这样一段代码,这就是对视图路径的一个拼接了,getPrefix()方法获取前缀,也就是我们在配置文件中配置的 <property name="prefix" value="/"/>  的value中的值了,getSuffix()方法就是获取后缀值了,也就是我们在配置文件中配置的<property name="suffix" value=".jsp"/>   的value中的值。这样就将将视图的物理路径找到了,并赋值到View的URL属性中去。

  1. protected AbstractUrlBasedView buildView(String viewName) throws Exception {  
  2.         AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());  
  3.         view.setUrl(getPrefix() + viewName + getSuffix());  
  4.         String contentType = getContentType();  
  5.         if (contentType != null) {  
  6.             view.setContentType(contentType);  
  7.         }  
  8.         view.setRequestContextAttribute(getRequestContextAttribute());  
  9.         view.setAttributesMap(getAttributesMap());  
  10.         return view;  
  11.     }  
就这样我们得到了一个View对象,这个视图的name就是逻辑视图名,因为当将View对象放在缓存的时候,我们可以通过逻辑视图名在缓存中找出View对象。我们在获取到View对象的时候,我们还要将View进行渲染,并呈现给用户。我们再来看看View中的render方法。

  1. view.render(mv.getModelInternal(), request, response);  
View是个接口,View由AbstractView实现。AbstractView中的reder方法
  1. public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {  
  2.     if (logger.isTraceEnabled()) {  
  3.         logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +  
  4.             " and static attributes " + this.staticAttributes);  
  5.     }  
  6.   
  7.     // Consolidate static and dynamic model attributes.  
  8.     Map<String, Object> mergedModel =  
  9.             new HashMap<String, Object>(this.staticAttributes.size() + (model != null ? model.size() : 0));  
  10.     mergedModel.putAll(this.staticAttributes);  
  11.     if (model != null) {  
  12.         mergedModel.putAll(model);  
  13.     }  
  14.   
  15.     // Expose RequestContext?  
  16.     if (this.requestContextAttribute != null) {  
  17.         mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));  
  18.     }  
  19.   
  20.     prepareResponse(request, response);  
  21.     renderMergedOutputModel(mergedModel, request, response);  
  22. }  

renderMergedOutputModel方法由AbstractView的孙子类InternalResourceView实现

InternalResourceView的renderMergedOutputModel方法

renderMergedOutputModel方法中我们看到了我们刚学servlet的一丝痕迹。我们获取到视图的物理路径,然后将这段路径传给RequestDispatcher对象,再调用RequestDispatcher的forward方法将页面呈现给用户,这样就走完了视图的解析了。至于RequestDispatcher的forward方法是如何根据视图路径将页面呈现给用户,这个我也不知道,只是知道这个方法是这么用的罢了。

  1. protected void renderMergedOutputModel(  
  2.         Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {  
  3.   
  4.     // Determine which request handle to expose to the RequestDispatcher.  
  5.     HttpServletRequest requestToExpose = getRequestToExpose(request);  
  6.   
  7.     // Expose the model object as request attributes.  
  8.     exposeModelAsRequestAttributes(model, requestToExpose);  
  9.   
  10.     // Expose helpers as request attributes, if any.  
  11.     exposeHelpers(requestToExpose);  
  12.   
  13.     // Determine the path for the request dispatcher.  
  14.     String dispatcherPath = prepareForRendering(requestToExpose, response);  
  15.   
  16.     // Obtain a RequestDispatcher for the target resource (typically a JSP).  
  17.     RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);  
  18.     if (rd == null) {  
  19.         throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +  
  20.                 "]: Check that the corresponding file exists within your web application archive!");  
  21.     }  
  22.   
  23.     // If already included or response already committed, perform include, else forward.  
  24.     if (useInclude(requestToExpose, response)) {  
  25.         response.setContentType(getContentType());  
  26.         if (logger.isDebugEnabled()) {  
  27.             logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");  
  28.         }  
  29.         rd.include(requestToExpose, response);  
  30.     }  
  31.   
  32.     else {  
  33.         // Note: The forwarded resource is supposed to determine the content type itself.  
  34.         exposeForwardRequestAttributes(requestToExpose);  
  35.         if (logger.isDebugEnabled()) {  
  36.             logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");  
  37.         }  
  38.         rd.forward(requestToExpose, response);  
  39.     }  
  40. }  
renderMergedOutputModel方法中调用了InternalResourceView祖先类AbstractView的exposeModelAsRequestAttributes方法

AbstractViewexposeModelAsRequestAttributes方法,在exposeModelAsRequestAttributes方法中是不是看到了我非常熟悉的一段代码

  1. request.setAttribute(modelName, modelValue);  
就是将ModelAndView中的Mdoel里面的值交给request存储,我们就可以在页面就可以通过el表达式来获取这些值了。

  1. protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {  
  2.         for (Map.Entry<String, Object> entry : model.entrySet()) {  
  3.             String modelName = entry.getKey();  
  4.             Object modelValue = entry.getValue();  
  5.             if (modelValue != null) {  
  6.                 request.setAttribute(modelName, modelValue);  
  7.                 if (logger.isDebugEnabled()) {  
  8.                     logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +  
  9.                             "] to request in view with name '" + getBeanName() + "'");  
  10.                 }  
  11.             }  
  12.             else {  
  13.                 request.removeAttribute(modelName);  
  14.                 if (logger.isDebugEnabled()) {  
  15.                     logger.debug("Removed model object '" + modelName +  
  16.                             "' from request in view with name '" + getBeanName() + "'");  
  17.                 }  
  18.             }  
  19.         }  
  20.     }  

    已经快晚上十一点啦,我还在公司写博客,哈哈,是不是很认真,哎,一整天了还没将这片博客写完,惭愧啊。该回去啦,明天来进行一下总结。

     三.ResolverView和View相关类的介绍

      1.类AbstractCachingViewResolver

       AbstractCachingViewResolver实现了ResolverView接口。AbstractCachingViewResolver类的主要作用就是在缓存中通过逻辑视图名查找视图,如果没有查找到,就去创建一个新的视图,并将该视图存入缓存中。

      2.类UrlBasedViewResolver

       UrlBasedViewResolver继承了AbstractCachingViewResolver。UrlBasedViewResolver的主要作用是创建一个View的对象,这个View的对象可以在配置文件中配置,也可以取默认的,默认的就是JstlView,读取配置文件中对ResolverView的配置,根据逻辑视图名找到真正视图的路径,将路径存入View对象中。这样就得到了一个View对象。

      3.类AbstractView

       AbstractView实现了View接口。AbstractView的主要作用是渲染视图和将model中的数据取出来并传给页面。AbstractView渲染视图只是实现了一个抽象方法,该功能主要靠AbstractView的孙子类InternalResourceView来实现

      4.AbstractUrlBasedView类

      主要是起到一个承上启下的作用,其他的作用这个我也不知道。

      5.InternalResourceView类

       InternalResourceView继承了AbstractUrlBasedView,InternalResourceView的主要作用就是拿到视图的路径,创建一个RequestDispatcher对象。将视图路径给RequestDispatcher,RequestDispatcher条用forward方法,将视图展示给用户。

      有很多细节的地方其实自己也不是特别懂,所以就没有写。总结的话等以后自己对ResolverView和View后再来写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值