第五章 SpringMVC之ViewResolver和View解析
2016-02-18 22:51
375 查看
过完年了,本来是想在年前将SpringMVC系列写完的,只是在接近年末的时候没有了一种学习心态,这两天看了一下ViewResolver源码,就想尽快将这篇博客写出,也好完结SpringMVC的系列博客并开始下面的学习。
自己写的配置文件springController.xml中和ViewResolve有关的部分
2.View的作用就是在获取到ViewResolve传来的View和Model,对Model进行渲染,通过View对象找到要展示给用户的物理视图,将渲染后的视图展示给用户。用很直白的话将就是将数据通过request存储起来,找到要展示给用户的页面,将这些数据放在页面中,并将页面呈现给用户。
下面是DispatcherServlet类里面取出IOC容器里面的viewResolver相关对象,可以看成是DispatcherServlet对viewResolver的注册。
DispatcherServlet的doDispatch方法中有一个render(mv, processedRequest, response);render就是对ModelAndView对象进行解析
DispatcherServlet的render方法
我们再看ViewResoler的resolveViewName方法。InternalResourceViewResolver的祖先类AbstractCachingViewResolver中有resolveViewName方法。
AbstractCachingViewResolver是实现了ViewResolver接口的抽象方法
AbstractCachingViewResolver中的resolveViewName方法,该方法首先会判断有没有缓存,要是有缓存,它会先去缓存中通过viewName查找是否有View对象的存在,要是没有,它会通过viewName创建一个新的View对象,并将View对象存入缓存中,这样再次遇到同样的视图名的时候就可以直接在缓存中取出View对象了
AbstractCachingViewResolver中的createView方法
UrlBasedViewResolver中的loadView方法
我觉得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属性中去。
renderMergedOutputModel方法由AbstractView的孙子类InternalResourceView实现
InternalResourceView的renderMergedOutputModel方法
在renderMergedOutputModel方法中我们看到了我们刚学servlet的一丝痕迹。我们获取到视图的物理路径,然后将这段路径传给RequestDispatcher对象,再调用RequestDispatcher的forward方法将页面呈现给用户,这样就走完了视图的解析了。至于RequestDispatcher的forward方法是如何根据视图路径将页面呈现给用户,这个我也不知道,只是知道这个方法是这么用的罢了。
AbstractView的exposeModelAsRequestAttributes方法,在exposeModelAsRequestAttributes方法中是不是看到了我非常熟悉的一段代码
已经快晚上十一点啦,我还在公司写博客,哈哈,是不是很认真,哎,一整天了还没将这片博客写完,惭愧啊。该回去啦,明天来进行一下总结。
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后再来写。
自己写的配置文件springController.xml中和ViewResolve有关的部分
<!--视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- webroot到一指定文件夹文件路径 --> <property name="prefix" value="/"/> <!-- 视图名称后缀 --> <property name="suffix" value=".jsp"/> </bean>自己写的处理器处理用户的请求,在处理完请求后返回一个ModelAndView对象
package com.wangbiao.springMVC; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.multiaction.MultiActionController; public class HelloWorld extends MultiActionController{ public ModelAndView sayHelloWorld(HttpServletRequest request, HttpServletResponse response) { String param = request.getParameter("param"); System.out.println("springMVC测试:helloWorld;"+param); ModelAndView mv = new ModelAndView(); mv.addObject("content", "springMVC HelloWorld:"+param); mv.setViewName("springMVC/helloWorld"); ServletContext ctx = this.getServletContext(); return mv; } }可以看出处理器返回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的注册。
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<ViewResolver>(matchingBeans.values()); // We keep ViewResolvers in sorted order. OrderComparator.sort(this.viewResolvers); } } else { try { ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);//从IOC容器里面的viewResolver相关对象并存储 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.isDebugEnabled()) { logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default"); } } }再看看viewResolver拿到了ModelAndView对象后是怎么处理的
DispatcherServlet的doDispatch方法中有一个render(mv, processedRequest, response);render就是对ModelAndView对象进行解析
DispatcherServlet的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.resolveLocale(request); response.setLocale(locale); View view; if (mv.isReference()) { // 获取<span style="font-size: 13.3333339691162px;">ModelAndView对象中的视图名和数据模型,通过逻辑视图名获取到真正的view对象。</span> 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() + "'"); } view.render(mv.getModelInternal(), request, response);//View对象对Model进行渲染后将视图展示给用户 }在render方法中主要看两段代码:
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
view.render(mv.getModelInternal(), request, response);先看resolveViewName方法
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; }在resolveViewName方法中我们看到了通过遍历viewResolers,从中选出合适的ViewResolver对视图名进行解析,如果解析出的View不为空,就直接返回了,结合上面的配置文件,viewResolers中只有一个InternalResourceViewResolver对象。
我们再看ViewResoler的resolveViewName方法。InternalResourceViewResolver的祖先类AbstractCachingViewResolver中有resolveViewName方法。
AbstractCachingViewResolver是实现了ViewResolver接口的抽象方法
AbstractCachingViewResolver中的resolveViewName方法,该方法首先会判断有没有缓存,要是有缓存,它会先去缓存中通过viewName查找是否有View对象的存在,要是没有,它会通过viewName创建一个新的View对象,并将View对象存入缓存中,这样再次遇到同样的视图名的时候就可以直接在缓存中取出View对象了
public View resolveViewName(String viewName, Locale locale) throws Exception { if (!isCache()) { return createView(viewName, locale); } else { Object cacheKey = getCacheKey(viewName, locale); synchronized (this.viewCache) { View view = this.viewCache.get(cacheKey); if (view == null) { // Ask the subclass to create the View object. view = createView(viewName, locale); this.viewCache.put(cacheKey, view); if (logger.isTraceEnabled()) { logger.trace("Cached view [" + cacheKey + "]"); } } return view; } } }
AbstractCachingViewResolver中的createView方法
protected View createView(String viewName, Locale locale) throws Exception { return loadView(viewName, locale); }AbstractCachingViewResolver中的loadView方法是一个抽象方法,它通过AbstractCachingViewResolver的子类UrlBasedViewResolver方法实现
UrlBasedViewResolver中的loadView方法
protected View loadView(String viewName, Locale locale) throws Exception { AbstractUrlBasedView view = buildView(viewName); View result = (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName); return (view.checkResource(locale) ? result : null); }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属性中去。
protected AbstractUrlBasedView buildView(String viewName) throws Exception { AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass()); view.setUrl(getPrefix() + viewName + getSuffix()); String contentType = getContentType(); if (contentType != null) { view.setContentType(contentType); } view.setRequestContextAttribute(getRequestContextAttribute()); view.setAttributesMap(getAttributesMap()); return view; }就这样我们得到了一个View对象,这个视图的name就是逻辑视图名,因为当将View对象放在缓存的时候,我们可以通过逻辑视图名在缓存中找出View对象。我们在获取到View对象的时候,我们还要将View进行渲染,并呈现给用户。我们再来看看View中的render方法。
view.render(mv.getModelInternal(), request, response);View是个接口,View由AbstractView实现。AbstractView中的reder方法
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); } // Consolidate static and dynamic model attributes. Map<String, Object> mergedModel = new HashMap<String, Object>(this.staticAttributes.size() + (model != null ? model.size() : 0)); mergedModel.putAll(this.staticAttributes); if (model != null) { mergedModel.putAll(model); } // Expose RequestContext? if (this.requestContextAttribute != null) { mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel)); } prepareResponse(request, response); renderMergedOutputModel(mergedModel, request, response); }
renderMergedOutputModel方法由AbstractView的孙子类InternalResourceView实现
InternalResourceView的renderMergedOutputModel方法
在renderMergedOutputModel方法中我们看到了我们刚学servlet的一丝痕迹。我们获取到视图的物理路径,然后将这段路径传给RequestDispatcher对象,再调用RequestDispatcher的forward方法将页面呈现给用户,这样就走完了视图的解析了。至于RequestDispatcher的forward方法是如何根据视图路径将页面呈现给用户,这个我也不知道,只是知道这个方法是这么用的罢了。
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine which request handle to expose to the RequestDispatcher. HttpServletRequest requestToExpose = getRequestToExpose(request); // Expose the model object as request attributes. exposeModelAsRequestAttributes(model, requestToExpose); // Expose helpers as request attributes, if any. exposeHelpers(requestToExpose); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(requestToExpose, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(requestToExpose, 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(requestToExpose, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.include(requestToExpose, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. exposeForwardRequestAttributes(requestToExpose); if (logger.isDebugEnabled()) { logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.forward(requestToExpose, response); } }在renderMergedOutputModel方法中调用了InternalResourceView祖先类AbstractView的exposeModelAsRequestAttributes方法
AbstractView的exposeModelAsRequestAttributes方法,在exposeModelAsRequestAttributes方法中是不是看到了我非常熟悉的一段代码
request.setAttribute(modelName, modelValue);就是将ModelAndView中的Mdoel里面的值交给request存储,我们就可以在页面就可以通过el表达式来获取这些值了。
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() + "'"); } } } }
已经快晚上十一点啦,我还在公司写博客,哈哈,是不是很认真,哎,一整天了还没将这片博客写完,惭愧啊。该回去啦,明天来进行一下总结。
三.ResolverView和View相关类的介绍
1.类AbstractCachingViewResolverAbstractCachingViewResolver实现了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后再来写。
相关文章推荐
- Java [Leetcode 125]Valid Palindrome
- Eclipse下 快速找到类对应的包
- java日期格式
- 六种常用的设计模式java实现(四)模板模式
- Eclipse包与类的显示和查找小技巧
- 对JAVA的初步相识
- Simple-Java—Collections(二)Java中如何把数组转换为ArrayList
- 【004】Mac下命令行terminal安装jdk
- LeetCode_1_TwoSum(Java Code)
- java学习之JDBC
- Java 入门 之 AWT 与 Swing 的比较
- Java中JSON操作
- Spring事务的来龙去脉
- spring hibernate4整合问题
- Java byte数据类型详解
- Servlet生命周期
- Eclipse导出JavaDoc中文乱码问题解决
- 01、数据类型初阶(Java的基本数据类型)
- 简单的java Socket 例子
- 解决java mail发送TXT附件被直接显示在正文中的问题