解析springboot自动配置springmvc的秘密之DispatcherServlet

我们知道springboot虽说简化了spring那套繁琐的xml文件配置,但是springboot的底层本质上还是spring和springmvc的那套东西。所以提升开发内功,不能仅仅只是停留在使用的层面,还需要深入了解springboot背后运作的底层原理。

所以,今天来谈谈springboot是如何自动配置springmvc的,当然重点是DispatcherServlet。

因为DispatcherServlet是springmvc的五大核心组件最重要的一个组件,用户请求首先会经过DispatcherServlet前端控制器,然后由它分发给其他组件进行处理:

 另外我们知道在springmvc开发中通过@Controller注解就可以简化servlet的开发,但是这里有一个重要的规则需要提前了解

Controller要能工作,需要两个条件:

  1.  至少有一个DispatcherServlet,注意:不能是空构造的DispatcherServlet,而且必须是跟WebAppContext绑定的
  2.  Controller和DispatcherServlet必须在同一个IOC容器 这两个条件必须同时满足, 否则即便controller注解被扫描到还是会报404错误

所以springmvc要能运行需要有一个DispatcherServlet的bean, 也就是前端控制器。

传统springmvc开发通常需要有一个web.xml文件对它进行配置,基本格式如下如下:

 <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <!-- 配置springmvc配置文件的位置 -->
      <!-- 注意: contextConfigLocation不是必须的, 如果不配置contextConfigLocation, 
                  springmvc的配置文件则默认为:WEB-INF/servlet的name+"-servlet.xml" -->
      <param-name>contextConfigLocation</param-name>  
    <!-- springmvc配置文件的路径 -->
      <param-value>classpath:springmvc-servlet.xml</param-value> 
    </init-param>
    <!--Servlet默认是在第1次访问才创建,但配置load-on-startup为1则会在tomcat启动时提前创建 -->
    <load-on-startup>1</load-on-startup>   <!--启动级别为1 -->
  </servlet>
  <!-- servlet映射声明 -->
  <servlet-mapping>   
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>  <!-- 定义url拦截规则,未匹配到则报404错误 -->
  </servlet-mapping>

springmvc通过编程方式实现xml零配置,有以下两种方式:

1.  实现WebApplicationInitializer接口(原理是spi机制),并重写其onStartup方法:

2.  继承类AbstractAnnotationConfigDispatcherServletInitializer,并重写其getRootConfigClasses方法, getServletConfigClasses方法。

那么springboot又是如何自动加载DispatcherServlet,然后又如何add到tomcat的ServletContext上下文的呢?

因为从本质上说DispatcherServlet仍然是一个servlet,那么就会涉及到serlvet的生成以及如何绑定到tomcat上下文context。

下面从springboot源码开始分析这个过程:

首先是DispatcherServlet bean对象的自动生成和配置:

1.  查看org.springframework.boot.autoconfigure依赖jar包META-INF/spring.factories文件。

备注:springboot的SPI机制跟Servlet 3.0规范的spi略有不同,它查找的是META-INF/spring.factories文件,具体实现细节,会单独开一篇文章来讲,不在本章的范围

spring.factories文件里面有DispatcherServletAutoConfiguration,也就是说这个类会在springboot启动时被加载:

 

 DispatcherServletAutoConfiguration这个配置类从上面的注解都可以看出来:是对dispatcherServlet进行自动配置的,上面的一些条件注解大致也猜的出来:必须是servlet类型才会生效。这个里面还有个内部类:DispatcherServletRegistrationConfiguration

下面重点看这个内部类里面的两处代码:

a. 自动生成dispatcherServlet bean对象

  b. 生成一个用于注册dispatcherServlet的bean对象DispatcherServletRegistrationBean:

然后查看一下DispatcherServletRegistrationBean类图层级结构:
                        

发现ServletContextInitializer是其顶级父级接口,而且这是一个函数式接口:有且仅有一个抽象方法,这种情况通常需要通过匿名内部类或者lambda表达式重写该方法。

那么看看这个接口里面有什么方法:

这个接口只有一个onStartup方法, 而且方法说明的大概意思是:将servlet,过滤器,监听器等参数加入到ServletContext上下文,进行初始化配置。

因为springboot的web容器通常是tomcat, 这里可以理解为将servlet,过滤器,监听器等参数加入到tomcat上下文.

然后根据之前的DispatcherServletRegistrationBean类图层级结构,应选择onStartup方法的实现类是:RegistrationBean: 

然后查看RegistrationBean的onStartup方法实现,这里面有个register方法

 而register是一个抽象方法,需要进一步看看子类DynamicRegistrationBean的具体实现

 子类DynamicRegistrationBean中register方法中:重点看addRegistration方法:

 继续跟进addRegistration方法,发现该方法为抽象方法,所以继续跟进子类ServletRegistrationBean的实现:

 可以看到ServletRegistrationBean中的addRegistration方法最终会将servlet添加到tomcat上下文

而ServletRegistrationBean是DispatcherServletRegistrationBean的父类,

DispatcherServletRegistrationBean会通过它的构造方法中的super方法将DispatcherServlet传递给ServletRegistrationBean,所以最终DispatcherServlet也会通过addRegistration方法添加到tomcat上下文.

阶段性总结一下: DispatcherServletRegistrationConfiguration会完成两件事:

  •  a. 创建DispatcherServlet bean对象(如下图所示DispatcherServletRegistrationConfiguration是一个内部类,import了DispatcherServletConfiguration,故其实例化之前必然触发DispatcherServletConfiguration的配置类DispatcherServlet 实例化)
  • b. 创建DispatcherServletRegistrationBean对象,这个对象的作用是:外部调用能够通过其顶级父接口ServletContextInitializer的onStartup方法最终层层调用到ServletRegistrationBean中的addRegistration方法:最终将DispatcherServlet添加到tomcat上下文

那么问题来了:这个onStartup方法是怎么触发的?

跟踪springboot的启动代码,最终会追踪到ServletWebServerApplicationContext的createWebServer方法(org.springframework.boot.SpringApplication#run(java.lang.String...)--->org.springframework.boot.SpringApplication#refreshContext--->org.springframework.context.support.AbstractApplicationContext#refresh--->org.springframework.context.support.AbstractApplicationContext#onRefresh--->org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer)

这里特别关注一下getSelfInitializer方法

这里可以看到:getSelfInitializer实际返回的是ServletContextInitializer对象,通过selfInitialize方法重写了ServletContextInitializer的onStartup方法(this::selfInitialize是lambda表达式,通过它重写函数式接口的方法)

selfInitialize方法内部恰好有一段for循环遍历:调用了ServletContextInitializer的onStartup方法。

从debug调试的结果来看这个for循环中getServletContextInitializerBeans返回的集合当中果然包含了DispatcherServletRegistrationBean,那么根据之前的分析,最终必然会从其顶级父接口ServletContextInitializer的onStartup方法层层调用,一直到==》ServletRegistration中的addRegistration方法,将DispatcherServlet添加到tomcat的上下文。

但问题是:getServletContextInitializerBeans方法是如何生成DispatcherServletRegistrationBean的,所以继续跟进这个方法,跳到ServletContextInitializerBeans方法:

这个方法代码很长:但是重点只有两个:

  1.    ServletContextInitializerBeans的构造方法是如何生成一个ServletContextInitializer集合的
  2.     DispatcherServletRegistrationBean是如何生成的

第一个问题其实很隐蔽,不容易发现。

通过仔细分析,发现ServletContextInitializerBeans继承了AbstractCollection,而AbstractCollection实现了Collection接口。那么突然灵光一闪:既然ServletContextInitializerBeans是一个集合,那么它必然有iterator迭代器属性,会不会是重写了iterator迭代器方法

 然后发现ServletContextInitializerBeans果然重写了iterator迭代器方法

返回值是sortedList, 那么也就是说ServletContextInitializerBeans构造方法返回的集合其实就是sortedList, 这才是问题的关键!

	@Override
	public Iterator<ServletContextInitializer> iterator() {
		return this.sortedList.iterator();
	}
 
 

 而sortedList本身就是ServletContextInitializer的List集合

继续跟进ServletContextInitializerBeans构造方法中的addServletContextInitializerBeans方法

 接着跟进addServletContextInitializerBeans方法中的getOrderedBeansOfType方法:

 在 getOrderedBeansOfType方法中可以看到:通过beanFactory.getBeanNamesForType方法,就能将IOC容器中所有已加载的bean对象获取出来(本质还是通过反射)

然后回顾一下本文的开头:DispatcherServletRegistrationConfiguration类中已经自动生成了DispatcherServletRegistration的bean对象

所以getOrderedBeansOfType方法必然可以通过反射获取到DispatcherServletRegistrationBean对象

然后回到addServletContextInitializerBeans方法,打个断点看看getOrderedBeansOfType方法的返回值:

 可以看到这里果然返回了DispatcherServletRegistrationBean对象,正好印证了上面的观点。

然后层层跟进addServletContextInitializerBean方法

可以看到: 最终DispatcherServletRegistrationBean会添加到initializers集合
而这个initializers会影响到sortedList, 也就是ServletContextInitializerBeans重写的iterator迭代器返回的集合

这也就是为什么通过构造一个ServletContextInitializerBeans实例,就能返回一个包含DispatcherServletRegistrationBean集合的原因

然后getServletContextInitializerBeans方法就能获取到包含DispatcherServletRegistrationBean的集合,然后就能从其顶级父接口ServletContextInitializer的onStartup方法开始层层调用,一直到==》ServletRegistration中的addRegistration方法,将DispatcherServlet添加到tomcat的上下文。

疑问

如图所示,ServletWebServerFactory factory = getWebServerFactory();这行代码触发了DispatcherServlet的实例化,后续步骤才是DispatcherServlet被添加到tomcat的上下文中,问题是getWebServerFactory()代码是怎么触发DispatcherServlet实例化的呢?这里实际是在获取tomcatServletWebServerFactory这个实例,也就是说tomcatServletWebServerFactory的实例化过程中依赖了DispatcherServlet,debug下,实例化关系大致如下,可以自行翻看源码,其实就是一些配置类之间的依赖关系,跟上面提到的DispatcherServletRegistrationConfiguration会完成两件事,其一就是初始化DispatcherServlet原理几乎一样(StartupStep只是标识流程进度而用,没啥实际意义)。实例化tomcatServletWebServerFactory的过程中依次会实例化以下类:

tomcatServletWebServerFactory

org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat

websocketServletWebServerCustomizer

org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration

servletWebServerFactoryCustomizer

org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

server-org.springframework.boot.autoconfigure.web.ServerProperties

org.springframework.boot.context.properties.BoundConfigurationProperties

tomcatServletWebServerFactoryCustomizer

tomcatWebServerFactoryCustomizer

org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$TomcatWebServerFactoryCustomizerConfiguration

localeCharsetMappingsCustomizer

org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration

errorPageCustomizer

org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration

dispatcherServletRegistration

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration

dispatcherServlet

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration

spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties

最后进入断点

重点可以看看最后几个类之间的依赖关系,前面的可以忽略,了解tomcatServletWebServerFactory实例化怎么触发DispatcherServlet即可(注意@Configuration和@Bean的实例化顺序,内部类和外部类的加载顺序)。

相关文章:

SpringBoot源码之DispatcherServlet初始化的执行时机

————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/jiaohuizhuang6019/article/details/129947758

猜你喜欢

转载自blog.csdn.net/meser88/article/details/138135425