三、SpringMVC的视图处理器

从上一篇文章我们已经看到了在获取到一个ModelAndView之后,会调用DispatcherServlet.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);对用户进行响应

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);    
    ├render(mv, request, response);
    │    ├View view = viewResolver.resolveViewName(mv.getViewName(), locale);//此处viewResolover常用的是FreeMarkerViewResolver,返回FreeMarkerView
    │    └view.render(mv.getModelInternal(), request, response);
    │        ├Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    │        ├prepareResponse(request, response);
    │        └renderMergedOutputModel(mergedModel, request, response);
    │            └FreeMarkerView.doRender(model, request, response);
    │                └template.process(model, response.getWriter());//给用户输出内容
    │    
    └mappedHandler.triggerAfterCompletion(request, response, null);
        ├HandlerInterceptor[] interceptors = getInterceptors()
        └for(HandlerInterceptor h : interceptors)h.afterCompletion(request, response, ex)

我们上一节已经大致描述了这个过程。

此处仅强调一下:

在视图解析器的解析过程中,采用的是如下代码:

for (ViewResolver viewResolver : this.viewResolvers) {
    View view = viewResolver.resolveViewName(viewName, locale);
    if (view != null) {
        return view;
    }
}

即:在系统中按照viewResolvers对view进行解析,如果解析成功,则直接返回,跳过其他viewResolvers

然后调用view.render(mv.getModelInternal(), request, response);

最终调用template.process(model, response.getWriter());把结果输出到用户


PS:依稀记得当年和九扬搞车险时,九扬整了一个同时支持ftl和jsp的解析器,我找找代码去………..

Spring配置如下:

<bean id="viewResolver" class="com.spring.action.MultiViewResover">  
    <property name="resolvers">  
        <map>  
            <entry key="jsp">  
                <bean  
                 class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
                    <property name="viewClass"  
                     value="org.springframework.web.servlet.view.JstlView"/>  
                    <property name="prefix" value="/WEB-INF/jsp/"/>  
                    <property name="suffix" value=".jsp"/>  
                </bean>  
            </entry>  
            <entry key="ftl">  
                <bean  
                 class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">  
                    <property name="cache" value="true"/>  
                    <property name="prefix" value="/"/>  
                    <property name="suffix" value=".ftl"/>    
                </bean>  
            </entry>  
            <entry key="vm">  
                <bean  
                 class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">  
                    <property name="cache" value="true"/>  
                    <property name="prefix" value="/"/>  
                    <property name="suffix" value=".vm"/>  
                </bean>  
            </entry>  
        </map>  
    </property>  
</bean>  

JAVA类如下:

package com.spring.action;  

import java.util.Locale;  
import java.util.Map;  
import org.springframework.web.servlet.View;  
import org.springframework.web.servlet.ViewResolver;  

/** 
 * @author Jeson 
 * @blog:http://www.gbsou.com 
 * @date:Oct 20, 2009 8:39:27 AM 
 * @version :1.0 
 *  
 */  
public class MultiViewResover implements ViewResolver {  

    private Map<String, ViewResolver> resolvers;  

    @Override  
    public View resolveViewName(String viewName, Locale locale)  
            throws Exception {  
        int n = viewName.lastIndexOf("_"); // 获取  
                                            // viewName(modelAndView中的名字)看其有没有下划线  
        if (n == (-1))  
            return null; // 没有则直接返回空  
        // 有的话截取下划线后面的字符串 这里一般是jsp,ftl,vm与配置文件中的<entry key="ftl">的key匹配  
        String suffix = viewName.substring(n + 1);  
        // 根据下划线后面的字符串去获取托管的视图解析类对象  
        ViewResolver resolver = resolvers.get(suffix);  

        // 取下划线前面的部分 那时真正的资源名.比如我们要使用hello.jsp 那viewName就应该是hello_jsp  
        viewName = viewName.substring(0, n);  
        if (resolver != null)  
            return resolver.resolveViewName(viewName, locale);  
        return null;  
    }  

    public Map<String, ViewResolver> getResolvers() {  
        return resolvers;  
    }  

    public void setResolvers(Map<String, ViewResolver> resolvers) {  
        this.resolvers = resolvers;  
    }  
}  

Controller如下

package com.spring.action;  

import java.util.HashMap;  
import java.util.Map;  

import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.servlet.ModelAndView;  

@Controller  
public class HelloWorldAction {  

    @RequestMapping(value = "/helloworld.do")  
    public ModelAndView hello() {  
        ModelAndView modelAndView = new ModelAndView();  
        modelAndView.setViewName("hello_jsp");  
        modelAndView.addObject("message", "Hello,SpringMvc");  
        return modelAndView;  
    }  

    @RequestMapping(value = "/welcome.do")  
    public ModelAndView helloVm() {  
        // ModelAndView modelAndView=new ModelAndView();  
        // modelAndView.setViewName("freemarker_ftl");  
        // modelAndView.addObject("message", "Hello,Velocity");  
        // return modelAndView;  
        Map map = new HashMap();  
        return new ModelAndView("freemarker_ftl",  map); // 根据之前的解释这里就是去寻找  
                                                         // user.ftl资源  

    }  

    @RequestMapping(value = "/free.do")  
    public ModelAndView helloFreeMarker() {  
        ModelAndView modelAndView = new ModelAndView();  
        modelAndView.setViewName("welcome");  
        modelAndView.addObject("message", "Hello,Freemarker");  
        return modelAndView;  
    }  

} 

这是大概四年前的代码了,而我今天才看懂其内部是怎么走的,惭愧惭愧。

更惭愧的是:因为一直没时间,我本来不计划看SpringMVC的。但是最近在看服务化,有同事推荐SpringCloud,然后SpringCloud的基础是SpringBoot,而SpringBoot的第一组件是SpringMVC,我在看完SpringBoot后发现自己对SpringMVC用了很多年但还是无知的概念,因此我决定沉下心来看一下SpringMVC的源码。

到今天终于看完了,终于可以回头看SpringCloud了。