解读springMVC启动以及请求处理过程

Java程序员几乎都逃不过没日没夜地写rest api,改rest api,最后跑路[旺柴]的宿命。Java web发展到如今,那必然少不了一个得心应手的框架,好的框架使开发如鱼得水,事半功倍,妙啊[坏笑]。漏洞百出的Strurt曾经风靡一时的日子一去不复返,取而代之的是小巧轻便的spring MVC占据了大壁江山,期待下一次的技术变革[机智],让新框架变得可以自动生成业务代码,成熟的框架要会自己动[旺柴]。作为如今哪里需要哪里搬的码农,一起来搞懂spring MVC启动加载、工作流程机制吧。giegie我们一起学习啊,天呐,giegie跟我一起学习,你女朋友知道了,不会生气吧,你女朋友好可怕,不像我,我只想跟giegie学习。

将一个web应用程序部署到web容器中时,在web应用程序开始处理客户端请求之前,必须按此顺序执行以下步骤。

一、实例化部署描述符中由<listener>元素标识的每个事件监听器的实例。对于实现ServletContextListener的实例化监听器实例,会调用contextInitialized()方法。

二、实例化部署描述符中由<filter>元素标识的每个过滤器的实例,并调用每个过滤器实例的init()方法。

三、实例化由<servlet>元素标识的每个servlet的实例,该元素按照load on startup元素值定义的顺序包含<load on startup>元素,并调用每个servlet实例的init()方法。

从Tomcat源码中可看出,监听器启动成功,则启动过滤器,过滤器启动成功,则启动servlet,若某一个环节出错,则不进行后续的加载。

    @Override
    protected synchronized void startInternal() throws LifecycleException {
            // Configure and call application event listeners
            if (ok) {
                if (!listenerStart()) {
                    log.error(sm.getString("standardContext.listenerFail"));
                    ok = false;
                }
            }
            // Configure and call application filters
            if (ok) {
                if (!filterStart()) {
                    log.error(sm.getString("standardContext.filterFail"));
                    ok = false;
                }
            }

            // Load and initialize all "load on startup" servlets
            if (ok) {
                if (!loadOnStartup(findChildren())){
                    log.error(sm.getString("standardContext.servletFail"));
                    ok = false;
                }
            }
    }

一、通过给定的servlet context初始化spring web应用上下文。

搭建一个springMVC项目的时候,会在web.xml里指定listener-class,通常使用的是org.springframework.web.context.ContextLoaderListener,该监听器可以说是web容器跟web项目的切入点,或者说连接点,当web容器启动过程中会实例化该监听器,启动好之后会改调用监听器的contextInitialized方法来初始化root web应用上下文。经常使用的Tomcat容器会在org.apache.catalina.core.StandardContext的listenerStart方法中发布事件org.apache.catalina.core.ApplicationContextFacade,就会通知到ContextLoaderListener监听器执行web应用上下文的初始化。说白的就是Tomcat启动好了,接下来就会把web应用部署到Tomcat中,这个过程中Tomcat会加载ContextLoaderListener实例对象,后面在org.apache.catalina.core.StandardContext的listenerStart方法执行的时候会调用ContextLoaderListener实例对象的contextInitialized方法来加载web应用上下文。

package org.apache.catalina.core;

public class StandardContext extends ContainerBase
        implements Context, NotificationEmitter {
    public boolean listenerStart() {
        // 省略
        for (Object instance : instances) {
            if (!(instance instanceof ServletContextListener)) {
                continue;
            }
            ServletContextListener listener = (ServletContextListener) instance;
            try {
                fireContainerEvent("beforeContextInitialized", listener);
                if (noPluggabilityListeners.contains(listener)) {
                    listener.contextInitialized(tldEvent);
                } else {
                    listener.contextInitialized(event);
                }
                fireContainerEvent("afterContextInitialized", listener);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                fireContainerEvent("afterContextInitialized", listener);
                getLogger().error(sm.getString("standardContext.listenerStart",
                        instance.getClass().getName()), t);
                ok = false;
            }
        }
        return ok;

    }
}

org.springframework.web.context.ContextLoaderListener实现了servlet的中的接口,既实现了javax.servlet.ServletContextListener接口,该接口中定义了初始化和销毁web应用上下文的方法,同时ContextLoaderListener的各种实际的功能实现都是在父类org.springframework.web.context.ContextLoader中实现,并没有进行重写,当ContextLoaderListener接收的相应事件的时候直接调用父类中实现的方法,从而实现了web容器跟spring MVC通过ServletListener实现了耦合。

web容器调用ContextLoaderListener的contextInitialized方法,在该方法中直接调用了在ContextLoader父类中实现的initWebApplicationContext(ServletContext servletContext)方法来进行web应用上下文的初始化。整个过程包括创建一个ConfigurableWebApplicationContext类型的上下文对象content,接着对 该对象 进行configureAndRefreshWebApplicationContext 配置和刷新


package org.springframework.web.context;
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
}
package org.springframework.web.context;
public class ContextLoader {
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		//省略
    if (this.context == null) {
        this.context = createWebApplicationContext(servletContext);
    } 
    if (this.context instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
        //省略
		configureAndRefreshWebApplicationContext(cwac, servletContext);
	}       
    // 省略
    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
			return this.context;
    //省略
	}
}

1、配置

1.1、设置上下文的id

默认是org.springframework.web.context.WebApplicationContext:配置的访问路径

1.2、把servlet context作为设置到spring web上下文的属性并设置进去,为spring web上下文设置配置路径(/WEB-INF/applicationContext.xml)会在该配置文件中指定扫描路径,配置bean对象等等。等会加载web 应用上下文的时候会根据该文件中的配置去解析加载。其实就像ClassPathXmlApplicationContext中一样,只不过是直接指定配置文件位置的,而这里的话是在web.xml的init-param标签中设置的。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.qiaoHaoTing.*"/>

</beans>

1.3、进行一些自定义

1.4、加载环境配置

2、刷新

刷新动作是在抽象应用上下文AbstractApplicationContext中定义,没有对刷新动作进行重写的spring上下文都是直接使用AbstractApplicationContext中定义的。这里刷新就不过多进行描述,可以参考上一篇博文:https://blog.youkuaiyun.com/qq_34485626/article/details/116088267,有区别的地方可能就是会去扫描我们定义的controller接口实例(如果定义了的话,并且扫描路径指定到了controller包)。完成加载之后可以看到定义service、controller、bean后置处理器等对象都已被实例化。

二、实例化给定的过滤器并调用其init()方法

监听器加载完毕之后,若配有过滤器,则接下来会把配置好的过滤器依次实例化以及执行init方法,所以说过滤器并非必须要配置的。来看一下servlet包下的过滤器顶级接口Filter,定义了三个方法。

init方法是会被web容器调用,被调用后,过滤器才能在整个服务中正常使用。该方法只应被调用一次,并且在过滤器开始工作前;

doFilter该方法定义了过滤器实际的工作逻辑,每个请求或者响应来到链路中的时候,web容器就会调用该方法对本次的请求或者响应进行一些检查、包装工作;

destroy方法将在web容器要停止服务(关闭)时会被调用,并且会等待过滤器的doFilter方法中所有工作处理完成或者等待执行超时,该方法才会被执行,执行最终的销毁工作,能够释放掉所持有的资源,比如内存,文件句柄,线程资源等等,确保任何持久状态都与内存中过滤器的当前状态同步。

实现了该接口的类便可称为过滤器,负责对资源的请求或者响应执行过滤任务,每个过滤器对象中会持有一个FilterConfig对象,通过其FilterConfig对象,获取其初始化参数,以及前一步加载好的servletcontext。我们在web.xml中配置好的初始化参数init-param,web容器会去执行解析,放入FilterConfig对象,在初始化init的时候作为参数传过来。

package javax.servlet;
import java.io.IOException;

public interface Filter {
	
	public void init(FilterConfig filterConfig) throws ServletException;	
	
    public void doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException;

	public void destroy();
}

以Tomcat容器为例,如果监听器加载成功,则接下来就进行过滤器的加载。启动的时候会解析web.xml中的配置定义,放入上下文context名为filterDefs的map集合中,在初始化过滤器配置的时候遍历集合,一一进行实例化并执行init()方法。每个过滤器会对应一个把过滤器配置对象,让context持有应用过滤器配置对象,过滤器配置对象中维护着过滤器信息,在过滤器对象实例化的时候会实例化过滤器

     public boolean filterStart() {       
        // 省略
        synchronized (filterConfigs) {
            filterConfigs.clear();
            for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
                String name = entry.getKey();
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug(" Starting filter '" + name + "'");
                }
                try {
                    ApplicationFilterConfig filterConfig =
                            new ApplicationFilterConfig(this, entry.getValue());
                    filterConfigs.put(name, filterConfig);
                } catch (Throwable t) {
                   // 省略
                }
            }
        }
       
    }
    ApplicationFilterConfig(Context context, FilterDef filterDef)
            throws ClassCastException, ReflectiveOperationException, ServletException,
            NamingException, IllegalArgumentException, SecurityException {

        super();

        this.context = context;
        this.filterDef = filterDef;
        // Allocate a new filter instance if necessary
        if (filterDef.getFilter() == null) {
            getFilter();
        } else {
            this.filter = filterDef.getFilter();
            context.getInstanceManager().newInstance(filter);
            initFilter();
        }
    }

    Filter getFilter() throws ClassCastException, ReflectiveOperationException, ServletException,
            NamingException, IllegalArgumentException, SecurityException {

        // Return the existing filter instance, if any
        if (this.filter != null)
            return this.filter;

        // Identify the class loader we will be using
        String filterClass = filterDef.getFilterClass();
        this.filter = (Filter) context.getInstanceManager().newInstance(filterClass);

        initFilter();

        return this.filter;

    }

    private void initFilter() throws ServletException {
        if (context instanceof StandardContext &&
                context.getSwallowOutput()) {
            try {
                SystemLogHandler.startCapture();
                filter.init(this);
            } finally {
                String capturedlog = SystemLogHandler.stopCapture();
                if (capturedlog != null && capturedlog.length() > 0) {
                    getServletContext().log(capturedlog);
                }
            }
        } else {
            filter.init(this);
        }

        // Expose filter via JMX
        registerJMX();
    }

过滤器以org.springframework.web.filter.CharacterEncodingFilter为例,该类维护了两个成员变量,一个是编码encoding,一个是 是否强制编码forceEncoding;具体的doFilter方法在父类中实现,doFilter中调用的doFilterInternal由子类自己进行实现,CharacterEncodingFilter则重写了父类的doFilterInternal方法,在该doFilterInternal方法中,如果设置了编码encoding,同时如果强制进行编码/请求中没有设置编码,则将请求的编码设置为encoding;如果强制编码,则将响应的编码设置为encoding。初始化init方法也是在父类中实现,在GenericFilterBean中实现了init方法,但只负责通过包装器把初始化参数设置到过滤器对象属性中,接着调用了一个由其子类实现的方法initFilterBean,交给子类去做具体的初始化动作。

在web.xml中把CharacterEncodingFilter配置进去,encoding设置为UTF-8,过滤路径设置为过滤所有请求。

    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

通过下面的debug截图可以看出,init方法执行完毕之后web.xml中设置的初始化参数被赋值给CharacterEncodingFilter过滤器实例化对象filter,CharacterEncodingFilter并没有对initFilterBean方法进行重写,没有做进一步的初始化动作。

三、加载servlet

先来看一下DispatcherServlet类定义和结构(如下UML类图),DispatcherServlet被官方定义为HTTP请求处理程序/控制器的中央调度器,例如用于web UI控制器或基于HTTP的远程服务导出器。分派给已注册的处理程序以处理web请求,从而提供方便的映射和异常处理工具。

DispatcherServlet可以使用任何处理器映射器HandlerMapping实现(预构建或作为应用程序的一部分提供)来控制请求到处理程序对象的路由。spring MVC中默认的处理映射器实现有这两个:org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping和org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping。当然也可以自己定义处理映射器HandlerMapping,实现HandlerMapping接口,把自定义对象在servlet的应用程序上下文中定义为bean,覆盖默认HandlerMapping即可(如果存在)。HandlerMappings可以被赋予任何bean名称(它们是按类型测试的)。

DispatcherServlet可以使用任何处理适配器handleAdapter,允许使用任何处理程序接口。默认提供了org.springframework.web.servlet.mvc.httprequesthandleadapter和org.springframework.web.servlet.mvc.simplecontrollerhandleadapter两个适配器,分别对应于Spring的org.springframework.web.HttpRequestHandler和org.springframework.web.servlet.mvc.Controller接口。默认的org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter也将被注册。handleAdapter对象可以作为bean添加到应用程序上下文中,覆盖默认HandlerAdapters。与HandlerMappings一样,HandlerAdapter可以被赋予任何bean名称(它们是按类型测试的)。

调度器的异常解决策略可以通过HandlerExceptionResolver指定,例如将某些异常映射到错误页的示例。默认提供了

org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver、org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver以及 org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver三个异常解析器,HandlerExceptionResolver异常处理解析器也可以通过自动义重写到spring上下文中。HandlerExceptionResolver也可以被赋予任何bean名称(它们是按类型测试的)。

视图解析策略可以指定实现了ViewResolver的类对象,即解析符号视图将名称添加到视图对象中。默认的视图解析器有org.springframework.web.servlet.view.InternalResourceViewResolver。ViewResolver对象可以作为bean添加到应用程序上下文中,从而覆盖默认的ViewResolver。ViewResolver可以被赋予任何bean名称(它们是按类型测试的)。

web容器在监听器和过滤器加载完毕之后,会进行servlet的加载,即,实例化servlet对象和调用该对象的init方法。通常在web.xml配置中指定servlet-name名称,指定servlet-class具体实现类,该类一般为org.springframework.web.servlet.DispatcherServlet,指定init-param初始化参数以及请求路径映射等等。以Tomcat容器为例,Tomcat完成该动作是在类StandardWrapper的loadServlet方法中实现。通过newInstance和类名实例化servlet对象,接着调用servlet的init初始化方法来完成servlet的加载。实例化DispatcherServlet对象,然后调用带对象的init方法。

package org.apache.catalina.core;
public class StandardWrapper extends ContainerBase
    implements ServletConfig, Wrapper, NotificationEmitter {
    
    /**
     * Load and initialize an instance of this servlet, if there is not already
     * at least one initialized instance.  This can be used, for example, to
     * load servlets that are marked in the deployment descriptor to be loaded
     * at server startup time.
     * @return the loaded Servlet instance
     * @throws ServletException for a Servlet load error
     */
    public synchronized Servlet loadServlet() throws ServletException {
        //省略
        Servlet servlet;
        //省略
        InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
        servlet = (Servlet) instanceManager.newInstance(servletClass);
        //省略
        initServlet(servlet);
        //省略
        return servlet;
    }
    
    private synchronized void initServlet(Servlet servlet)
            throws ServletException {
        if (instanceInitialized && !singleThreadModel) return;
        if( Globals.IS_SECURITY_ENABLED) {
            // 省略
        } else {
            servlet.init(facade);
        }
        // 省略
    }
}

通过UML类图可知,DispatcherServlet的init()方法是在父类中实现的,顶层接口servlet定义了init(ServletConfig config)方法,抽象父类GenericServlet实现了该方法,在方法体里把容器传过来的config保留在了本地副本中,既赋值给了成语变量,然后再调用无参的init()方法,该无参的init()方法最终是在其子类HttpServletBean中具体实现。在HttpServletBean的init方法中,先把init param参数加载到bean 属性中,接下来的部分又交给了其子类FrameworkServlet的initServletBean()方法中来实现。在FrameworkServlet实现的initServletBean()中会初始化web应用上下文,注意,这里又进行了一次上下文的加载,前面加载的上下文作为这里的 parent。web应用上下文的加载主要是实例化的定义rest 接口的controller类对象、默认的映射处理器,处理适配器,异常解析器、一些注解类,如下图,完成单例对象实例化后集合中的成员对象。但是有木有发现这里没有实例化默认的视图解析器,莫慌莫慌,下面的策略初始化将会加载进来。

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {   
    @Override
	protected final void initServletBean() throws ServletException {
            // 省略		
            // 初始化web应用上下文
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		    // 省略
	}
}

在加载web应用上下文过程中,实例化bean对象的时候,通过包装器把对象bean实例化出来之后,如果该类实现InitializingBean接口,并实现了afterPropertiesSet方法,则会调用该对象bean的afterPropertiesSet方法。

    protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable {
        // 省略
        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            // 省略
            ((InitializingBean) bean).afterPropertiesSet();
        }
        // 省略
    }

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping则实现了InitializingBean接口,并在afterPropertiesSet方法中实现了 把controller类中的 rest接口 转化为RequestMappingInfo和HandlerMethod,然后存储在该对象bean的urlMap和handlerMethods两个Map集合中。一个rest接口方法对应着一个HandlerMethod和一个RequestMappingInfo;urlMap中以请求路径为key,以RequestMappingInfo作为value;handlerMethods以RequestMappingInfo作为key,以HandlerMethod作为value;

rest接口的每个方法定义对应着一个HandlerMethod对象,rest接口的RequestMapping注解对应着一个RequestMappingInfo对象,对应关系如下。

package org.springframework.web.method;
public class HandlerMethod {

        // rest接口类的类名
        private final Object bean;
        // dispatcher servlet对应的上下文
        private final BeanFactory beanFactory;
        // rest接口方法定义
        private final Method method;
        // 桥接方法,如果不存在则为方法本身method
        private final Method bridgedMethod;
        // rest接口方法参数
        private final MethodParameter[] parameters;
}

如下debug截图,在RequestMappingHandlerMapping对象的执行完afterPropertiesSet方法后,完成了对rest接口的解析。

@Controller
@RequestMapping(value = "/user")
public class UserController {

    @Autowired
    UserService userService;

    @RequestMapping(value = "/info", method = RequestMethod.GET)
    @ResponseBody
    public Response userInfo(@RequestParam Integer id) {
        return Response.success(userService.getUserInfo(id));
    }
}

应用上下文加载的最后广播器会把 上下文加载完成的事件 发布出去,在上面的加载过程中,会给广播器注入一个默认的ContextRefreshListener监听对象,该监听对象收到上下文加载完成事件后执行initStrategies的方法来初始化此DispatcherServlet使用的策略对象。

initMultipartResolver(context)——初始化文件上传解析器,如果配置了则初始化,否则不处理;

initLocaleResolver(context)——初始化国际化解析器,如果有定义配置则进行初始化,否则不处理;

initThemeResolver(context)——初始化主题解析器,如果有定义配置则进行初始化,否则不处理;

initHandlerMappings(context)——初始化处理映射器,在加载上面的web 应用上下文的时候已经做了初始化,这里做的是保存一份副本。

initHandlerAdapters(context)——初始化处理适配器,在加载上面的web 应用上下文的时候已经做了初始化,这里做的是保存一份副本。

initHandlerExceptionResolvers(context)——初始化异常解析器,在加载上面的web 应用上下文的时候已经做了初始化,这里做的是保存一份副本。

initRequestToViewNameTranslator(context)——初始化请求转视图转化器,如果有定义配置则进行初始化,否则不处理;

initViewResolvers(context)——初始化视图解析器,刚才进行web 应用上下文加载的时候并没有初始化该对象,这里会加载默认的org.springframework.web.servlet.view.InternalResourceView。

initFlashMapManager(context)——初始化FlashMapManager(重定向前后信息存储),如果有定义配置则进行初始化,否则不处理;

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

这里策略处理器都有着对应的实现类,需要使用的话,配上去即可,这里不做过多的描述。比如常用的文件上传,在dispatcher-servlet.xml作如下配置即可。

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<property name="maxUploadSize">
		<value>9242880</value>
	</property>
</bean>

DispatcherServlet初始化好之后会加载好如下图所示的默认属性

四、请求处理

4.1、执行过滤器

当一个请求过来的时候,如果设置了过滤器,则先执行过滤,web容器会调用过滤器的doFilter方法。

package org.apache.catalina.core;
public final class ApplicationFilterChain implements FilterChain {
    private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            // 省略    
            Filter filter = filterConfig.getFilter();
            filter.doFilter(request, response, this);
        }
        // We fell off the end of the chain -- call the servlet instance
        // 省略    
        servlet.service(request, response);
        // 省略    
    }
}

会逐一执行过滤链ApplicationFilterChain中的过滤器,不同的过滤器在自己的doFilterInternal方法中实现自己具体的过滤逻辑,过滤器CharacterEncodingFilter则把请求/响应中的编码设置成指定的编码。在所有的过滤器执行完毕,请求通过了过滤器的过滤逻辑(比如做了权限校验,提前返回),接下来则会调用servlet。

4.2、servlet接收到标准的http请求,根据请求方法(GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE)调用对应的处理函数。在每种请求处理函数中会进行一些特殊处理外,最终调用processRequest方法来处理请求,并在处理完之后,会将请求、耗时、异常信息作为一个事件发布出去。processRequest中请求处理调用doService方法去完成,在doService中进行一些副本快照保存以及请求属性设置后,后续逻辑在doDispatch中实现。转发,为什么

4.3、doDispatch,调度,根据请求的url/名称找到对应的接口,或者说接口对应的handlerMethod(应用启动的时候,会将接口转化为handlerMethod存放在web应用上下文中),接着找到合适的适配器HandlerAdapter并执行接口方法,返回ModelAndView,最后使用ViewResolver对ModelAndView进行解析,把逻辑视图解析为真正的视图View对象。注意,如果是纯接口,适配器执行返回的ModelAndView结果为null,就相当于没有视图,在适配器执行最后会将接口的执行结果写入到请求响应中。如今,web应用应该都是前后端分离的,后端只负责处理数据,不再进行视图渲染。

package org.springframework.web.servlet;
public class DispatcherServlet extends FrameworkServlet {
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);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				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()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] 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);
			}
		}
	}
}

a、为当前请求查找处理器handler。在servlet初始化的时候已经加载好了handlerMapping,当一个请求过来的时候会遍历加载好的handlerMapping,在handlerMapping中找到一个对应的HandlerMethod,主要根据请求的url去匹配handlerMapping的handlerMethod,匹配上的话,会new一个新的对应handlerMethod,最后并进一步封装成一个HandlerExecutionChain(会加入handlerMapping的拦截器)。

package org.springframework.web.servlet;
public class DispatcherServlet extends FrameworkServlet {
	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() + "'");
			}
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}
}

最终生成的HandlerExecutionChain会包含匹配到的handlerMethod以及interceptor。

b、为当前请求查找处理适配器handler Adapter。跟handlerMapping一样,handler Adapter在servlet初始化的时候已经加载好了。遍历适配器,找到提前返回,判定当前适配器是否适用于当前处理器,RequestMappingHandlerAdapter的判定就较为简单,处理器是否实现了HandlerMethod接口。

package org.springframework.web.servlet;
public class DispatcherServlet extends FrameworkServlet {
		protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			if (ha.supports(handler)) {
				return ha;
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}
}

c、执行拦截器的preHandle方法。HandlerInterceptor总共定义了preHandle、postHandle以及afterCompletion三个方法,每个方法有着不同的执行时机。preHandle在确定了处理器和处理器适配器之后执行,postHandle在适配器执行invoke后执行,afterCompletion在请求处理完成后回调,即渲染视图后回调,可以允许适当的进行清理回收资源。

d、执行实际的处理方法。将通过反射执行handlerMethod对应的rest接口的方法,然后将执行返回结果写入到web请求,最后返回ModelAndView。

e、执行拦截器的postHandle方法。

f、如果返回的ModelAndView不为空,则进行视图渲染。根据ModelAndView得到view,然后调用view的render方法进行渲染。第一步是准备请求,比如在JSP中,这意味着将模型对象设置为请求属性;第二步是视图的实际呈现,例如通过RequestDispatcher包含JSP。

g、执行拦截器的afterCompletion方法。

 

 

 

 

 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值