debug方式深入springMVC源码的实现

前言

转载请注明来源
        上一次写博客已经是5月份的事了,其实是蛮想抽时间写写,沉淀一下自己,无奈,天天加班加点,人都傻了,心累.真心觉得,对于一个搞技术的人来说,不能只是工作工作,虽然这是必要的(毕竟大多数人还是要吃饭的),但是经常的周期性的沉淀下自己,总结自己一段时间的收获是分重要的.
        话不多说了,本篇主要是针对一下springMVC的处理流程及实现原理通过debug方式层层深入理解.
        整个篇幅过长,偏向于个人的一个debug过程的展示,以及个人理解的笔记,感兴趣的可以参照我的流程进行debug,加上一些我个人的理解与提示,多debug几遍流程,相信看的人都能得到自己的理解.

服务器原理简介

springMVC作为现在Java这块非常流行的MVC框架,基本上只要会spring就可以无缝衔接springMVC,
那么springMVC到底是如何工作的呢,这里不得不提的是服务器,相信在Java Web这块的程序员们都非常的熟悉.
正好最近撸了点tomcat源码的皮毛,了解了类tomcat等的web服务器的工作原理,有兴趣的可以吃一吃我的安利
<<how tomcat works>> 这本书.
那么为什么需要服务器呢?
简单来讲,服务器通过ServerSocket获取到Http请求然后对其解析并封装成Request和Response对象,
然后将其交给Servlet容器进行处理,选择对应的Servlet处理请求,返回结果(实际上是很复杂,作为一个web
程序员,这个真的是应该好好了解研究的).
那么为什么tomcat和springmvc可以结合起来呢,最最核心的原因是他们都是基于Servlet规范的,
由于Servlet规范,他们可以互相通信(服务器和SpringMVC的结合在debug时将会简单体现).

SpringMVC

开始详解SpringMVC了.

1、web.xml

 web.xml中配置了最重要的ContextLoaderListener以及DispatchServlet.
 ContextLoaderListener用于启动web容器时,自动装ApplicationContext的配置信息,
 由于 ContextLoaderListener实现了ServletContextListener,所以在web容器启动应用时,
 创建ServletContext对象,每个应用都有一个对应的ServletContext对象,ServletContext在应用关闭
 时将会销毁,在启动时,可以向ServletContext中添加WebApplicationContext,这个在ServletContext
 整个运行期间都是可见的.
 DispatchServlet是SpringMVC最重要的一个类,它实现了Servlet,用于对请求做逻辑处理,相应的
 ContextLoaderListener实际上只是为了创建WebApplicationContext,DispatchServlet则是负责了
 SpringMVC中对客户端请求的逻辑处理,我们的每个请求都是经过DispatchServlet进行处理,调用对应的
 逻辑实现(Controller中对应的请求的方法),返回视图,所以说DispatchServlet是SpringMVC中最重要的一个
 类一点不为过.

2、启动

终于要接触代码了,下面就对springMVC启动过程的流程与逻辑进行debug。
本文采用的是Jetty服务器.

如下所示,web.xml中配置的监听器类:

image.png

可以看到,该类实现了ServletContextListener,ServletContextListener接口,当系统启动时,将会调用ServletContextListener的实现类的contextInitialized方法,用于在初始化时加入一些属性,这样就可以在全局范围内调用这些属性.
debug启动web工程,直入主题,在contextInitialized方法出打断点:


image.png

继续跳入父类ContextLoader中的initWebApplicationContext方法中进行WebApplicationContext的创建,WebApplicationContext是继承自ApplicationContext,在ApplicationContext的基础上增加了一些特定于Web的操作及属性,详细可以自行查看.
下面是整个initWebApplicationContext方法的详细代码,加入了一点个人理解的注释:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //验证context是否已经存在,
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException(
                    "Cannot initialize context because there is already a root application context present - " +
                    "check whether you have multiple ContextLoader* definitions in your web.xml!");
        }

        Log logger = LogFactory.getLog(ContextLoader.class);
        servletContext.log("Initializing Spring root WebApplicationContext");
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            //初始化 WebApplicationContext
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent ->
                        // determine parent for root web application context, if any.
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    //刷新上下文环境
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            //将WebApplicationContext记录在servletContext中
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                //映射当前的类加载器与context实例到全局变量currentContextPerThread中
                currentContextPerThread.put(ccl, this.context);
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
            }
            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
            }

            return this.context;
        }
        catch (RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
        catch (Error err) {
            logger.error("Context initialization failed", err);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
            throw err;
        }
    }

debug到下图280行,创建WebApplicationContext,

image.png

debug 跳入createWebApplicationContext(servletContext)方法中,

image.png

determineContextClass方法返回一个WebApplicationContext 接口的实现类,否则默认返回XmlWebApplicationContext 或者一个指定的context

image.png

此处有一个defaultStrategies,可以看下图,ContextLoader有一个static代码块,


image.png
image.png

image.png

通过以上我们可以得知,在ContextLoader类加载的时候就先读取了ContextLoader同级目录下的ContextLoader.properties配置文件,在初始化WebApplicationContext时,根据其中的配置提取WebApplicationContext接口的实现类,并根据这个实现类通过反射的方式进行实例的创建.

image.png

接着debug走,将WebApplicationContext记录在servletContext中


image.png

映射当前的类加载器与context实例到全局变量currentContextPerThread中


image.png
初始化servlet
   SpringMVC通过DispatcherServlet对请求进行处理,而DispatcherServlet是实现了Servlet接口的,而servlet接口是基于servlet规范编写的一个Java类,实际上一个servlet会经历三个阶段:初始化阶段、运行阶段、销毁阶段,也就是我们熟知的servlet 的init、doGet/doPost、destroy这三个方法的执行,而dispatchservlet是实现了servlet接口的,那么必然也会经历这三个阶段,下面是DispatchServlet类的父类结构图:
image.png

可以看到dispatServlet的父类FrameworkServlet,FrameworkServlet又继承了HttpServletBean,实际上整个servlet的三个阶段都在这三个类中做了具体的实现。
初始化阶段在HttpServletBean中可以找到相应的方法,

@Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // Set bean properties from init parameters.
        try {
            //解析init parameters 并封装到PropertyValues中
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            //将当前这个Servlet类转化为BeanWrapper,从而能以spring的方式对init parameters的值进行注入
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            //注册自定义属性编辑器,一旦遇到resource类型的属性将会使用ResourceEditor进行解析
            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;
        }

        // Let subclasses do whatever initialization they like.
        //在FrameworkServlet中覆盖了该方法
        initServletBean();

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

debug进入init方法中,跳入FrameworkServlet的initServletBean方法中,如下图

image.png

可以看到,最重要的就是this.webApplicationContext = initWebApplicationContext();
这段代码,这个方法的作用是创建或刷新WebApplicationContext实例,并对servlet功能所使用的变量进行初始化:

image.png
image.png

可以看到上图这段代码将不会执行,直接到if(wac == null)中去了,

image.png

跳入findWebApplicationContext方法中,这个方法是用于根据ContextAttribute属性加载WebApplicationContext,但这里可以看到ContextAttribute为空,所以这段代码最终返回的还是null

image.png
image.png
image.png

接着走下一个if,

image.png
image.png
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    //获取servlet初始化参数,如果没有则默认为XMLWebApplicationContext.class
        Class<?> contextClass = getContextClass();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet with name '" + getServletName() +
                    "' will try to create custom WebApplicationContext context of class '" +
                    contextClass.getName() + "'" + ", using parent context [" + parent + "]");
        }
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException(
                    "Fatal initialization error in servlet with name '" + getServletName() +
                    "': custom WebApplicationContext class [" + contextClass.getName() +
                    "] is not of type ConfigurableWebApplicationContext");
        }
        //通过反射的方式实例化contextClass
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
        //设置servlet环境
        wac.setEnvironment(getEnvironment());
        //这个parent使用的就是ContextLoaderListener初始化时创建的那个root WebApplicationContext
        wac.setParent(parent);
        //获取ContextConfigLocation属性,配置在servlet初始化参数中
        wac.setConfigLocation(getContextConfigLocation());
        //初始化spring环境包括加载配置文件等
        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }

上面是对createWebApplicationContext方法的一个详细介绍,下面debug一步一步看这个方法的逻辑:

image.png
image.png
image.png

接着创建wac并配置了servlet初始化的一些参数后,初始化整个spring的环境:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            // The application context id is still set to its original default value
            // -> assign a more useful id based on available information
            if (this.contextId != null) {
                wac.setId(this.contextId);
            }
            else {
                // Generate default id...
                ServletContext sc = getServletContext();
                if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
                    // Servlet <= 2.4: resort to name specified in web.xml, if any.
                    String servletContextName = sc.getServletContextName();
                    if (servletContextName != null) {
                        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
                                "." + getServletName());
                    }
                    else {
                        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
                    }
                }
                else {
                    // Servlet 2.5's getContextPath available!
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                            ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
                }
            }
        }

        wac.setServletContext(getServletContext());
        wac.setServletConfig(getServletConfig());
        wac.setNamespace(getNamespace());
        wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

        // the wac environment's #initPropertySources will be called in any case when
        // the context is refreshed; do it eagerly here to ensure servlet property sources
        // are in place for use in any post-processing or initialization that occurs
        // below prior to #refresh
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig());
        }

        postProcessWebApplicationContext(wac);

        applyInitializers(wac);
        //加载配置文件及整合parent到wac中
        wac.refresh();
    }

    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }
        }
    }

上述代码每一步都有自带的注释,相信很容易就能理解,其中的onRefresh方法是FrameworkServlet类中提供的模板方法,在子类dispatchservlet中进行了重写,其主要作用就是为了刷新spring在web功能实现中必须使用的全局变量,这些变量在接下来的处理请求响应中将会用到,如下,就不详细介绍了

    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    /**
     * Initialize the strategy objects that this servlet uses.
     * <p>May be overridden in subclasses in order to initialize further strategy objects.
     */
    protected void initStrategies(ApplicationContext context) {
        /**
        * 初始化MultipartResolver,主要用于处理文件上传,默认情况下,spring是没有Multipart处理的
        * 需要用户自己去配置,常用配置如下:
        * <bean id="multipartResolver"
        *  class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        * <property name="defaultEncoding" value="utf-8"></property>
        * <property name="maxUploadSize" value="10485760000"></property>
        * <property name="maxInMemorySize" value="40960"></property>
        * </bean>
        */
        initMultipartResolver(context);
        /**
        * 初始化LocaleResolver,用于国际化配置
        */
        initLocaleResolver(context);
        /**
        * 初始化ThemeResolver,主题theme用于控制网页风格
        */
        initThemeResolver(context);
        /**
        * 初始化HandlerMappings,dispatchservlet会将Request请求提交给HandlerMapping然后HandlerMapping
        * 根据webapplicationcontext的配置来返回给dispatchservlet 相应的controller
        * 默认情况下,springMVC会加载当前系统中所有实现了HandlerMapping接口的bean
        */
        initHandlerMappings(context);
        /**
        * 初始化HandlerAdapters,适配器设计模式,HandlerAdapter适配当前请求到对应的控制器,
        * 
        */
        initHandlerAdapters(context);
        /**
        * 初始化HandlerExceptionResolvers,
        */
        initHandlerExceptionResolvers(context);
        /**
        * 初始化RequestToViewNameTranslator,当controller处理方法没有返回一个view或者逻辑视图名称时,并且
        * 没有在该方法中直接往response的输出流中写数据时,就会通过RequestToViewNameTranslator接口的实现类
        * 来提供一个约定好的逻辑视图名称供使用,spring中提供了一个默认的实现类
        */
        initRequestToViewNameTranslator(context);
        /**
        * 初始化ViewResolvers,当controller将请求处理结果放入到modelandview中后,dispatchservlet会根据
        * modelandview选择合适的视图进行渲染,springMVC通过ViewResolver接口定义的resolverViewName方法
        * 根据合适的viewname创建对应的view.
        * 配置如下:
        * <bean
        * class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        * <property name="prefix" value="/WEB-INF/views/" />
        * <property name="suffix" value=".jsp" />
        * </bean>
        */
        initViewResolvers(context);
        /**
        * 初始化FlashMapManager用于管理,FlashMapManager用于管理FlashMap,FlashMap用于保持flash attributes,
        * flash attributes提供了一个请求存储属性,在使用请求重定向时非常重要,flash attributes在重定向之前暂存
        * 以便重定向之后还能使用,并立即删除.
        */
        initFlashMapManager(context);
    }

创建完WebApplicationContext 并刷新成功后,接着走下一步


image.png

发布wac

image.png

到此,dispatchservlet初始化完成,整个web工程才算启动完成.

image.png

处理请求响应

完成了servlet的初始化过程后,现在可以进行对请求的处理响应过程了,打开浏览器地址栏输入url;
http://localhost:8080/demo-idle-web/index.do

这个时候其实可以通过debug的信息简略看下服务器是如何处理请求并与springMVC交互的:


上图可以看到,从最下面的信息看起,可以看到jetty服务器先解析http请求,解析成HTTPServletRequest以及HTTPServletResponse后经过一系列逻辑处理后将request 与response传递给servlet容器,然后容器选择对应的servlet进行处理request 与 response,这个时候其实就传递到了springMVC中的DispatchServlet中去了.
接下来继续 debug:

image.png
image.png

继续,跳入了doGet方法中,

image.png

deGet/doPost都没有直接对请求进行处理,都是在processRequest方法中对请求进行处理的:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        /**
        * 为了保证当前线程的LocaleContext属性,RequestAttributes属性可以再当前请求完成后还能恢复,
        */
        //提取当前线程LocaleContext属性
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        //根据当前request创建对应的localeContext
        LocaleContext localeContext = buildLocaleContext(request);
        //提取当前线程对应的RequestAttributes属性
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        //根据当前request创建对应的RequestAttributes
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        //将上述的根据request创建后的两个属性绑定到当前线程
        initContextHolders(request, localeContext, requestAttributes);

        try {
            //准备工作做完后,具体的处理逻辑委托给了子类dispatchServlet中的doService方法进行处理
            doService(request, response);
        }
        catch (ServletException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }

        finally {
            //请求处理结束后恢复线程到原始状态
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }

            if (logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", failureCause);
                }
                else {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        logger.debug("Leaving response open for concurrent processing");
                    }
                    else {
                        this.logger.debug("Successfully completed request");
                    }
                }
            }
            //请求处理结束后无论成功与失败都发布事件通知
            publishRequestHandledEvent(request, startTime, failureCause);
        }
    }

看了上面这段带有注释的代码,相信对processRequest的处理逻辑应该是比较清楚了,这里额外讲一点东西(仅仅是个人所了解的一些知识,可能不完全对):

 针对每个request请求,服务器都会分配一个线程进行处理,线程也不是无限的,频繁的创建销毁线程,
 进行线程上下文切换是非常消耗资源的,所以针对这些请求进行线程分配,一般来说都是通过线程池完成的,
 所以, 在请求处理完成后,是需要恢复线程到原始状态的,删除掉前一个request请求遗留的信息

接着debug进入doService方法中:

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isDebugEnabled()) {
            String requestUri = urlPathHelper.getRequestUri(request);
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                    " processing " + request.getMethod() + " request for [" + requestUri + "]");
        }

        // Keep a snapshot of the request attributes in case of an include,
        // to be able to restore the original attributes after the include.
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            logger.debug("Taking snapshot of request attributes before include");
            attributesSnapshot = new HashMap<String, Object>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                    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());

        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

        try {
            doDispatch(request, response);
        }
        finally {
            if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                return;
            }
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }

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 {
                //如果是MultipartContent类型的request则转换成MultipartHTTPServletRequest类型的Request
                processedRequest = checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                // Determine handler for the current request.
                //根据request信息寻找对应的handler
                mappedHandler = getHandler(processedRequest, false);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    //如果没找到对应的handler则通过response返回错误信息
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                //根据当前的handler寻找对应的handlerAdapter
                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 {
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                }
                finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }

                applyDefaultViewName(request, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            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);
            }
        }
    }

继续跳入getHandler方法中,getHandler会通过request的信息从handlerMappings中提取对应的handler,其实就是提取了当前实例中的Controller的相关的信息,debug可以看到相关的信息:

,
{{[/index],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}
=
public java.lang.String com.axa.idle.controller.IdleController.heartBeatCode() throws java.lang.Exception}

可以看到 /index 与 controller中的方法IdleController.heartBeatCode() 有了一个映射关系,继续debug,hm.getHandler这个方法中,这里就不花篇幅详细解读了,这一块的逻辑处理挺长,但是都还挺简单,容易理解,且源码的注释也是写的比较详细,这里简单介绍下这一过程,

通过Request中的url等信息去匹配对应的controller,这里分一个直接匹配和通配符匹配的处理方式,
匹配完成后,将handler封装成HandlerExecutionChain执行链,然后往执行链中加入拦截器,
以保证拦截器可以作用到目标对象中.

看到这个返回的handler的信息:

image.png

接着debug:

image.png

看名字就知道是个适配器设计模式,看下具体的逻辑,简单易懂,遍历所有的handlerAdapters,选择适配的适配器:

image.png
image.png

接着debug,处理last-modified 请求头缓存,客户端第一次访问url时会添加一个last-modified 的响应头,客户端第二次访问url时,客户端会向服务器发送请求头"if-modified-since",询问服务器该时间之后当前请求的内容是否修改过,如果无变化,则自动返回 304 状态码(只要响应头,内容为空,节省服务器网络带宽).

image.png

继续debug,拦截器拦截请求前置处理:

image.png

接着debug,处理逻辑:

image.png

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
debug step into 方法可以看到,跳入的是具体的哪个实现类(AbstractHandlerMethodAdapter):

image.png

看下该方法收到的具体的参数信息:


image.png
protected final ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            // Always prevent caching in case of session attribute management.
            //
            checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
        }
        else {
            // Uses configured default cacheSeconds setting.
            checkAndPrepare(request, response, true);
        }

        // Execute invokeHandlerMethod in synchronized block if required.
        //需要在session内的同步执行
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    return invokeHandleMethod(request, response, handlerMethod);
                }
            }
        }
        //调用用户逻辑
        return invokeHandleMethod(request, response, handlerMethod);
    }

上面这段代码重点在最后一句invokeHandleMethod(request, response, handlerMethod),这里就是执行具体的controller中的方法的逻辑了,该方法返回的是一个ModelAndView,这里的具体实现是通过将request解析以及提供的参数组合成controller中映射的方法所需要的参数,利用反射的方式调用该方法逻辑,计算执行结果,将返回结果再封装到ModelAndView中,如下图可以看到调用invokeHandleMethod方法会跳入controller中/index 映射的方法中去执行逻辑

image.png
image.png

返回结果封装到ModelAndView中,由于heartBeatCode方法并没有将任何执行结果放入model中,所以可以看到mv中view为index,model is {}:

image.png

接着debug:

image.png

applyDefaultViewName方法则是当mv中没有view的值时,采用之前初始化时这个方法中提供的信息:

/**
* 初始化RequestToViewNameTranslator,当controller处理方法没有返回一个view或者逻辑视图名称时,并且
* 没有在该方法中直接往response的输出流中写数据时,就会通过RequestToViewNameTranslator接口的实现类
* 来提供一个约定好的逻辑视图名称供使用,spring中提供了一个默认的实现类
*/
initRequestToViewNameTranslator(context);

这个时候mv已经封装好了,那么就是要做渲染视图的事情了:

image.png
image.png

这段代码逻辑篇幅有点长,这里就总结下resolveViewName实现了什么逻辑:
采用之前初始化时的ViewResolvers对视图进行解析:

/**
        * 初始化ViewResolvers,当controller将请求处理结果放入到modelandview中后,dispatchservlet会根据
        * modelandview选择合适的视图进行渲染,springMVC通过ViewResolver接口定义的resolverViewName方法
        * 根据合适的viewname创建对应的view.
        * 配置如下:
        * <bean
        * class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        * <property name="prefix" value="/WEB-INF/views/" />
        * <property name="suffix" value=".jsp" />
        * </bean>
        */
        initViewResolvers(context);
image.png

然后解析视图名时看当前的这个viewName是否在缓存中,在则直接从缓存中提取,提高效率,不在则直接创建该视图,并且提供了对 redirect:xx 和 forward:xx 前缀的支持,最后向view中添加前缀以及后缀,并向view中添加了必要的属性设置,view渲染完成后,接着是页面跳转了,

image.png
image.png

在renderMergedOutputModel方法中,主要就是完成了将model中的信息放入到Request中,这样我们就可以在页面中使用JSTL语法或者Request信息直接获取的方式渲染页面,这样到达了我们通常在使用jsp页面时采用JSTL的语法的方式获取后台返回过来的值渲染到页面上.这一步最主要的就是通过将model中的值放入到Request中,这样我们就可以在别的地方调用到这些值.看下页面结果:

image.png

       到此为止,我们就完成了整个springMVC处理Request请求响应的过程,整个过程中略过了一些东西,像异常视图处理,url错误处理等等.

总结

总结一下整个springMVC的处理流程:

  ContextLoaderListener初始化WebApplicationContext ROOT,接着初始化servlet,
  初始化WebApplicationContext以及一些web应用中必须用到的属性,初始化servlet完成后,
  整个web应用算是启动成功了,接着开始处理请求,所有的请求都是通过dispatchservlet进行处理的,
  通过解析Request信息从handlermappings中找到对应handler(通常来说就是controller),封装成一个
  包含拦截器的执行器链HandlerExecutionChain,然后找到对应的handlerAdapter适配器通过反射的方式
  调用controller中的方法进行逻辑处理,返回的结果封装成ModelAndView,然后通过viewReslover对view
  进行试图渲染,将model的值注入到Request中,最后返回response响应.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容