Spring 源代码解析:Spring MVC

下面我们将对 Spring MVC 框架的核心代码进行分析。关于 WebApplicationContext 的相关分析可参见之前的文档,本文着重分析 Spring Web MVC 框架的具体实现,入口点从 DispatcherServlet 开始。

DispatcherServlet 初始化

DispatcherServlet 是 Spring MVC 的核心前端控制器。以下是其初始化框架 Servlet 的方法,通过方法名我们可以清晰地看到各个 Spring MVC 主要元素的初始化过程:

protected void initFrameworkServlet() throws ServletException, BeansException {
    initMultipartResolver();
    initLocaleResolver();
    initThemeResolver();
    initHandlerMappings();
    initHandlerAdapters();
    initHandlerExceptionResolvers();
    initRequestToViewNameTranslator();
    initViewResolvers();
}

根据源码注解可知,这是 DispatcherServlet 的初始化过程。该过程是在 WebApplicationContext 已经存在的情况下进行的,这意味着初始化它时,IOC 容器应该已经工作。这也是为什么在 web.xml 中配置 Spring 时,需要将 DispatcherServletload-on-startup 属性配置为大于 0 的值(例如 2)的原因。

对于具体的初始化过程,我们以 initHandlerMappings() 为例进行分析:

private void initHandlerMappings() throws BeansException {
    if (this.detectAllHandlerMappings) {
        // 这里找到所有在上下文中定义的 HandlerMapping,同时把它们排序
        // 因为在同一个上下文中可以有不止一个 HandlerMapping,所以把它们载入到一个链里进行维护和管理
        Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
                getWebApplicationContext(), HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList(matchingBeans.values());
            // 这里通过 order 属性来对 HandlerMapping 在 List 中排序
            Collections.sort(this.handlerMappings, new OrderComparator());
        }
    }
    else {
        try {
            Object hm = getWebApplicationContext().getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // 如果在上下文中没有定义的话,那么我们使用默认的 BeanNameUrlHandlerMapping
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(HandlerMapping.class);
        // ........
    }
}

关于怎样获得上下文环境,可参见之前对 IOC 容器在 Web 环境中加载的分析。DispatcherServlet 把定义了的所有 HandlerMapping 都加载并放在一个 List 里待以后使用。这个链的每一个元素都是一个 HandlerMapping 的配置,而一般每一个 HandlerMapping 可以持有一系列从 URL 请求到 Spring Controller 的映射。例如,SimpleUrlHandlerMapping 中就定义了一个 Map 来持有这一系列的映射关系。

HandlerMapping 与执行链

DispatcherServlet 通过 HandlerMapping 使得 Web 应用程序确定一个执行路径。HandlerMapping 只是一个接口:

public interface HandlerMapping {
    public static final String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE =
                Conventions.getQualifiedAttributeName(HandlerMapping.class, "pathWithinHandlerMapping");
    
    // 实际上维护一个 HandlerExecutionChain,这是典型的 Command 模式的使用
    // 这个执行链里面维护 handler 和拦截器
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

其具体实现只需要实现一个接口方法,该方法返回的是一个 HandlerExecutionChain。实际上就是一个执行链,就像在 Command 模式描述的那样。这个类很简单,持有一个 Interceptor 链和一个 Controller:

public class HandlerExecutionChain {
    private Object handler;
    private HandlerInterceptor[] interceptors;
    // ........
}

这些 Handler 和 Interceptor 需要在定义 HandlerMapping 的时候配置好。比如对具体的 SimpleUrlHandlerMapping,它要根据 URL 映射的方式注册 Handler 和 Interceptor,自己维护一个反映映射关系的 handlerMap。当需要匹配 HTTP 请求的时候,需要使用这个表里的信息来得到执行链。这个注册的过程在 IOC 容器初始化 SimpleUrlHandlerMapping 的时候就被完成了,这样以后的解析才可以用到 Map 里的映射信息。这里的信息和 Bean 文件的信息是等价的,下面是具体的注册过程:

protected void registerHandlers(Map urlMap) throws BeansException {
    if (urlMap.isEmpty()) {
        logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
    }
    else {
        // 这里迭代在 SimpleUrlHandlerMapping 中定义的所有映射元素
        Iterator it = urlMap.keySet().iterator();
        while (it.hasNext()) {
            // 这里取得配置的 url
            String url = (String) it.next();
            // 这里根据 url 在 bean 定义中取得对应的 handler
            Object handler = urlMap.get(url);
            // Prepend with slash if not already present.
            if (!url.startsWith("/")) {
                url = "/" + url;
            }
            // 这里调用 AbstractHandlerMapping 中的注册过程
            registerHandler(url, handler);
        }
    }
}

AbstractHandlerMapping 中的注册代码如下:

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
    // 试图从 handlerMap 中取 handler,看看是否已经存在同样的 Url 映射关系
    Object mappedHandler = this.handlerMap.get(urlPath);
    if (mappedHandler != null) {
        // ........
    }

    // 如果是直接用 bean 名做映射那就直接从容器中取 handler
    if (!this.lazyInitHandlers && handler instanceof String) {
        String handlerName = (String) handler;
        if (getApplicationContext().isSingleton(handlerName)) {
            handler = getApplicationContext().getBean(handlerName);
        }
    }
    // 或者使用默认的 handler
    if (urlPath.equals("/*")) {
        setDefaultHandler(handler);
    }
    else {
        // 把 url 和 handler 的对应关系放到 handlerMap 中去
        this.handlerMap.put(urlPath, handler);
        // ........
    }
}

handlerMap 持有的是一个 HashMap,里面保存了具体的映射信息:

private final Map handlerMap = new HashMap();

SimpleUrlHandlerMapping 对接口 HandlerMapping 的实现如下。这个 getHandler 根据在初始化的时候就得到的映射表来生成 DispatcherServlet 需要的执行链:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 这里根据 request 中的参数得到其对应的 handler,具体处理在 AbstractUrlHandlerMapping 中
    Object handler = getHandlerInternal(request);
    // 如果找不到对应的,就使用缺省的 handler
    if (handler == null) {
        handler = this.defaultHandler;
    }
    // 如果缺省的也没有,那就没办法了
    if (handler == null) {
        return null;
    }
    // 如果 handler 不是一个具体的 handler,那我们还要到上下文中取
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = getApplicationContext().getBean(handlerName);
    }
    // 生成一个 HandlerExecutionChain,其中放了我们匹配上的 handler 和定义好的拦截器
    // 就像我们在 HandlerExecutionChain 中看到的那样,它持有一个 handler 和一个拦截器组
    return new HandlerExecutionChain(handler, this.adaptedInterceptors);
}

具体的 Handler 查找过程如下:

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    // 这里的 HTTP Request 传进来的参数进行分析,得到具体的路径信息
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    // ....... 下面是根据请求信息的查找
    return lookupHandler(lookupPath, request);
}

protected Object lookupHandler(String urlPath, HttpServletRequest request) {
    // 如果能够直接能在 SimpleUrlHandlerMapping 的映射表中找到,那最好
    Object handler = this.handlerMap.get(urlPath);
    if (handler == null) {
        // 这里使用模式来对 map 中的所有 handler 进行匹配,调用了 Jre 中的 Matcher 类来完成匹配处理
        String bestPathMatch = null;
        for (Iterator it = this.handlerMap.keySet().iterator(); it.hasNext();) {
            String registeredPath = (String) it.next();
            if (this.pathMatcher.match(registeredPath, urlPath) &&
                            (bestPathMatch == null || bestPathMatch.length() <= registeredPath.length())) {
                // 这里根据匹配路径找到最像的一个
                handler = this.handlerMap.get(registeredPath);
                bestPathMatch = registeredPath;
            }
        }

        if (handler != null) {
            exposePathWithinMapping(this.pathMatcher.extractPathWithinPattern(bestPathMatch, urlPath), request);
        }
    }
    else {
        exposePathWithinMapping(urlPath, request);
    }
    return handler;
}

我们可以看到,总是在 handlerMap 这个 HashMap 中找。当然如果直接找到最好,如果找不到,就看看是不是能通过 Match Pattern 的模式找。我们一定还记得在配置 HandlerMapping 的时候是可以通过 ANT 语法进行配置的,其中的处理就在这里。

这样可以清楚地看到整个 HandlerMapping 的初始化过程,同时我们也看到了一个具体的 Handler 映射是怎样被存储和查找的。这里生成一个 ExecutionChain 来储存我们找到的 Handler 和在定义 Bean 的时候定义的 Interceptors。

请求处理流程

让我们回到 DispatcherServlet。初始化完成以后,实际的 Web 请求是在 doService() 方法中处理的。我们知道 DispatcherServlet 只是一个普通的 Servlet:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // .......
    // 这里把属性信息进行保存
    Map attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        logger.debug("Taking snapshot of request attributes before include");
        attributesSnapshot = new HashMap();
        Enumeration attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith(DispatcherServlet.class.getName())) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    try {
         // 这里是实际的处理入口
        doDispatch(request, response);
    }
    finally {
        // Restore the original attribute snapshot, in case of an include.
        if (attributesSnapshot != null) {
            restoreAttributesAfterInclude(request, attributesSnapshot);
        }
    }
}

对于请求的处理实际上是让 doDispatch() 来完成的。这个方法很长,但是过程很简单明了:

protected void doDispatch(final HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    // 这是从 handlerMapping 中得到的执行链
    HandlerExecutionChain mappedHandler = null;
    int interceptorIndex = -1;
   
    // ........
    try {
        // 我们熟悉的 ModelAndView 开始出现了
        ModelAndView mv = null;
        try {
            processedRequest = checkMultipart(request);

            // 这是我们得到 handler 的过程
            mappedHandler = getHandler(processedRequest, false);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // 这里取出执行链中的 Interceptor 进行前处理
            if (mappedHandler.getInterceptors() != null) {
                for (int i = 0; i < mappedHandler.getInterceptors().length; i++) {
                    HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i];
                    if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
                        triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
                        return;
                    }
                    interceptorIndex = i;
                }
            }

            // 在执行 handler 之前,用 HandlerAdapter 先检查一下 handler 的合法性:是不是按 Spring 的要求编写的
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            // 这里取出执行链中的 Interceptor 进行后处理
            if (mappedHandler.getInterceptors() != null) {
                for (int i = mappedHandler.getInterceptors().length - 1; i >= 0; i--) {
                    HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i];
                    interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
                }
            }
        }
       
        // ........

        // Did the handler return a view to render?
        // 这里对视图生成进行处理
        if (mv != null && !mv.wasCleared()) {
            render(mv, processedRequest, response);
        }
        // .......
    }
}

我们很清楚地看到和 MVC 框架紧密相关的代码,比如如何得到和 HTTP 请求相对应的执行链,怎样执行执行链,以及怎样把模型数据展现到视图中去。

获取 Handler

先看怎样取得 Command 对象,对我们来说就是 Handler。下面是 getHandler 的代码:

protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
    // 在 ServletContext 取得执行链 - 实际上第一次得到它的时候,我们把它放在 ServletContext 进行了缓存
    HandlerExecutionChain handler =
            (HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
    if (handler != null) {
        if (!cache) {
            request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
        }
        return handler;
    }
    // 这里的迭代器迭代的是在 initHandlerMapping 中载入的上下文所有的 HandlerMapping
    Iterator it = this.handlerMappings.iterator();
    while (it.hasNext()) {
        HandlerMapping hm = (HandlerMapping) it.next();
        // .......
        // 这里是实际取得 handler 的过程,在每个 HandlerMapping 中建立的映射表进行检索得到请求对应的 handler
        handler = hm.getHandler(request);

        // 然后把 handler 存到 ServletContext 中去进行缓存
        if (handler != null) {
            if (cache) {
                request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
            }
            return handler;
        }
    }
    return null;
}

如果在 ServletContext 中可以取得 Handler 则直接返回,实际上这个 Handler 是缓冲了上次处理的结果(总要有第一次把这个 Handler 放到 ServletContext 中去)。如果在 ServletContext 中找不到 Handler,那就通过持有的 HandlerMapping 生成一个。我们看到它会迭代当前持有的所有的 HandlerMapping,因为可以定义不止一个,它们在定义的时候也可以指定顺序,直到找到第一个,然后返回。

先找到一个 HandlerMapping,然后通过这个 HandlerMapping 返回一个执行链,里面包含了最终的 Handler 和我们定义的一连串的 Interceptor。具体的我们可以参考上面的 SimpleUrlHandlerMapping 的代码分析,知道 getHandler 是怎样得到一个 HandlerExecutionChain 的。

HandlerAdapter 适配机制

得到 HandlerExecutionChain 以后,我们通过 HandlerAdapter 对这个 Handler 的合法性进行判断:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    Iterator it = this.handlerAdapters.iterator();
    while (it.hasNext()) {
        // 同样对持有的所有 adapter 进行匹配
        HandlerAdapter ha = (HandlerAdapter) it.next();
        if (ha.supports(handler)) {
            return ha;
        }
    }
    // ........
}

通过判断,我们知道这个 Handler 是不是一个 Controller 接口的实现。比如对于具体的 HandlerAdapter —— SimpleControllerHandlerAdapter

public class SimpleControllerHandlerAdapter implements HandlerAdapter {
   
    public boolean supports(Object handler) {
        return (handler instanceof Controller);
    }
    // .......
}

简单的判断一下 Handler 是不是实现了 Controller 接口,这也体现了一种对配置文件进行验证的机制。

让我们再回到 DispatcherServlet 看到代码:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

这个就是对 handle 的具体调用!相当于 Command 模式里的 Command.execute(),理所当然地返回一个 ModelAndView。下面就是一个对 View 进行处理的过程:

if (mv != null && !mv.wasCleared()) {
    render(mv, processedRequest, response);
}

视图解析与渲染

调用的是 render 方法:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
        throws Exception {
    response.setLocale(locale);

    View view = null;
    // 这里把默认的视图放到 ModelAndView 中去
    if (!mv.hasView()) {
        mv.setViewName(getDefaultViewName(request));
    }

    if (mv.isReference()) {
        // 这里对视图名字进行解析
        view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
        // .......
    }
    else {
        // 有可能在 ModelAndView 里已经直接包含了 View 对象,那我们就直接使用
        view = mv.getView();
        // ........
    }

    // 得到具体的 View 对象以后,我们用它来生成视图
    view.render(mv.getModelInternal(), request, response);
}

从整个过程我们看到,先在 ModelAndView 中寻找视图的逻辑名,如果找不到那就使用缺省的视图。如果能够找到视图的名字,那就对它进行解析得到实际的需要使用的视图对象。还有一种可能就是在 ModelAndView 中已经包含了实际的视图对象,这个视图对象是可以直接使用的。

不管怎样,得到一个视图对象以后,通过调用视图对象的 render 来完成数据的显示过程。我们可以看看具体的 JstlView 是怎样实现的。我们在 JstlView 的抽象父类 AbstractView 中找到 render 方法:

public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // .......
    // 这里把所有的相关信息都收集到一个 Map 里
    Map mergedModel = new HashMap(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, mergedModel));
    }
    // 这是实际的展现模型数据到视图的调用
    renderMergedOutputModel(mergedModel, request, response);
}

注解写得很清楚了,先把所有的数据模型进行整合放到一个 Map —— mergedModel 里,然后调用 renderMergedOutputModel()。这个 renderMergedOutputModel 是一个模板方法,它的实现在 InternalResourceView 也就是 JstlView 的父类:

protected void renderMergedOutputModel(
        Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    // Expose the model object as request attributes.
    exposeModelAsRequestAttributes(model, request);

    // Expose helpers as request attributes, if any.
    exposeHelpers(request);

    // 这里得到 InternalResource 定义的内部资源路径
    String dispatcherPath = prepareForRendering(request, response);

    // 这里把请求转发到前面得到的内部资源路径中去
    RequestDispatcher rd = request.getRequestDispatcher(dispatcherPath);
    if (rd == null) {
        throw new ServletException(
                "Could not get RequestDispatcher for [" + getUrl() + "]: check that this file exists within your WAR");
    }
    // .......
}

首先对模型数据进行处理,exposeModelAsRequestAttributes 是在 AbstractView 中实现的。这个方法把 ModelAndView 中的模型数据和其他 Request 数据统统放到 ServletContext 当中去,这样整个模型数据就通过 ServletContext 暴露并得到共享使用了:

protected void exposeModelAsRequestAttributes(Map model, HttpServletRequest request) throws Exception {
    Iterator it = model.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry entry = (Map.Entry) it.next();
        // ..........
        String modelName = (String) entry.getKey();
        Object modelValue = entry.getValue();
        if (modelValue != null) {
            request.setAttribute(modelName, modelValue);
            // ...........
        }
        else {
            request.removeAttribute(modelName);
            // .......
        }
    }
}

让我们回到数据处理部分的 exposeHelpers()。这是一个模板方法,其实在 JstlView 中实现:

public class JstlView extends InternalResourceView {

    private MessageSource jstlAwareMessageSource;

    protected void initApplicationContext() {
        super.initApplicationContext();
        this.jstlAwareMessageSource =
                JstlUtils.getJstlAwareMessageSource(getServletContext(), getApplicationContext());
    }

    protected void exposeHelpers(HttpServletRequest request) throws Exception {
        JstlUtils.exposeLocalizationContext(request, this.jstlAwareMessageSource);
    }
}

JstlUtils 中包含了对于其他而言 JSTL 特殊的数据处理和设置。

过程是不是很长?我们现在在哪里了?我们刚刚完成的是 MVC 中 View 的 render。对于 InternalResourceViewrender 过程比较简单,只是完成一个资源的重定向处理。需要做的就是得到实际 View 的 internalResource 路径,然后转发到那个资源中去。怎样得到资源的路径呢?通过调用:

protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
        throws Exception {
    return getUrl();
}

那这个 URL 在哪里生成呢?我们在 View 相关的代码中没有找到,实际上,它在 ViewResolver 的时候就生成了。在 UrlBasedViewResolver 中:

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 的地方,自然也把生成的 URL 和其他一些和 View 相关的属性也配置好了。

那这个 ViewResolver 是什么时候被调用的呢?我们又要回到 DispatcherServlet 中去看看究竟:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
        throws Exception {
    // ........
    View view = null;

    // 这里设置视图名为默认的名字
    if (!mv.hasView()) {
        mv.setViewName(getDefaultViewName(request));
    }

    if (mv.isReference()) {
        // 这里对视图名进行解析,在解析的过程中根据需要生成实际需要的视图对象
        view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
        // ..........
    }
    // ......
}

下面是对视图名进行解析的具体过程:

protected View resolveViewName(String viewName, Map model, Locale locale, HttpServletRequest request)
        throws Exception {
    // 我们有可能不止一个视图解析器
    for (Iterator it = this.viewResolvers.iterator(); it.hasNext();) {
        ViewResolver viewResolver = (ViewResolver) it.next();
        // 这里是视图解析器进行解析并生成视图的过程
        View view = viewResolver.resolveViewName(viewName, locale);
        if (view != null) {
            return view;
        }
    }
    return null;
}

这里调用具体的 ViewResolver 对视图的名字进行解析。除了单纯的解析之外,它还根据我们的要求生成了我们实际需要的视图对象。具体的 ViewResolver 在 Bean 定义文件中进行定义,同时在 initViewResolver() 方法中被初始化到 viewResolver 变量中。我们看看具体的 InternalResourceViewResolver 是怎样对视图名进行处理并生成 View 对象的。对 resolveViewName 的调用模板在 AbstractCachingViewResolver 中:

public View resolveViewName(String viewName, Locale locale) throws Exception {
    // 如果没有打开缓存设置,那创建需要的视图
    if (!isCache()) {
        logger.warn("View caching is SWITCHED OFF -- DEVELOPMENT SETTING ONLY: This can severely impair performance");
        return createView(viewName, locale);
    }
    else {
        Object cacheKey = getCacheKey(viewName, locale);
        // No synchronization, as we can live with occasional double caching.
        synchronized (this.viewCache) {
            // 这里查找缓存里的视图对象
            View view = (View) this.viewCache.get(cacheKey);
            if (view == null) {
                // 如果在缓存中没有找到,创建一个并把创建的放到缓存中去
                view = createView(viewName, locale);
                this.viewCache.put(cacheKey, view);
                // ........
            }
            return view;
        }
    }
}

关于这些 createView(), loadView(), buildView() 的关系,可查看 Eclipse 里的 Call Hierarchy。

然后我们回到 view.render 中完成数据的最终对 HttpResponse 的写入,比如在 AbstractExcelView 中的实现:

protected final void renderMergedOutputModel(
        Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // .........
    // response.setContentLength(workbook.getBytes().length);
    response.setContentType(getContentType());
    ServletOutputStream out = response.getOutputStream();
    workbook.write(out);
    out.flush();
}

这样就和我们前面的分析一致起来了:DispatcherServlet 在解析视图名的时候就根据要求生成了视图对象,包括在 InternalResourceView 中需要使用的 URL 和其他各种和 HTTP Response 相关的属性都会保存在生成的视图对象中,然后就直接调用视图对象的 render 来完成数据的展示。

总结

这就是整个 Spring Web MVC 框架的大致流程,整个 MVC 流程由 DispatcherServlet 来控制。MVC 的关键过程包括:

  1. 配置到 Handler 的映射关系,以及怎样根据请求参数得到对应的 Handler。在 Spring 中,这是由 HandlerMapping 通过执行链来完成的,而具体的映射关系我们在 Bean 定义文件中定义,并在 HandlerMapping 载入上下文的时候就被配置好了。
  2. DispatcherServlet 调用 HandlerMapping 来得到对应的执行链。
  3. 最后通过视图来展现模型数据,但我们要注意的是视图对象是在解析视图名的时候生成配置好的。

这些作为核心类的 HandlerMappingViewResolverViewHandler 的紧密协作实现了 MVC 的功能。

说明

本文基于较早版本的 Spring 源代码进行分析(代码中可见原始类型的使用,如 MapIterator 等,未广泛使用泛型)。现代 Spring 版本(如 Spring 5.x/6.x 及 Spring Boot)在内部实现细节、类结构及配置方式上已有显著变化(例如注解驱动开发、函数式端点等),但核心设计模式(如前端控制器、适配器模式、策略模式)依然保持一致。阅读时请注意版本差异。