Spring源码解析之Spring MVC

2023-07-18 14:37:51 浏览数 (2)

下面我们对Spring MVC框架代码进行分析,对于webApplicationContext的相关分析可以参见以前的文档,我们这里着重分析Spring Web MVC 框架的实现.我们从分析 DispatcherServlet 入手:

代码语言:javascript复制
 1//这里是对 DispatcherServlet 的初始化方法,根据名字我们很方面的看到对各个 Spring MVC 主要元素的初始化 
 2protected void initFrameworkServlet() throws ServletException, BeansException { 
 3    initMultipartResolver(); 
 4    initLocaleResolver(); 
 5    initThemeResolver(); 
 6    initHandlerMappings(); 
 7    initHandlerAdapters(); 
 8    initHandlerExceptionResolvers(); 
 9    initRequestToViewNameTranslator(); 
10    initViewResolvers(); 
11} 

看到注解我们知道,这是 DispatcherSerlvet 的初始化过程,它是在 WebApplicationContext 已经存在的情况下进行的,也就意味着在初始化它的时候,IOC 容器应该已经工作了,这也是我们在 web.xml 中配置 Spring 的时候,需要把 DispatcherServlet 的 load-on-startup的属性配置为 2 的原因。

对于具体的初始化过程,很容易理解,我们拿 initHandlerMappings()来看看:

代码语言:javascript复制
 1private void initHandlerMappings() throws BeansException { 
 2    if (this.detectAllHandlerMappings) { 
 3        // 这里找到所有在上下文中定义的 HandlerMapping,同时把他们排序 
 4        // 因为在同一个上下文中可以有不止一个 handlerMapping,所以我们把他们都载入到一个链里进行维护和管理 
 5        Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( 
 6        getWebApplicationContext(), HandlerMapping.class, true, false); 
 7        if (!matchingBeans.isEmpty()) { 
 8            this.handlerMappings = new ArrayList(matchingBeans.values()); 
 9            // 这里通过 order 属性来对 handlerMapping 来在 list 中排序 
10            Collections.sort(this.handlerMappings, new OrderComparator()); 
11        } 
12    }else { 
13        try { 
14            Object hm = getWebApplicationContext().getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); 
15            this.handlerMappings = Collections.singletonList(hm); 
16        }catch (NoSuchBeanDefinitionException ex) { 
17            // Ignore, we'll add a default HandlerMapping later. 
18        } 
19    } 
20
21    //如果在上下文中没有定义的话,那么我们使用默认的 BeanNameUrlHandlerMapping 
22    if (this.handlerMappings == null) { 
23        this.handlerMappings = getDefaultStrategies(HandlerMapping.class); 
24        ...
25    } 
26} 

怎样获得上下文环境,可以参见我们前面的对 IOC 容器在 web 环境中加载的分析。 DispatcherServlet 把定义了的所有 HandlerMapping都加载了放在一个 List 里待以后进行使用,这个链的每一个元素都是一个 handlerMapping 的配置,而一般每一个 handlerMapping 可以持有一系列从 URL 请求到 Spring Controller 的映射,比如 SimpleUrl HandlerMaaping 中就定义了一个 map 来持有这一系列的映射关系。

DisptcherServlet 通过 HandlerMapping 使得 Web 应用程序确定一个执行路径,就像我们在 HanderMapping 中看到的那样,HandlerMapping 只是一个借口:

代码语言:javascript复制
1public interface HandlerMapping { 
2    public static final String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = 
3    Conventions.getQualifiedAttributeName(HandlerMapping.class, "pathWithinHandlerMapping"); 
4    //实际上维护一个 HandlerExecutionChain,这是典型的 Command 的模式的使用,这个执行链里面维护 handler 和拦截器 
5    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; 
6} 

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

代码语言:javascript复制
1public class HandlerExecutionChain { 
2    private Object handler; 
3
4    private HandlerInterceptor[] interceptors; 
5
6    ...
7} 

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

代码语言:javascript复制
 1protected void registerHandlers(Map urlMap) throws BeansException { 
 2    if (urlMap.isEmpty()) { 
 3        logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping"); 
 4    }else { 
 5        //这里迭代在 SimpleUrlHandlerMapping 中定义的所有映射元素 
 6        Iterator it = urlMap.keySet().iterator(); 
 7        while (it.hasNext()) { 
 8            //这里取得配置的 url 
 9            String url = (String) it.next(); 
10            //这里根据 url 在 bean 定义中取得对应的 handler 
11            Object handler = urlMap.get(url); 
12            // Prepend with slash if not already present. 
13            if (!url.startsWith("/")) { 
14                url = "/"   url; 
15            } 
16            //这里调用 AbstractHandlerMapping 中的注册过程 
17            registerHandler(url, handler); 
18        } 
19    } 
20} 

在 AbstractMappingHandler 中的注册代码:

代码语言:javascript复制
 1protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { 
 2    //试图从 handlerMap 中取 handler,看看是否已经存在同样的 Url 映射关系 
 3    Object mappedHandler = this.handlerMap.get(urlPath); 
 4    if (mappedHandler != null) { 
 5        ...
 6    } 
 7
 8    //如果是直接用 bean 名做映射那就直接从容器中取 handler 
 9    if (!this.lazyInitHandlers && handler instanceof String) { 
10        String handlerName = (String) handler; 
11        if (getApplicationContext().isSingleton(handlerName)) { 
12            handler = getApplicationContext().getBean(handlerName); 
13        } 
14    } 
15    //或者使用默认的 handler. 
16    if (urlPath.equals("/*")) { 
17        setDefaultHandler(handler); 
18    }else { 
19        //把 url 和 handler 的对应关系放到 handlerMap 中去 
20        this.handlerMap.put(urlPath, handler); 
21        ...
22    } 
23} 

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

代码语言:javascript复制
1private final Map handlerMap = new HashMap(); 

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

代码语言:javascript复制
 1public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 
 2    //这里根据 request 中的参数得到其对应的 handler,具体处理在 AbstractUrlHandlerMapping 中 
 3    Object handler = getHandlerInternal(request); 
 4    //如果找不到对应的,就使用缺省的 handler 
 5    if (handler == null) { 
 6        handler = this.defaultHandler; 
 7    } 
 8    //如果缺省的也没有,那就没办法了 
 9    if (handler == null) { 
10        return null; 
11    } 
12    // 如果 handler 不是一个具体的 handler,那我们还要到上下文中取 
13    if (handler instanceof String) { 
14        String handlerName = (String) handler; 
15        handler = getApplicationContext().getBean(handlerName); 
16    } 
17    //生成一个 HandlerExecutionChain,其中放了我们匹配上的 handler 和定义好的拦截器,就像我们在 HandlerExecutionChain 中看到的那样,它持有一个 handler 和一个拦截器组。 
18    return new HandlerExecutionChain(handler, this.adaptedInterceptors); 
19} 

我们看看具体的 handler 查找过程:

代码语言:javascript复制
 1protected Object getHandlerInternal(HttpServletRequest request) throws Exception { 
 2    //这里的 HTTP Request 传进来的参数进行分析,得到具体的路径信息。 
 3    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); 
 4    ...
 5    //下面是根据请求信息的查找 
 6    return lookupHandler(lookupPath, request); 
 7} 
 8
 9protected Object lookupHandler(String urlPath, HttpServletRequest request) { 
10    // 如果能够直接能在 SimpleUrlHandlerMapping 的映射表中找到,那最好。 
11    Object handler = this.handlerMap.get(urlPath); 
12    if (handler == null) { 
13        // 这里使用模式来对 map 中的所有 handler 进行匹配,调用了 Jre 中的 Matcher 类来完成匹配处理。 
14        String bestPathMatch = null; 
15        for (Iterator it = this.handlerMap.keySet().iterator(); it.hasNext();) { 
16            String registeredPath = (String) it.next(); 
17            if (this.pathMatcher.match(registeredPath, urlPath) && 
18            (bestPathMatch == null || bestPathMatch.length() <= registeredPath.length())) { 
19                //这里根据匹配路径找到最象的一个 
20                handler = this.handlerMap.get(registeredPath); 
21                bestPathMatch = registeredPath; 
22            } 
23        } 
24
25        if (handler != null) { 
26            exposePathWithinMapping(this.pathMatcher.extractPathWithinPattern(bestPathMatch, urlPath), request); 
27        } 
28    }else { 
29        exposePathWithinMapping(urlPath, request); 
30    } 
31    return handler; 
32} 

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

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

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

代码语言:javascript复制
 1protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { 
 2    ...
 3    //这里把属性信息进行保存 
 4    Map attributesSnapshot = null; 
 5    if (WebUtils.isIncludeRequest(request)) { 
 6        logger.debug("Taking snapshot of request attributes before include"); 
 7        attributesSnapshot = new HashMap(); 
 8        Enumeration attrNames = request.getAttributeNames(); 
 9        while (attrNames.hasMoreElements()) { 
10            String attrName = (String) attrNames.nextElement(); 
11            if (this.cleanupAfterInclude || attrName.startsWith(DispatcherServlet.class.getName())) { 
12                attributesSnapshot.put(attrName, request.getAttribute(attrName)); 
13            } 
14        } 
15    } 
16
17    // Make framework objects available to handlers and view objects. 
18    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); 
19    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); 
20    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); 
21    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); 
22
23    try { 
24        //这里使实际的处理入口 
25        doDispatch(request, response); 
26    } 
27    finally { 
28        // Restore the original attribute snapshot, in case of an include. 
29        if (attributesSnapshot != null) { 
30            restoreAttributesAfterInclude(request, attributesSnapshot); 
31        } 
32    } 
33} 

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

代码语言:javascript复制
 1protected void doDispatch(final HttpServletRequest request, HttpServletResponse response) throws Exception { 
 2    HttpServletRequest processedRequest = request; 
 3    //这是从 handlerMapping 中得到的执行链 
 4    HandlerExecutionChain mappedHandler = null; 
 5    int interceptorIndex = -1; 
 6
 7    ...
 8    try { 
 9        //我们熟悉的 ModelAndView 开始出现了。 
10        ModelAndView mv = null; 
11        try { 
12            processedRequest = checkMultipart(request); 
13
14            // 这是我们得到 handler 的过程 
15            mappedHandler = getHandler(processedRequest, false); 
16            if (mappedHandler == null || mappedHandler.getHandler() == null) { 
17                noHandlerFound(processedRequest, response); 
18                return; 
19            } 
20
21            // 这里取出执行链中的 Interceptor 进行前处理 
22            if (mappedHandler.getInterceptors() != null) { 
23                for (int i = 0; i < mappedHandler.getInterceptors().length; i  ) { 
24                    HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i]; 
25                    if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { 
26                        triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); 
27                        return; 
28                    } 
29                    interceptorIndex = i; 
30                } 
31            } 
32
33            //在执行 handler 之前,用 HandlerAdapter 先检查一下 handler 的合法性:是不是按 Spring 的要求编写的。 
34            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 
35            mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 
36
37            // 这里取出执行链中的 Interceptor 进行后处理 
38            if (mappedHandler.getInterceptors() != null) { 
39                for (int i = mappedHandler.getInterceptors().length - 1; i >= 0; i--) { 
40                    HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i]; 
41                    interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv); 
42                } 
43            } 
44        } 
45
46        ...
47
48        // Did the handler return a view to render? 
49        //这里对视图生成进行处理 
50        if (mv != null && !mv.wasCleared()) { 
51            render(mv, processedRequest, response); 
52        } 
53        ...
54} 

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

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

代码语言:javascript复制
 1protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception { 
 2    //在 ServletContext 取得执行链 - 实际上第一次得到它的时候,我们把它放在 ServletContext 进行了缓存。 
 3    HandlerExecutionChain handler = (HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE); 
 4    if (handler != null) { 
 5        if (!cache) { 
 6            request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE); 
 7        } 
 8        return handler; 
 9    } 
10    //这里的迭代器迭代的时在 initHandlerMapping 中载入的上下文所有的 HandlerMapping 
11    Iterator it = this.handlerMappings.iterator(); 
12    while (it.hasNext()) { 
13        HandlerMapping hm = (HandlerMapping) it.next(); 
14        ...
15        //这里是实际取得 handler 的过程,在每个 HandlerMapping 中建立的映射表进行检索得到请求对应的 handler 
16        handler = hm.getHandler(request); 
17
18        //然后把 handler 存到 ServletContext 中去进行缓存 
19        if (handler != null) { 
20            if (cache) { 
21                request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler); 
22            } 
23            return handler; 
24        } 
25    } 
26    return null; 
27} 

如果在 ServletContext 中可以取得 handler 则直接返回,实际上这个 handler 是缓冲了上次处理的结果 - 总要有第一次把这个 handler放到 ServletContext 中去:

如果在 ServletContext 中找不到 handler,那就通过持有的 handlerMapping 生成一个,我们看到它会迭代当前持有的所有的handlerMapping,因为可以定义不止一个,他们在定义的时候也可以指定顺序,直到找到第一个,然后返回。先找到一个 handlerMapping,然后通过这个 handlerMapping 返回一个执行链,里面包含了最终的 Handler 和我们定义的一连串的 Interceptor。具体的我们可以参考上面的 SimpleUrlHandlerMapping 的代码分析知道 getHandler 是怎样得到一个 HandlerExecutionChain 的。

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

代码语言:javascript复制
 1protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { 
 2    Iterator it = this.handlerAdapters.iterator(); 
 3    while (it.hasNext()) { 
 4        //同样对持有的所有 adapter 进行匹配 
 5        HandlerAdapter ha = (HandlerAdapter) it.next(); 
 6        if (ha.supports(handler)) { 
 7            return ha; 
 8        } 
 9    } 
10    ...
11} 

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

代码语言:javascript复制
1public class SimpleControllerHandlerAdapter implements HandlerAdapter { 
2    public boolean supports(Object handler) { 
3        return (handler instanceof Controller); 
4    } 
5    ...
6} 

简单的判断一下 handler 是不是实现了 Controller 接口。这也体现了一种对配置文件进行验证的机制。 让我们再回到 DispatcherServlet 看到代码:

代码语言:javascript复制
1mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 

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

代码语言:javascript复制
1if (mv != null && !mv.wasCleared()) { 
2    render(mv, processedRequest, response); 
3} 

调用的是 render 方法:

代码语言:javascript复制
 1protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {response.setLocale(locale); 
 2    View view = null; 
 3    //这里把默认的视图放到 ModelAndView 中去。 
 4    if (!mv.hasView()) { 
 5        mv.setViewName(getDefaultViewName(request)); 
 6    } 
 7
 8    if (mv.isReference()) { 
 9        // 这里对视图名字进行解析 
10        view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); 
11        ...
12    }else { 
13        // 有可能在 ModelAndView 里已经直接包含了 View 对象,那我们就直接使用。 
14        view = mv.getView(); 
15        ...
16    } 
17
18    //得到具体的 View 对象以后,我们用它来生成视图。 
19    view.render(mv.getModelInternal(), request, response); 
20} 

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

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

代码语言:javascript复制
 1public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { 
 2    ...
 3    // 这里把所有的相关信息都收集到一个 Map 里 
 4    Map mergedModel = new HashMap(this.staticAttributes.size()   (model != null ? model.size() : 0)); 
 5    mergedModel.putAll(this.staticAttributes); 
 6    if (model != null) { 
 7        mergedModel.putAll(model); 
 8    } 
 9
10    // Expose RequestContext? 
11    if (this.requestContextAttribute != null) { 
12        mergedModel.put(this.requestContextAttribute, createRequestContext(request, mergedModel)); 
13    } 
14    //这是实际的展现模型数据到视图的调用。 
15    renderMergedOutputModel(mergedModel, request, response); 
16} 

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

代码语言:javascript复制
 1protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { 
 2    // Expose the model object as request attributes. 
 3    exposeModelAsRequestAttributes(model, request); 
 4
 5    // Expose helpers as request attributes, if any. 
 6    exposeHelpers(request); 
 7
 8    // 这里得到 InternalResource 定义的内部资源路径。 
 9    String dispatcherPath = prepareForRendering(request, response); 
10
11    //这里把请求转发到前面得到的内部资源路径中去。 
12    RequestDispatcher rd = request.getRequestDispatcher(dispatcherPath); 
13    if (rd == null) { 
14        throw new ServletException("Could not get RequestDispatcher for ["   getUrl()   "]: check that this file exists within your WAR"); 
15    } 
16    ...

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

代码语言:javascript复制
 1protected void exposeModelAsRequestAttributes(Map model, HttpServletRequest request) throws Exception { 
 2    Iterator it = model.entrySet().iterator(); 
 3    while (it.hasNext()) { 
 4        Map.Entry entry = (Map.Entry) it.next(); 
 5        ...
 6        String modelName = (String) entry.getKey(); 
 7        Object modelValue = entry.getValue(); 
 8        if (modelValue != null) { 
 9            request.setAttribute(modelName, modelValue); 
10            ...
11        }else { 
12            request.removeAttribute(modelName); 
13            ...
14        } 
15    } 
16} 

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

代码语言:javascript复制
 1public class JstlView extends InternalResourceView { 
 2    private MessageSource jstlAwareMessageSource; 
 3
 4    protected void initApplicationContext() { 
 5        super.initApplicationContext(); 
 6        this.jstlAwareMessageSource = 
 7        JstlUtils.getJstlAwareMessageSource(getServletContext(), getApplicationContext()); 
 8    } 
 9
10    protected void exposeHelpers(HttpServletRequest request) throws Exception { 
11        JstlUtils.exposeLocalizationContext(request, this.jstlAwareMessageSource); 
12    } 
13
14} 

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

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

代码语言:javascript复制
1protected String prepareForRendering(HttpServletRequest request, HttpServletResponseresponse) throws Exception { 
2    return getUrl(); 

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

代码语言:javascript复制
 1protected AbstractUrlBasedView buildView(String viewName) throws Exception { 
 2    AbstractUrlBasedView view = (AbstractUrlBasedView) 
 3        BeanUtils.instantiateClass(getViewClass()); 
 4    view.setUrl(getPrefix()   viewName   getSuffix()); 
 5    String contentType = getContentType(); 
 6    if (contentType != null) { 
 7        view.setContentType(contentType); 
 8    } 
 9    view.setRequestContextAttribute(getRequestContextAttribute()); 
10    view.setAttributesMap(getAttributesMap()); 
11    return view; 
12} 

这里是生成 View 的地方,自然也把生成的 url 和其他一些和 view 相关的属性也配置好了。

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

代码语言:javascript复制
 1protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { 
 2    ...
 3    View view = null; 
 4
 5    // 这里设置视图名为默认的名字 
 6    if (!mv.hasView()) { 
 7        mv.setViewName(getDefaultViewName(request)); 
 8    } 
 9
10    if (mv.isReference()) { 
11        //这里对视图名进行解析,在解析的过程中根据需要生成实际需要的视图对象。 
12        view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); 
13        ...
14    } 
15    ...
16} 

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

代码语言:javascript复制
 1protected View resolveViewName(String viewName, Map model, Locale locale, HttpServletRequest request) throws Exception { 
 2    //我们有可能不止一个视图解析器 
 3    for (Iterator it = this.viewResolvers.iterator(); it.hasNext();) { 
 4        ViewResolver viewResolver = (ViewResolver) it.next(); 
 5        //这里是视图解析器进行解析并生成视图的过程。 
 6        View view = viewResolver.resolveViewName(viewName, locale); 
 7        if (view != null) { 
 8            return view; 
 9        } 
10    } 
11    return null; 
12} 

这里调用具体的 ViewResolver 对视图的名字进行解析 - 除了单纯的解析之外,它还根据我们的要求生成了我们实际需要的视图对象。

具体的 viewResolver 在 bean 定义文件中进行定义同时在 initViewResolver()方法中被初始化到 viewResolver 变量中,我们看看具体的InternalResourceViewResolver 是怎样对视图名进行处理的并生成 V 视图对象的:对resolveViewName 的调用模板在AbstractCachingViewResolver 中,

代码语言:javascript复制
 1public View resolveViewName(String viewName, Locale locale) throws Exception { 
 2    //如果没有打开缓存设置,那创建需要的视图 
 3    if (!isCache()) { 
 4        logger.warn("View caching is SWITCHED OFF -- DEVELOPMENT SETTING ONLY: This can severely impair performance"); 
 5        return createView(viewName, locale); 
 6    }else { 
 7        Object cacheKey = getCacheKey(viewName, locale); 
 8        // No synchronization, as we can live with occasional double caching. 
 9        synchronized (this.viewCache) { 
10            //这里查找缓存里的视图对象 
11            View view = (View) this.viewCache.get(cacheKey); 
12            if (view == null) { 
13                //如果在缓存中没有找到,创建一个并把创建的放到缓存中去 
14                view = createView(viewName, locale); 
15                this.viewCache.put(cacheKey, view); 
16                ...
17            } 
18            return view; 
19        } 
20    } 
21} 

关于这些 createView(),loadView(),buildView()的关系,我们看看 Eclipse 里的 call hiearchy 然后我们回到 view.render 中完成数据的最终对 httpResponse 的写入,比如在 AbstractExcelView 中的实现:

代码语言:javascript复制
1protected final void renderMergedOutputModel( 
2    Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { 
3    ...
4    // response.setContentLength(workbook.getBytes().length); 
5    response.setContentType(getContentType()); 
6    ServletOutputStream out = response.getOutputStream(); 
7    workbook.write(out); 
8    out.flush(); 
9} 

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

这就是整个 Spring Web MVC 框架的大致流程,整个 MVC 流程由 DispatcherServlet 来控制。MVC 的关键过程包括: 配置到 handler 的映射关系和怎样根据请求参数得到对应的 handler,在 Spring 中,这是由 handlerMapping 通过执行链来完成的,而具体的映射关系我们在 bean 定义文件中定义并在 HandlerMapping 载入上下文的时候就被配置好了。然后 DispatcherServlet 调用HandlerMapping 来得到对应的执行链,最后通过视图来展现模型数据,但我们要注意的是视图对象是在解析视图名的时候生成配置好的。这些作为核心类的HanderMapping,ViewResolver,View,Handler 的紧密协作实现了 MVC 的功能。

0 人点赞