博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringMVC源码解析
阅读量:6481 次
发布时间:2019-06-23

本文共 10967 字,大约阅读时间需要 36 分钟。

hot3.png

SpringMVC 源码解析

作为一个javaer,Spring、SpringMVC目前是一项必不可少的技能了。SpringMVC在struts2之后异军突起,现如今已经完全超过了struts2,成为了javaweb开发的主流框架。今天我们基于Spring5.0对SpringMVC容器初始化过程、DispatcherServlet初始话过程、DispatcherServlet处理请求流程三个方面进行分析。

WebApplicationContext

WebApplicationContext结构

WebApplicationContext继承关系

我们可以看到WebApplicationContext继承自ApplicationContext,也就是我们web开发中的IOC容器,我们一般用到的是它的实现类 XmlWebApplicationContext。

WebApplicationContext初始化过程

开发web的时候,我们都需要在web.xml中配置Spring的监听器、Spring配置文件

contextConfigLocation
classpath:spring-context.xml
org.springframework.web.context.ContextLoaderListener

ContextLoaderListener

我们从ContextLoaderListener入手,讲解WebApplicationContext的创建流程。

ContextLoaderListener继承了ContextLoader,同时实现了ServletContextListener接口,通过实现contextInitialized来初始化IOC容器,而主要的方法都在ContextLoader里面实现。

@Override    public void contextInitialized(ServletContextEvent event) {        initWebApplicationContext(event.getServletContext());    }

我们来看看initWebApplicationContext方法的具体实现:

//1.先判断ServletContext中是否已经存在WebApplicationContext,比如我们不小心多配置了一个ContextLoaderListener的时候,就会导致重复的初始化    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 {        if (this.context == null) {            //2.创建WebApplicationContext对象            this.context = createWebApplicationContext(servletContext);        }        if (this.context instanceof ConfigurableWebApplicationContext) {            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;            if (!cwac.isActive()) {                if (cwac.getParent() == null) {                    ApplicationContext parent = loadParentContext(servletContext);                    cwac.setParent(parent);                }                configureAndRefreshWebApplicationContext(cwac, servletContext);            }        }        //3.把创建好的IOC容器放到Serverlet容器中        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) {            currentContextPerThread.put(ccl, this.context);        }        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;    }

我们这里就不展示过多的源码细节。 WebApplicationContext初始化图 图片地址:

从上面的图我们可以知道,WebApplicationContext的初始化,确切的说是XmlWebApplicationContext对象的创建过程主要包括以下几个步骤:

  1. 调用determineContextClass()方法获取具体的实现类。首先会获取用户配置的WebApplicationContext,如果没有,会使用默认的配置,具体的配置在spring-context模块下的ContextLoader.properties,里面配置的默认使用XmlWebApplicationContext;
    org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
    如果我们需要使用自己的自定义容器,我们需要在web.xml中配置
    contextClass
    org.springframework.web.context.support.XmlWebApplicationContext
  2. 调用ContextLoader的initWebApplicationContext()方法,通过反射创建具体的WebApplicationContext对象。
  3. 设置WebApplication的id,configLocation(也就是我们在web.xml中配置的contextConfigLocation)。
  4. 设置WebApplication的环境变量,设置web的环境参数servletContextInitParams 值为当前的ServletContext 5.customizeContext(),我们需要在容器初始化之前进行一些自己的操作,可以实现ApplicationContextInitializer接口对我们的容器进行一些修改或者操作,只需要在web.xml中配置contextInitializerClasses。 6.调用refresh()方法刷新容器,IOC容器加载配置,解析BeanDefintion初始化bean,初始化配置,运行Bean的后置处理器等等一系列操作都在这个方法里完成。
    [@Override](https://my.oschina.net/u/1162528)    public void refresh() throws BeansException, IllegalStateException {        synchronized (this.startupShutdownMonitor) {            //1.准备刷新容器            prepareRefresh();        //2.获取beanFactory,这一步会调用XmlWebApplication的loadBeanDefinitions()方法,从我们的配置文件加载所有的bean定义        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();        //3.准备刷新beanFactory        prepareBeanFactory(beanFactory);        try {            //4.设置beanFactory的后置处理器            postProcessBeanFactory(beanFactory);            //5.运行beanFactory的后置处理器            invokeBeanFactoryPostProcessors(beanFactory);            //6.注册bean的后置处理器,这一步主要是获取4 5两步中设置的BeanPostProcessor进行分类、排序等处理,以确定BeanPostProcessor的调用顺序            registerBeanPostProcessors(beanFactory);            //7.初始化MessageSource,用来处理国际化             initMessageSource();            //8.注册事件广播器            initApplicationEventMulticaster();            //9.再次初始化其它的bean            onRefresh();            //10.注册监听器            registerListeners();            //11.完成对BeanFactory的初始化,初始化所有非non-lazy-init的bean,这一步会创建bean实例、运行bean的后置处理器、加载我们配置的properties文件等            finishBeanFactoryInitialization(beanFactory);            //12.完成刷新,这里会发布一个ContextRefreshedEvent的事件            finishRefresh();        }        catch (BeansException ex) {            destroyBeans();            cancelRefresh(ex);            throw ex;        }        finally {            resetCommonCaches();        }    }}

到这里,我们WebAp初始化就已经完成了,但是我们不免有个疑问,我们的Controller,HandlerInterceptor这些没有在我们设置的contextConfigLocation里面,那他们又是怎么初始化的呢,下面我们继续对DispatcherServlet的初始化过程进行分析。

DispatcherServlet初始化

web.xml配置

mvc-dispatcher
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-mvc.xml
1
mvc-dispatcher
/

这里我们又配置了一个contextConfigLocation,这个就是SpringMVC容器的配置文件,我们一开始也配置了一个contextConfigLocation,他们之间没有任何的区别,我们第一个配置的contextConfigLocation是作为一个父容器,现在配置的是作为一个子容器,我们只能有一个父容器,但是可以配置多个子容器,也就是我们可以在一个项目中配置多个DispatcherServlet。

DispatcherServlet继承关系

DispatcherServlet初始化过程

  • DispatcherServlet本质还是一个Servlet,Servlet的初始化要求我们实现init()方法,DispatcherServlet的init()方法是在HttpServletBean中实现的。
[@Override](https://my.oschina.net/u/1162528)    public final void init() throws ServletException {        if (logger.isDebugEnabled()) {            logger.debug("Initializing servlet '" + getServletName() + "'");        }        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);        if (!pvs.isEmpty()) {            try {                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) {                throw ex;            }        }        //最主要的操作都在这里面了        initServletBean();    }

SpringMCV子容器初始化过程

从上面的图我们可以知道,SpringMVC子容器的初始是在DispatcherServlet初始化的时候完成的,主要包括下面几个方面:

  1. 执行init()方法。
  2. 执行initServletBean(),这个方法具体实现在FrameworkServlet里面。
  3. 执行initWebApplicationContext(),这个方法和我们前面讲过的初始化父IOC容器的流程基本一致,我们可以对比着看看,一些比较大的区别就是现在创建的子容器会一个parent contetx,也就是我们之前创建的WepApplicationContext,我们怎么获取到这个parent context呢,就是从ServletContext中获取,我们之前创建好父容器之后把它放到了ServletContext中。 ServletContext#getAttribute()
  4. 设置自容器的环境变量,设置web的环境参数servletContextInitParams 值为当前的ServletContext,设置namespace,用来区别不同的子容器,添加一个监听器,监听我们在父容器发布的事件,这里监听刷新容器,调用了refresh()方法。
  5. applyInitializers(),我们需要在容器初始化之前进行一些自己的操作,可以实现ApplicationContextInitializer接口对我们的容器进行一些修改或者操作,只需要在web.xml中配置contextInitializerClasses。
  6. 调用refresh()方法刷新容器,IOC容器加载配置,解析BeanDefintion初始化bean,初始化配置,运行Bean的后置处理器等等一系列操作都在这个方法里完成。关于refresh()方法,前面已经具体分析过了,我们就不在分析了。
  7. onfresh(),上面几部已经把我们的子容器初始化完成了,继续调用DispatcherServlet的onfresh()方法,验证和初始化HandleMapping、HandlerAdapter、ViewResolvers、ExceptionHandler等一系列前端控制器策略。
@Override    protected void onRefresh(ApplicationContext context) {        initStrategies(context);    }    protected void initStrategies(ApplicationContext context) {        initMultipartResolver(context);        initLocaleResolver(context);        initThemeResolver(context);        initHandlerMappings(context);        initHandlerAdapters(context);        initHandlerExceptionResolvers(context);        initRequestToViewNameTranslator(context);        initViewResolvers(context);        initFlashMapManager(context);    }

至此,我们已经分析了在web项目中父容器的初始化流程和自容器的初始化流程,下面的内容我们来分析DispatcherServlet处理请求的流程。

DispatcherServlet处理请求

我们先回忆下在没有MVC框架的时候,我们是怎么处理请求的。在我们还在通过继承Servlet接口的远古时代,我们的请求到达Servelt的时候,会调用service()方法,我们通过重写doPost()、doGet()等方法,自己操作Request、Response处理请求,当然我们需要在web.xml中配置一大堆的servelt。 DispatcherServelt请求处理时序图 图片地址:

从上面的图中我们可以看到DispatcherServlet处理请求主要包括以下几个方面:

  • 用户发起请求,DispatcherServlet拦截到请求。
    1. 调用父类FrameworkServlet的sercice()方法。
    2. FrameworkServlet重写了HttpServlet的service()方法doGet()、doPost()等doXX()方法,通过不同的请求类型,调用不同的doXX()方法处理请求。
    3. 而重写的这些doXX()方法都是调用了FrameworkServlet的processRequest()方法进行处理。
  • processRequest()处理请求。
    1. 调用buildRequestAttributes()重新构建请求参数。
    2. initContextHolders()把当前请求参数设置到RequestContextHolder()中,通过ThreadLocal让请求与当前线程绑定。
    3. 调用doService()方法处理请求。
    4. 最后发布一个ServletRequestHandledEvent事件。
  • doService()方法。doService()方法是由DispatcherServlet实现的,首先会把当前子容器对象、LocaleResolver、ThemeResolver等设置到当前请求中,然后调用doDispatch()方法
  • doDispatch()方法。这个方法就是DispatcherServlet处理请求的主要方法。
    1. 首先获取到HandlerExecutionChain,HandlerExecutionChain结构很简单,包括一个我们具体处理请求的Controller层方法的对象handler,以及拦截器interceptors、interceptorList。如果没有获取到相应的HandlerExecutionChain,则会返回404。
    2. 然后找到合适的HandlerAdapter。顺序调用拦截器的preHandle()方法,如果不符合拦截器的拦截规则则会同时调用afterCompletion()方法。
    3. 调用HandlerAdapter的handle()方法,返回一个ModelAndView对象。handle()方法会通过反射调用我们具体的Controller层方法,完成业务逻辑处理。
    4. 调用拦截器的postHandle()。
    5. 调用processDispatchResult()对请求结果进行处理。如果业务处理过程中有异常抛出,调用processHandlerException()对异常进行处理,这里会调用我们自定义的HandlerExceptionResolvers进行处理,然后返回一个异常处理结果的ModelAndView对象。
    6. DisPatcherServlet的render()方法。调用resolveViewName()获取到具体的View对象,比如RedirectView进行重定向、其它扩展的如FastJsonJsonView、MappingJackson2JsonView进行json处理、MappingJackson2XmlView进行xml处理等。
    7. View的render()方法。根据不同的View对象实例,对返回结果做不同的对象,重定向、返回json、返回xml、返回页面等。
    8. 调用拦截器afterCompletion()方法。

至此,我们对SpringMVC WebApplicationContext初始化、DispatcherServlet初始化、DispatcherServlet请求处理流程进行了大概的分析。总体来说,分析的还是比较粗糙,很多细节的方面没有深入分析,建议大家多去看看源码内容,会有不一样的理解和感受。文章中可能有表达不准确、理解不正确的地方,欢迎随时指正,一起探讨。

``

转载于:https://my.oschina.net/lengchuan/blog/1844738

你可能感兴趣的文章
APNS 生成证书 p12 或者 PEM
查看>>
PCA误差
查看>>
烦人的数据不一致问题到底怎么解决?——通过“共识”达成数据一致性
查看>>
WPFの获取任意元素的位置
查看>>
WPF的TextBox产生内存泄露的情况
查看>>
RAMPS1.4 3d打印控制板接线与测试1
查看>>
PS 多次剪裁同一图片
查看>>
MusicXML 3.0 (2) - 调号
查看>>
收集的QCon 北京(Beijing) 2010 PPT 及总结
查看>>
Qt 让QLabel自适应text的大小,并且自动换行(转)
查看>>
PostgreSQL学习手册(十六) SQL语言函数
查看>>
网络编程——第一篇 基础之进程线程
查看>>
9.png 技巧
查看>>
hdu 4715(打表)
查看>>
java J2EE学习入门
查看>>
Linux系统信息查看命令大全
查看>>
为什么项目的jar包会和tomcat的jar包冲突?
查看>>
这些.NET开源项目你知道吗?.NET平台开源文档与报表处理组件集合(三)
查看>>
linux ps top 命令 VSZ,RSS,TTY,STAT, VIRT,RES,SHR,DATA的含义【转】
查看>>
程序员接私活记(转)
查看>>