当springmvc搭上springboot的便车后,tom

前面几篇基本上是把web开发从servlet带到了springmvc,开发越来越简便,配置文件越来越少,索性可以直接抛弃了,但是随着springboot的出现,我们还能做到更简便,只需要引入一个依赖,然后一个main方法直接一键启动web应用,而且连tomcat都不需要自己配了。

1
2
3
4
xml复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

但是在享受这种便捷的同时还是需要了解为什么,只有了解了整个流程才能运筹帷幄,虽然我一直觉得知道的越多不会的就越多(难受.jpeg)。好了废话不多说,来看一看在springboot中tomcat去哪了。

内嵌容器的启动

首先我们还记得前面提到过的Servlet3.0提供的两个扩展接口吧,先去瞅一瞅在springboot中的实现是怎么样的,首先试一试还是在META-INF下按规则创件一个文件

image.png
然后启动应用,发现控制台并没有按照预期和前面讲springmvc那样打印出这几个类的名字,这是咋回事呢?这里就带着这个问题进入主题。
点开这个ServletContainerInitializer发现他的实现类比起前面springmvc那个实现SpringServletContainerInitializer还额外多了TomcatStarter,点进去发现这是springboot的实现,但是这跟前面那个不生效有什么关系呢。

我们知道当我门在springboot中引入web依赖的时候只需要一键启动,并不需要像运行springmvc工程那样提前配置tomcat,这是因为在web依赖中已经引入了tomcat的依赖,这里使用的是内嵌的tomcat,当然也可以像springmvc那样使用外部tomcat(后面再说),那既然前面使用内嵌容器效果不符合预期,那就来分析一下启动流程,直接进入run方法,就能看到springboot启动的核心流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scss复制代码ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);

其他的我们暂时不需要关心,前面准备好环境之后,来到refreshContext这一步,这里就会进到AbstractApplicationContext的refresh方法,知道spring的人应该都知道这个方法,然后我们注意力放在onfresh()这个方法上,打上断点,直接step into 发现该方法进入到了ServletWebServerApplicationContext

1
2
3
4
5
6
7
8
9
10
typescript复制代码@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}

然后这里就是关键了,看名字就是创建server,在这里默认就是创建内嵌的tomcat,所以我们可以看出来springboot和springmvc的启动室友明显差别的,前者是依赖spring的refresh带动tomcat启动,后者是依赖tomcat启动加监听器来带动spring的refresh,继续跟进

1
ini复制代码this.webServer = factory.getWebServer(getSelfInitializer());

来到selfInitialize方法,这里先设置父容器,所以在springboot中也是存在父子容器的

1
2
3
4
5
6
7
8
scss复制代码private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}

然后就出现了一个ServletContextInitializer,是不是感觉他和servlet提供的那个ServletContainerInitializer很相似,但是这是springboot提供的,我们看一下她的实现类,有好几个,但是我们先记住DispatcherServletRegistrationBean,看名字就知道他是用来注册DispatcherServlet的,然后是在这一步执行这些实现类的onstartup方法的,连方法名都一样,值得思考。

继续往下来到创建tomcat的方法里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
scss复制代码@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}

在tomcat创建好后进入prepareContext方法,同时传入了前面拿到的ServletContextInitializer们,然后进入最关键的一步configureContext(),

1
2
3
4
5
6
7
ini复制代码TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
context.addServletContainerInitializer(starter, NO_CLASSES);

这里创建了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的(自动配置这些不是我想要说的内容,默认大家都知道了),不信点击那个绿色的圈圈

image.png
就能进入这个自动配置类DispatcherServletAutoConfiguration,然后再回头来看onstartup的实现,进入RegistrationBean,看到需要调用子类的register方法

1
2
3
4
5
6
7
8
9
java复制代码@Override
protected final void register(String description, ServletContext servletContext) {
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
return;
}
configure(registration);
}

继续进入addRegistration方法来到ServletRegistrationBean,发现这是一个泛型class

1
scala复制代码public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic>

其实现类就是前面提到的就是DispatcherServletRegistrationBean,提供的泛型恰好就是DispatcherServlet,so?bean也有了,类型也确定了,

1
2
3
4
5
typescript复制代码@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}

那么这个放法前面我已经演示过了,可以动态注册servlet,是不是觉得自己突然懂了什么。。。

最后的最后,请一定注意,这里面说的所有东西都是基于内嵌容器来说的,如果使用了外部容器,我们前面那个例子就会生效了,因为他走的就是以前springmvc的流程

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%