前面几篇基本上是把web开发从servlet带到了springmvc,开发越来越简便,配置文件越来越少,索性可以直接抛弃了,但是随着springboot的出现,我们还能做到更简便,只需要引入一个依赖,然后一个main方法直接一键启动web应用,而且连tomcat都不需要自己配了。
1 | xml复制代码<dependency> |
但是在享受这种便捷的同时还是需要了解为什么,只有了解了整个流程才能运筹帷幄,虽然我一直觉得知道的越多不会的就越多(难受.jpeg)。好了废话不多说,来看一看在springboot中tomcat去哪了。
内嵌容器的启动
首先我们还记得前面提到过的Servlet3.0提供的两个扩展接口吧,先去瞅一瞅在springboot中的实现是怎么样的,首先试一试还是在META-INF下按规则创件一个文件
然后启动应用,发现控制台并没有按照预期和前面讲springmvc那样打印出这几个类的名字,这是咋回事呢?这里就带着这个问题进入主题。
点开这个ServletContainerInitializer
发现他的实现类比起前面springmvc那个实现SpringServletContainerInitializer
还额外多了TomcatStarter,点进去发现这是springboot的实现,但是这跟前面那个不生效有什么关系呢。
我们知道当我门在springboot中引入web依赖的时候只需要一键启动,并不需要像运行springmvc工程那样提前配置tomcat,这是因为在web依赖中已经引入了tomcat的依赖,这里使用的是内嵌的tomcat,当然也可以像springmvc那样使用外部tomcat(后面再说),那既然前面使用内嵌容器效果不符合预期,那就来分析一下启动流程,直接进入run方法,就能看到springboot启动的核心流程
1 | scss复制代码ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); |
其他的我们暂时不需要关心,前面准备好环境之后,来到refreshContext这一步,这里就会进到AbstractApplicationContext的refresh方法,知道spring的人应该都知道这个方法,然后我们注意力放在onfresh()这个方法上,打上断点,直接step into 发现该方法进入到了ServletWebServerApplicationContext
中
1 | typescript复制代码@Override |
然后这里就是关键了,看名字就是创建server,在这里默认就是创建内嵌的tomcat,所以我们可以看出来springboot和springmvc的启动室友明显差别的,前者是依赖spring的refresh带动tomcat启动,后者是依赖tomcat启动加监听器来带动spring的refresh,继续跟进
1 | ini复制代码this.webServer = factory.getWebServer(getSelfInitializer()); |
来到selfInitialize
方法,这里先设置父容器,所以在springboot中也是存在父子容器的
1 | scss复制代码private void selfInitialize(ServletContext servletContext) throws ServletException { |
然后就出现了一个ServletContextInitializer,是不是感觉他和servlet提供的那个ServletContainerInitializer很相似,但是这是springboot提供的,我们看一下她的实现类,有好几个,但是我们先记住DispatcherServletRegistrationBean
,看名字就知道他是用来注册DispatcherServlet的,然后是在这一步执行这些实现类的onstartup方法的,连方法名都一样,值得思考。
继续往下来到创建tomcat的方法里面
1 | scss复制代码@Override |
在tomcat创建好后进入prepareContext方法,同时传入了前面拿到的ServletContextInitializer们,然后进入最关键的一步configureContext()
,
1 | ini复制代码TomcatStarter starter = new TomcatStarter(initializers); |
这里创建了TomcatStarter,我们前面说了她又springboot提供,并且是实现了servlet提供的ServletContainerInitializer接口的,所以到这里就应该很明了了,springboot通过控制内嵌tomcat的启动,手动管理了ServletContainerInitializer的注册,这里只注册了TomcatStarter这一个实现,所以我们现在应该知道了为什么我们最开始写的那个例子不生效了,因为人压根儿就没察觉到你的存在,他只会关心TomcatStarter这一个实现,然后sprngboot又在这个实现里传入了自己提供的扩展接口ServletContextInitializer,看起来就好像效果一样,我们只需要实现这个接口就能达到一样的效果,而且还不用额外去添加一个META-INF文件了。
回过头来总结一下吧,首先是sprinboot并没有说抛弃ServletContainerInitializer,只是她为了能更好的控制整个应用的生命周期,所以强行提供了一个类似的接口ServletContextInitializer,他俩的效果几乎是一摸一样的,但是对于我们开发者来说,如果是使用了内嵌容器的这种方式,我们的开发更便捷了,更加不用考虑容器底层的实现机制了,只用关心我们的业务,但是能够了解这个过程也还是很有好处的。
DispatchServlet
但凡是springmvc的应用,都离不开DispatchServlet,在分析springmvc的时候我们知道了他的加载实际(分别是xml形式和config形式),那我们再来看一看在springboot中他又是咋个加载的。
上面我们让注意一个东西,就是DispatcherServletRegistrationBean,他是ServletContextInitializer的实现,所以在内嵌容器启动的时候他就会执行onstartup方法
首先进到这个class,借助idea的提示,在class前面有一个绿色的圈圈,表示这个class是被springboot自动配置过的,也就是说springboot在自动创建bean的时候是包含了DispatchServlet相关的bean的(自动配置这些不是我想要说的内容,默认大家都知道了),不信点击那个绿色的圈圈
就能进入这个自动配置类DispatcherServletAutoConfiguration
,然后再回头来看onstartup的实现,进入RegistrationBean
,看到需要调用子类的register方法
1 | java复制代码@Override |
继续进入addRegistration方法来到ServletRegistrationBean
,发现这是一个泛型class
1 | scala复制代码public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> |
其实现类就是前面提到的就是DispatcherServletRegistrationBean,提供的泛型恰好就是DispatcherServlet
,so?bean也有了,类型也确定了,
1 | typescript复制代码@Override |
那么这个放法前面我已经演示过了,可以动态注册servlet,是不是觉得自己突然懂了什么。。。
最后的最后,请一定注意,这里面说的所有东西都是基于内嵌容器来说的,如果使用了外部容器,我们前面那个例子就会生效了,因为他走的就是以前springmvc的流程
本文转载自: 掘金