SpringMVC 源码解析
作为一个javaer,Spring、SpringMVC目前是一项必不可少的技能了。SpringMVC在struts2之后异军突起,现如今已经完全超过了struts2,成为了javaweb开发的主流框架。今天我们基于Spring5.0对SpringMVC容器初始化过程、DispatcherServlet初始话过程、DispatcherServlet处理请求流程三个方面进行分析。
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的初始化,确切的说是XmlWebApplicationContext对象的创建过程主要包括以下几个步骤:
- 调用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 - 调用ContextLoader的initWebApplicationContext()方法,通过反射创建具体的WebApplicationContext对象。
- 设置WebApplication的id,configLocation(也就是我们在web.xml中配置的contextConfigLocation)。
- 设置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本质还是一个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(); }
从上面的图我们可以知道,SpringMVC子容器的初始是在DispatcherServlet初始化的时候完成的,主要包括下面几个方面:
- 执行init()方法。
- 执行initServletBean(),这个方法具体实现在FrameworkServlet里面。
- 执行initWebApplicationContext(),这个方法和我们前面讲过的初始化父IOC容器的流程基本一致,我们可以对比着看看,一些比较大的区别就是现在创建的子容器会一个parent contetx,也就是我们之前创建的WepApplicationContext,我们怎么获取到这个parent context呢,就是从ServletContext中获取,我们之前创建好父容器之后把它放到了ServletContext中。
ServletContext#getAttribute()
- 设置自容器的环境变量,设置web的环境参数servletContextInitParams 值为当前的ServletContext,设置namespace,用来区别不同的子容器,添加一个监听器,监听我们在父容器发布的事件,这里监听刷新容器,调用了refresh()方法。
- applyInitializers(),我们需要在容器初始化之前进行一些自己的操作,可以实现ApplicationContextInitializer接口对我们的容器进行一些修改或者操作,只需要在web.xml中配置contextInitializerClasses。
- 调用refresh()方法刷新容器,IOC容器加载配置,解析BeanDefintion初始化bean,初始化配置,运行Bean的后置处理器等等一系列操作都在这个方法里完成。关于refresh()方法,前面已经具体分析过了,我们就不在分析了。
- 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。 图片地址:
从上面的图中我们可以看到DispatcherServlet处理请求主要包括以下几个方面:
- 用户发起请求,DispatcherServlet拦截到请求。
- 调用父类FrameworkServlet的sercice()方法。
- FrameworkServlet重写了HttpServlet的service()方法doGet()、doPost()等doXX()方法,通过不同的请求类型,调用不同的doXX()方法处理请求。
- 而重写的这些doXX()方法都是调用了FrameworkServlet的processRequest()方法进行处理。
- processRequest()处理请求。
- 调用buildRequestAttributes()重新构建请求参数。
- initContextHolders()把当前请求参数设置到RequestContextHolder()中,通过ThreadLocal让请求与当前线程绑定。
- 调用doService()方法处理请求。
- 最后发布一个ServletRequestHandledEvent事件。
- doService()方法。doService()方法是由DispatcherServlet实现的,首先会把当前子容器对象、LocaleResolver、ThemeResolver等设置到当前请求中,然后调用doDispatch()方法
- doDispatch()方法。这个方法就是DispatcherServlet处理请求的主要方法。
- 首先获取到HandlerExecutionChain,HandlerExecutionChain结构很简单,包括一个我们具体处理请求的Controller层方法的对象handler,以及拦截器interceptors、interceptorList。如果没有获取到相应的HandlerExecutionChain,则会返回404。
- 然后找到合适的HandlerAdapter。顺序调用拦截器的preHandle()方法,如果不符合拦截器的拦截规则则会同时调用afterCompletion()方法。
- 调用HandlerAdapter的handle()方法,返回一个ModelAndView对象。handle()方法会通过反射调用我们具体的Controller层方法,完成业务逻辑处理。
- 调用拦截器的postHandle()。
- 调用processDispatchResult()对请求结果进行处理。如果业务处理过程中有异常抛出,调用processHandlerException()对异常进行处理,这里会调用我们自定义的HandlerExceptionResolvers进行处理,然后返回一个异常处理结果的ModelAndView对象。
- DisPatcherServlet的render()方法。调用resolveViewName()获取到具体的View对象,比如RedirectView进行重定向、其它扩展的如FastJsonJsonView、MappingJackson2JsonView进行json处理、MappingJackson2XmlView进行xml处理等。
- View的render()方法。根据不同的View对象实例,对返回结果做不同的对象,重定向、返回json、返回xml、返回页面等。
- 调用拦截器afterCompletion()方法。
至此,我们对SpringMVC WebApplicationContext初始化、DispatcherServlet初始化、DispatcherServlet请求处理流程进行了大概的分析。总体来说,分析的还是比较粗糙,很多细节的方面没有深入分析,建议大家多去看看源码内容,会有不一样的理解和感受。文章中可能有表达不准确、理解不正确的地方,欢迎随时指正,一起探讨。
``