您的位置:首页 > 编程语言 > Java开发

第五章 SpringMVC之ViewResolver和View解析

2016-02-18 22:51 375 查看
          过完年了,本来是想在年前将SpringMVC系列写完的,只是在接近年末的时候没有了一种学习心态,这两天看了一下ViewResolver源码,就想尽快将这篇博客写出,也好完结SpringMVC的系列博客并开始下面的学习。

          自己写的配置文件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.类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后再来写。

      
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: