Spring MVC 启动源码解析,看这篇就对了!

点击上方 "程序员小乐"关注公众号, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约

每日英文

Don't judge people by their outlook for you don't know what stories behind their eyes.

不要以貌取人,因为你不知道他们的双眼后面藏着什么故事。

每日掏心话

在逆境中看到希望,在磨难中感悟快乐,在平凡中发现快乐,在曲折中找寻快乐,在艰辛中品味快乐。


来自:超人小冰 | 责编:乐乐

链接:cnblogs.com/xiaobingblog/p/11743163.html

640?wx_fmt=jpeg

程序员小乐(ID:study_tech)第 670 次推文   图片来自网络

往日回顾:

   01 前言   

上篇文章介绍了

   02 初始化过程   

上文讲过一个Web项目的启动在加载listener、fliter初始化后,再进行servlet初始化。那SpringMvc如何与Servlet联系起来?看web.xml配置文件,有一个专门配置SpringMvc的servlet,就是DispatcherServlet。看下DispatcherServlet类继承关系

640?wx_fmt=png

 

如上图,DispatcherServlet本质上是一个Servlet。DispatcherServlet类的设计很巧妙,上层父类不同程度的实现了相关接口的部分方法,并留出了相关方法用于子类覆盖,将不变的部分统一实现,将变化的部分预留方法用于子类实现。对Servlet有一定了解的,Servlet初始化会首先调用init()方法。子类最后重写init()的是HttpServletBean,所以最开始对HttpServletBean的init()方法进行分析

640?wx_fmt=png

 

PropertyValues主要解析web.xml定义中<servlet>元素的子元素<init-param>中的参数值。见上图,有一个键值对就是SpringMvc的配置文件。bw.setPropertyValues(pvs, true) 将上一步解析的servlet初始化参数值绑定到DispatcherServlet对应的字段上;

接着就是执行initServletBean方法,因为HttpServletBean中的initServletBean就是个空方法,通过观察上述类图,发现子类FrameworkServlet重写了其initServletBean。于是对FrameworkServle的initServletBean进行分析

@Override	
protected final void initServletBean() throws ServletException {	
   getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");	
   if (this.logger.isInfoEnabled()) {	
      this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");	
   }	
   long startTime = System.currentTimeMillis();	

	
   try {	
      this.webApplicationContext = initWebApplicationContext();	
      initFrameworkServlet();	
   }	
   catch (ServletException ex) {	
      this.logger.error("Context initialization failed", ex);	
      throw ex;	
   }	
   catch (RuntimeException ex) {	
      this.logger.error("Context initialization failed", ex);	
      throw ex;	
   }	

	
   if (this.logger.isInfoEnabled()) {	
      long elapsedTime = System.currentTimeMillis() - startTime;	
      this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +	
            elapsedTime + " ms");	
   }	
}

该方法中比较重要的就是initWebApplicationContext()方法的调用,该方法仍由FrameworkServlet抽象类实现,继续查看其源码如下所示:

protected WebApplicationContext initWebApplicationContext() {	
   /*	
        获取由ContextLoaderListener创建的根IoC容器	
        获取根IoC容器有两种方法,还可通过key直接获取	
        */	
 WebApplicationContext rootContext =	
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());	
   WebApplicationContext wac = null;	

	
   if (this.webApplicationContext != null) {	
      // A context instance was injected at construction time -> use it	
      wac = this.webApplicationContext;	
      if (wac instanceof ConfigurableWebApplicationContext) {	
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;	
         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 -> set	
               // the root application context (if any; may be null) as the parent	
/*如果当前Servelt存在一个WebApplicationContext即子IoC容器并且上文获取的根IoC容器存在,则将根IoC容器作为子IoC容器的父容器                 */	
               cwac.setParent(rootContext);	
            }	
//配置并刷新当前的子IoC容器,功能与前文讲解根IoC容器时的配置刷新一致,用于构建相关Bean	
            configureAndRefreshWebApplicationContext(cwac);	
         }	
      }	
   }	
   if (wac == null) {	
      // No context instance was injected at construction time -> see if one	
      // has been registered in the servlet context. If one exists, it is assumed	
      // that the parent context (if any) has already been set and that the	
      // user has performed any initialization such as setting the context id	
//如果当前Servlet不存在一个子IoC容器则去查找一下	
      wac = findWebApplicationContext();	
   }	
   if (wac == null) {	
      // No context instance is defined for this servlet -> create a local one	
//如果仍旧没有查找到子IoC容器则创建一个子IoC容器	
      wac = createWebApplicationContext(rootContext);	
   }	

	
   if (!this.refreshEventReceived) {	
      // Either the context is not a ConfigurableApplicationContext with refresh	
      // support or the context injected at construction time had already been	
      // refreshed -> trigger initial onRefresh manually here.	
  //调用子类覆盖的onRefresh方法完成“可变”的初始化过程	
      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;	
}

该方法的主要作用同样是创建一个WebApplicationContext对象,即Ioc容器,上文我们已经创建过一个根Ioc容器,即Spring容器。Web第一次启动时,通过Debug,会执行wac = createWebApplicationContext(rootContext);将根IOC容器作为参数,调用createWebApplicationContex创建一个子IOC容器

这里简单提一下父子IOC容器,父子容器类似于类的继承关系,子类可以访问父类中的成员变量,而父类不可访问子类的成员变量,同样的,子容器可以访问父容器中定义的Bean,但父容器无法访问子容器定义的Bean。在一个SpringMvc项目中,父容器通常就是我们所说的Spring容器,它是加载Spring.xml配置文件,来管理Spring.xml中的Bean,这些Bean是全局共享的,即在任何当前容器或子容器中都能使用,我们一般配置Service,dao等bean。Service类中可以调用其他Service,dao。子容器通常是我们所说的SpringMvc容器,它所配置的Bean只能被当前子容器使用,但可以使用父容器的Bean。我们一般在子容器配置Controller、Interceptor等重要组件。这也就说明了我们为什么可以在Controller中使用service或dao,而反过来不行

  640?wx_fmt=png

 

接下来继续看createWebApplicationContex源码:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {	
   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");	
   }	
   ConfigurableWebApplicationContext wac =	
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);	

	
   wac.setEnvironment(getEnvironment());	
   wac.setParent(parent);	
   wac.setConfigLocation(getContextConfigLocation());	

	
   configureAndRefreshWebApplicationContext(wac);	

	
   return wac;	
}

该方法用于创建一个子IoC容器并将根IoC容器做为其父容器,接着进行配置和刷新操作用于构造相关的Bean。至此,根IoC容器以及相关Servlet的子IoC容器已经配置完成。子IOC容器配置完成后,调用onRefresh(wac)方法,通过类图可知,onRefresh具体实现是由DispatcherServlet类实现

 
                                                     

  摘抄一段评论:onRefresh()方法直接调用了initStrategies()方法,源码如上,通过函数名可以判断,该方法用于初始化创建multipartResovle来支持图片等文件的上传、本地化解析器、主题解析器、HandlerMapping处理器映射器、HandlerAdapter处理器适配器、异常解析器、视图解析器、flashMap管理器等,这些组件都是SpringMVC开发中的重要组件,相关组件的初始化创建过程均在此完成。


   03 总结   

在Debug源码中,涉及到了很多设计模式,想起校招面试时面试官问我,你知道Spring源码中有哪些设计模式吗,哈哈哈,一脸懵逼,不过现在也是。看来以后得好好学习设计模式了。

至此,对Tomcat启动一个Spring项目已有了大概认知,还是很开心。小白进阶之路任重而道远。

640?wx_fmt=png

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

欢迎各位读者加入程序员小乐

猜你还想看

640?wx_fmt=png

嘿,你在看吗640?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值