- <!--视图解析器-->
- <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <!-- webroot到一指定文件夹文件路径 -->
- <property name="prefix" value="/"/>
- <!-- 视图名称后缀 -->
- <property name="suffix" value=".jsp"/>
- </bean>
- 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;
- }
- }
一.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");
- }
- }
- }
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进行渲染后将视图展示给用户
- }
- view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
- view.render(mv.getModelInternal(), request, response);
- 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;
- }
我们再看V iewResoler的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);
- }
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);
- }
我觉得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.render(mv.getModelInternal(), request, response);
- 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);
- }
- }
AbstractView的exposeModelAsRequestAttributes方法,在exposeModelAsRequestAttributes方法中是不是看到了我非常熟悉的一段代码
- request.setAttribute(modelName, modelValue);
- 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后再来写。