servlet里面为什么有时候覆_Springboot为什么可以不用web.xml

d5a449c913dc0f6da9e0d512f425d943.png

前言:

为什么要写这个,因为感觉自己每天浑浑噩噩的过着...天天各种摸鱼,人都快成咸鱼了,整个人也没啥成长的。想着每个一段时间写一篇博客,梳理一下自己所学的东西,应该对自己非常的有帮助,毕竟我们自己有时候觉得自己已经什么都知道了,但是和别人说的时候却又说不出来,这就是没有梳理的后果。本来想着一周一篇的…想了一下,自己手上又是switch又是王者荣耀还有炉石,冲分的一大堆…还是两周一篇吧~(希望自己可以坚持的久一点,我已经预感自己早晚会鸽的~~~)

废话说了一大堆,下面开始我们的正文:

  1. ServletContainerInitializer

Springboot为什么可以不使用web.xml?

因为Servlet3.0的规范中提供了一种javaConfig的配置来代替我们原本的web.xml,那这个javaConfig是什么了,就是 ServletContainerInitializer接口。「为什么我能这么直接的说出来,百度一下不就知道了」

原理如下--->通过Java Spi 对ServletContainerInitializer的实现类进行加载

  • 容器在启动的时候会通过java spi获取所有的的ServletContainerInitializer的实现类,并且执行onStartup方法
  • 另外在实现ServletContainerInitializer时是可以通过@HandlesTypes注解定义当前实现类需要处理的类型「怎么说了之前自己理解这句话搞了很久,简单说就是注解里面写什么就处理什么」,容器会将当前应用中所有这一类型(继承或者实现)的类放在ServletContainerInitializer接口的集合 『Set<Class<?>> c』中传递进来。如果不定义处理类型,或者应用中不存在相应的实现类,则集合参数c为空。
    这里 如果类实现了ServletContainerInitializer的接口,做为独立的包发布,在打包时,会在JAR 文件的 META-INF/services/javax.servlet.ServletContainerInitializer 文件中进行注册。 容器在启动时,就会扫描所有带有这些注册信息的类(@HandlesTypes(WebApplicationInitializer.class)这里就是加载WebApplicationInitializer.class类)进行解析,启动时会调用其 onStartup方法——也就是说servlet容器负责加载这些指定类, 而ServletContainerInitializer的实现者(例如Spring-web中的SpringServletContainerInitializer对接口ServletContainerInitializer的实现中,是可以直接获取到这些类的)

941363bc6f1d02e741f88a6a24f62c55.png
图1

2.Spring 如何支持无Web.xml

首先我们看一段Spring源码中的注释:

96e788882e0bc1476a3f3989650b060d.png
图2

大概的意思为:

根据onStartup 上的注解我们Spring的 通过SpringServletContainerInitializer 将ServletContext委托给了WebApplicationInitializer。

图2中的这一段是来至于spring-web下面的/resources/META-INF/services/javax.servlet.ServletContainerInitializer。前面咱们就说过容器在启动时,就会扫描META-INF/services/javax.servlet.ServletContainerInitializer 文件中进行注册,springboot的无web.xml就是从这里开始的。「看到这里你是不是会非常的激动,想动手debug看一下了SpringServletContainerInitializer#onSetup 中怎么执行,emmmm~~~是不是发现怎么都debug不进去(手动滑稽)是的我也是这样的。

「当然这里讨论的是springboot内嵌容器,如果debug进去了麻烦留言告知一下」」

这里插入一嘴,如果你熟悉SpringMvc你就会知道知道一个类DispatchServlet,这个类大家用SpringMvc的一定都配置过,但在Springboot中 就是通过org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#registerDispatcherServlet 实现无web.xml的情况下创建DispatchServlet。

93a3fdccb441a7792abfa16dcb1ef0de.png
图3

如果你有耐心的话「如果你非要纠结为什么是有耐心,因为我是把所有的实现都debug了(哭了~)」,你可以发现当使用内嵌容器,springboot 完全走了另一套初始化流程,进入了 TomcatStarter 这个类,TomcatStarter其实也是ServletContainerInitializer的实现类。至于为什么Springboot内嵌容器会走这一套了(https://github.com/spring-projects/spring-boot/issues/321)看这里,这里大佬们已经说了,就是故意的。springboot的启动方式有两种一种是 java -jar 使用内嵌容器,另外一种是war包,交给外部容器使用。前者就会导致容器搜索算法出现问题,因为这是 jar 包的运行策略,不会按照 servlet3.0 的策略去加载 ServletContainerInitializer。

查看TomcatStarter的onStartUp方法 发现他主要的逻辑在于ServletContextInitializer的onStartUp,查看initializers 数组中的类,我们可以看见一个 ServletWebServerApplicationContext 的匿名类,剧透一下,我们对应的servlet,filter,都是在这里面进行的。

这里大家就可以自己看了,但是写了这么点,感觉有点少,所有凑个字数,我也来分析一波

515ca57c583b86b96e81460529e00009.png
图4

3.tomcatStart中initializers的ServletWebServerApplicationContext分析

看到XXXApplicationContext我们其实就可以想到在看Spring源码中最重要的一个类AbstractApplicationContext

31cf26c498349ededfdebe0311d40efb.png
图5

所以我们看ServletWebServerApplicationContext也是和看spring源码一样,看refresh()方法,而refresh中onRefresh()是用来描述ApplicationContext生命周期的方法,创建一个WebServer。

141288cbdbd23c965f82d690f933607c.png

点进去我们可以看到webServer的初始化过程,这里要分析的话需要将 factory.getWebServer / getSelfInitializer分开分析,getSelfInitializer中涉及到的就是我们web中用到的servlet filter lister ,factory.getWebServer 内嵌tomcat的其中,这里了内嵌tomcat不是我们的重点「这里就是为什么你debug的时候,从initializer.onStartup(servletContext) 直接跳到原因,bz也懵逼了很久 」,这里规避一下,说一下getSelfInitializer。

84160c0def304a1aa0592e5880bd46a9.png

还记得TomcatStarter中的initializers信息?ServletWebServerApplicationContext的创建就是这里的 getSelfInitializer()方法创建的!解释下这里的 getSelfInitializer() 和 selfInitialize(ServletContext servletContext) 为什么要这么设计:这是典型的回调式方式,当匿名 ServletContextInitializer 类被 TomcatStarter 的 onStartup 方法调用,设计上是触发了 selfInitialize(ServletContext servletContext) 的调用。所以这下就清晰了,为什么 TomcatStarter 中没有出现 RegisterBean ,其实是隐式触发了 ServletWebServerApplicationContext 中的 selfInitialize 方法。selfInitialize 方法中的 getServletContextInitializerBeans() 成了关键。

4aadf625f2a2b6dbef579cf9b5ecad58.png

b256b11c2634bf3ca8afe90dcb5a6f4d.png

558e69c3393f56a9c271bd3a82de09e5.png

总结一下:

  • ServletWebServerApplicationContext 的 onRefresh 方法触发配置了一个匿名的 ServletContextInitializer。
  • 这个匿名的 ServletContextInitializer 的 onStartup 方法会去容器中搜索到了所有的 RegisterBean 并按照顺序加载到 ServletContext 中。
  • 这个匿名的 ServletContextInitializer 最终传递给 TomcatStarter,由 TomcatStarter 的 onStartup 方法去触发 ServletContextInitializer 的 onStartup 方法,最终完成装配!

最后说一下本文基于spring-boot :2.1.7,如果有出入请确认版本。

emmmm~~~第一篇文章就这么开心的水完了.......下下个周末发什么了....emmm~~bz最近在研究使用groovy+spock写单元测试,bz支持调到后台部门之后发现,测试大部分只能靠自己了......自己动手丰衣足食

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值