SpringMVC源码分析

作为一名java程序员,SpringMVC大家应该都非常熟悉了,那么今天就来分析一下它的源码。主要从三方面来分析:

  1. DispatcherServlet的启动和初始化
  2. 如何处理分发HTTP请求
  3. 如何呈现视图

**

一. DispatcherServlet的启动和初始化

**
首先,分析DispatcherServlet的初始化,我们知道SpringMVC的实现是基于IoC容器的,所以首先需要知道IoC容器在Web项目中的启动过程,想了解的请看这篇文章:http://www.voidcn.com/article/p-gstvaqxi-brm.html,里面讲到了IoC容器启动过程中建立了全局的根上下文或者说父级上下文,在SpringMVC启动过程中,DispatcherServlet会将此根上下文设置为双亲上下文。下面就具体分析DispatcherServlet的初始化过程:DispatcherServlet的本质是一个Servlet,所以会执行init方法,这个方法在HttpServletBean类中,源码如下:

public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }
        // 利用初始参数设置bean属性
        try {
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }

        // 在子类中实现初始化过程
        initServletBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }

这段代码的try catch块中读取了配置在ServletConfig中的Bean属性参数,在项目的web.xml中。接着会在initServletBean这个方法中实现DispatcherServlet持有的IoC容器的初始化过程,一个新的上下文建立起来了,这个上下文可以看成是上面提到的根上下文的子上下文。根上下文是个Web应用对应的上下文,子上下文是和Servlet对应的上下文。一个Web应用中,可能有多个Servlet的初在,同理,一个根上下文可以作为多个Servlet上下文的双亲上下文。DispatcherServlet上下文建立起来之后会初始化,这个过程在refresh中实现。
下面进入initServletBean中的initWebApplicationContext方法,

protected WebApplicationContext initWebApplicationContext() {
        //找到根上下文
        WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    // 把根上下文设置为DispatcherServlet上下文的父级上下文
                    if (cwac.getParent() == null) {
                        cwac.setParent(rootContext);
                    }
                    //初始化根上下文
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // 如果没有根上下文,建立一个本地根上下文
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            // 执行初始化方法.
            onRefresh(wac);
        }

        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }

程序运行到onRefresh方法之前,此时DispatcherServlet的上下文已经建立,意味着它拥有了独立的Bean定义空间,这为使用xml文件配置MVC中的各个bean创造了条件。进入onRefresh方法,来到了DispatcherServlet中的initStrategies方法,SpringMVC框架的初始化过程就在这里实现,

protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

这里面初始化了各种MVC框架的实现元素,根据单词的字面意思可以猜出个大概,比如有支持国际化的LocaleResolver,支持请求映射的HandlerMappings,支持视图生成的ViewResolvers等,下面就以initHandlerMapping方法为例来看一下源码:

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // 找到ApplicationContext中的所有HandlerMappings,包括跟上下文的
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // 对所有的HandlerMappings 排序
                OrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }

        // 如果没有HandlerMappings,则使用默认的HandlerMappings 
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }

通过这个方法,handlerMappings就获取到了在BeanDefinition中配置好的映射关系,其他初始化方法与之类似,都是通过getBean方法直接从Ioc容器中读取配置。因此,MVC的初始化过程是建立在Ioc容器的初始化过程已经完成的基础上的。

二. 如何处理分发HTTP请求

初始化过程完成之后,每一个HandlerMapping都会持有一个从Http请求到Controller的映射,Spring MVC提供了多个HandlerMapping的实现,下面以SimpleUrlHandlerMapping为例来分析实现过程,在SimpleUrlHandlerMapping中,是通过一个map来持有一系列的映射关系,具体来说,是通过HandlerMapping这个接口类来实现这个映射关系,其中getHandler方法可以得到Http请求的getHandlerExecutionChain,在getHandlerExecutionChain中封装了具体的Controller对象。
HandlerExecutionChain包含了一个handler和interceptors链,handler实际上就是Http请求对应的Controller,interceptors链的存在是为了给handler提供功能的增强。
那url和HandlerExecutionChain的映射关系必须配置好,这个配置过程是在哪里完成的呢?这里有一个注册过程,下面分析这个注册过程在SimpleUrlHandlerMapping中的实现:

public void initApplicationContext() throws BeansException {
        super.initApplicationContext();
        registerHandlers(this.urlMap);
    }

    protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
        if (urlMap.isEmpty()) {
            logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
        }
        else {
            for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
                String url = entry.getKey();
                Object handler = entry.getValue();
                // Prepend with slash if not already present.
                if (!url.startsWith("/")) {
                    url = "/" + url;
                }
                // Remove whitespace from handler bean name.
                if (handler instanceof String) {
                    handler = ((String) handler).trim();
                }
                registerHandler(url, handler);
            }
        }
    }
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
        Assert.notNull(urlPath, "URL path must not be null");
        Assert.notNull(handler, "Handler object must not be null");
        Object resolvedHandler = handler;

        // Eagerly resolve handler if referencing singleton via name.
        if (!this.lazyInitHandlers && handler instanceof String) {
            String handlerName = (String) handler;
            if (getApplicationContext().isSingleton(handlerName)) {
                resolvedHandler = getApplicationContext().getBean(handlerName);
            }
        }

        Object mappedHandler = this.handlerMap.get(urlPath);
        if (mappedHandler != null) {
            if (mappedHandler != resolvedHandler) {
                throw new IllegalStateException(
                        "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
                        "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
            }
        }
        else {
            if (urlPath.equals("/")) {
                if (logger.isInfoEnabled()) {
                    logger.info("Root mapping to " + getHandlerDescription(handler));
                }
                setRootHandler(resolvedHandler);
            }
            else if (urlPath.equals("/*")) {
                if (logger.isInfoEnabled()) {
                    logger.info("Default mapping to " + getHandlerDescription(handler));
                }
                setDefaultHandler(resolvedHandler);
            }
            else {
                this.handlerMap.put(urlPath, resolvedHandler);
                if (logger.isInfoEnabled()) {
                    logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
                }
            }
        }
    }

完成这个解析过程之后把键值对放到handlerMap中,至此,url和handler的映射关系存放在了handlerMap中,准备工作完成。
接下来就是利用HandlerMapping来完成Http请求的映射处理,HandlerMapping接口类有一个getHandler方法,在AbstractHandlerMapping中实现,来看源码:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 获取handler
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }
        // 获取 handlerExecutionChain
        return getHandlerExecutionChain(handler, request);
    }

    // Http请求获取handler
    protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
        // 从Http请求中获取url,进而获取handler
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        Object handler = lookupHandler(lookupPath, request);
        if (handler == null) {
            // 如果handler为空,获取一个默认的handler
            Object rawHandler = null;
            if ("/".equals(lookupPath)) {
                rawHandler = getRootHandler();
            }
            if (rawHandler == null) {
                rawHandler = getDefaultHandler();
            }
            if (rawHandler != null) {
                // Bean name or resolved handler?
                if (rawHandler instanceof String) {
                    String handlerName = (String) rawHandler;
                    rawHandler = getApplicationContext().getBean(handlerName);
                }
                validateHandler(rawHandler, request);
                handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
            }
        }
        if (handler != null && logger.isDebugEnabled()) {
            logger.debug("Mapping [" + lookupPath + "] to " + handler);
        }
        else if (handler == null && logger.isTraceEnabled()) {
            logger.trace("No handler mapping found for [" + lookupPath + "]");
        }
        return handler;
    }

    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        HandlerExecutionChain chain =
            (handler instanceof HandlerExecutionChain) ?
                (HandlerExecutionChain) handler : new HandlerExecutionChain(handler);

        chain.addInterceptors(getAdaptedInterceptors());

        String lookupPath = urlPathHelper.getLookupPathForRequest(request);
        for (MappedInterceptor mappedInterceptor : mappedInterceptors) {
            if (mappedInterceptor.matches(lookupPath, pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        }

        return chain;
    }

通过getHandler方法,就利用Http请求获取到了handler并封装成HandlerExecutionChain 给DispatcherServlet进行Http请求的分发。

接下来分析DispatcherServlet是如何对请求进行分发的,DispatcherServlet是SpirngMVC中非常重要的一个类,不仅建立了自己的IoC容器,在MVC框架初始化完成之后,还肩负着对Http请求进行分发的责任,通过doService中的doDispatch方法进行分发,

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                // 获取当前请求对应的handler
                mappedHandler = getHandler(processedRequest, false);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 获取当前请求对应的handler adaptor
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        String requestUri = urlPathHelper.getRequestUri(request);
                        logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                try {
                    // controller处理请求的方法,触发对controller的handleRequest方法的调用
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                }
                finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }
                //设置view名称
                applyDefaultViewName(request, mv);
                // 执行拦截器的postHandle方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            // 处理handler方法的结果
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Error err) {
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                return;
            }
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }

整个流程如下图所示:
这里写图片描述

这个处理handler请求过程是一个典型的命令模式的使用,下面详细看一下,首先看看DispatcherServlet是如何getHandler的,

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace(
                        "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
            // 通过这个方法进入到AbstractHandlerMapping中的getHandler,和前面的内容接起来了
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }

得到了handler之后,执行handle方法处理Http请求,实际上是利用SimpleControllerHandlerAdapter中的handleRequest来实现,返回一个ModelAndView对象给MVC的视图类进行视图呈现。

3. 如何呈现视图

呈现视图入口方法是在doDispatch->processDispatchResult->render。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Locale locale = this.localeResolver.resolveLocale(request);
        response.setLocale(locale);

        View view;
        if (mv.isReference()) {
            // 解析view name得到view
            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 {
            // 可以直接得到view
            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);
    }

在这个方法中,两种方法从ModelAndView中得到了view,一种是通过ViewResolver解析view name得到,另一种是可以直接得到。最后通过render方法完成视图的呈现,不同的视图有不同的呈现方法。Spring MVC支持常见的视图,View接口设计简单,只需要实现render方法。继承关系图如下:
这里写图片描述
具体每一种视图的实现,刚兴趣的朋友可以自行分析。

至此,SpringMVC的源码就看完了,如果不正确之处请指出

相关文章
相关标签/搜索