目录
在springboot之前,web服务都是要部署在web容器里的。但是springboot改变了这个模式,web容器只是服务的一个组件,服务可以选择是否对外暴漏HTTP接口,以及使用什么容器暴漏HTTP接口。今天就来看下在springboot中内嵌的tomcat是如何启动的。
BeanDefinition注册
springboot的另一个特性就是autoconfigure,只要在spring.factories文件中配置EnableAutoConfiguration的实现,springboot就会自动加载配置类中定义的bean
看下spring-boot-autoconfigure的spring.factories中的文件内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
...
其中DispatcherServletAutoConfiguration负责注册servlet相关的bean。
ServletWebServerFactoryAutoConfiguration则通过@Import导入了EmbeddedTomcat,EmbeddedTomcat中注册了TomcatServletWebServerFactory。
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
//...
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
创建配置Tomcat
Tomcat基本组件配置
springboot启动时,若判断为web环境时会创建AnnotationConfigServletWebServerApplicationContext,而这个context继承了ServletWebServerApplicationContext。
tomcat的创建则是在context的onRefresh阶段完成。
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
这里面将WebServer的创建委托给工厂类ServletWebServerFactory。
前面说到EmbeddedTomcat注册了TomcatServletWebServerFactory,默认情况下ServletWebServerApplicationContext getWebServerFactory获取到的就是TomcatServletWebServerFactory。
继续跟踪TomcatServletWebServerFactory的getWebServer,它new了一个tomcat实例,并直接或间接设置了tomcat中的Server、Service、Connector组件,以及Engine、Host、Context容器。(这里默认你已经熟悉了tomcat的相关概念)
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相关组件的逻辑不做展开,可以通过tomcat.getXXX方法和prepareContext方法查看。执行getTomcatWebServer之前tomcat中的Server、Service、Connector、Engine、Host、Context就都已经具备了。只是这时候context中还没有添加servlet对应的Wrapper、Filter及initParameter。
Context配置
这里着重说一下Context的创建,在非嵌入方式启动的tomcat中,Context对应的实现是StandardContext,而在springboot的嵌入方式中context的实现是StandardContext的子类TomcatEmbeddedContext,它在prepareContext中被创建。
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
//...
if (isRegisterDefaultServlet()) {
addDefaultServlet(context);
}
if (shouldRegisterJspServlet()) {
addJspServlet(context);
addJasperInitializer(context);
}
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
configureContext(context, initializersToUse);
postProcessContext(context);
}
默认情况下会为context添加一个DefaultServlet,不会添加JspServlet,关于DefaultServlet这里面还有些疑问,后面专门说一下。
再来看看configureContext,这个方法里创建了一个TomcatStart,并将ServletContextInitializer设置给它,而TomcatStart自己则作为ServletContainerInitializer保存在context中。
这个TomcatStart就是负责启动容器的类,后面会涉及到。
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
context.addServletContainerInitializer(starter, NO_CLASSES);
//...
}
TomcatStart中的initializer
现在分析下TomcatStart的ServletContainerInitializer都有哪些。
ServletWebServerApplicationContext在getWebServer的时候将selfInitialize方法作为最原始的initializer参数传入。
这个方法将在后面被执行,这里先暂且记下selfInitialize是一个ServletContainerInitializer。
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
TomcatServletWebServerFactory的mergeInitializers又对原始的initializer做了处理和补充。
首先补充了一个表达式(servletContext) -> this.initParameters.forEach(servletContext::setInitParameter)用于设置initParameter,
然后补充了SessionConfiguringInitializer用于设置session信息,
最后将factory中的initializers(默认为空)添加进去。
protected final ServletContextInitializer[] mergeInitializers(ServletContextInitializer... initializers) {
List<ServletContextInitializer> mergedInitializers = new ArrayList<>();
mergedInitializers.add((servletContext) -> this.initParameters.forEach(servletContext::setInitParameter));
mergedInitializers.add(new SessionConfiguringInitializer(this.session));
mergedInitializers.addAll(Arrays.asList(initializers));
mergedInitializers.addAll(this.initializers);
return mergedInitializers.toArray(new ServletContextInitializer[0]);
}
启动tomcat
Service启动
再回到WebServer的获取,创建好tomcat之后要将它封装成springboot的WebServer对象,这里会new一个TomcatWebServer。
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
TomcatWebServer的构造方法通过initialize触发一系列的初始化操作。
其中比较重要的两个步骤,先在context中注册了一个事件监听器,用于在context start的时候删除service中的connector,然后就是直接调用了tomcat的start。
private void initialize() throws WebServerException {
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
this.tomcat.start();
//...
startDaemonAwaitThread();
}
//...
}
}
tomcat start则会触发内部Server、Service的启动。
正常情况下,service的容器启动之后会启动connector,但是由于之前添加了context的start监听器,在context start的时候移除了connector,所以tomcat start的时候并不会start connector。
现在spring的context还处于onrefresh阶段,单例bean还没有被创建初始化。如果这个时候外暴漏端口,请求提前进来很可能导致处理失败。所以这里移除connector,等到服务完全准备好后再启动则可以避免此问题。
容器启动
Engine、Host、Context作为tomcat中的容器,它们都是ContainerBase的子类,执行父类LifecycleBase的start的时候又会调用子类ContainerBase的startInternal接口,
容器逐层start采用的是异步方式,但提交异步任务之后会同步等待子容器start完成,效果相当于同步完成。
protected synchronized void startInternal() throws LifecycleException {
//...
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
for (Future<Void> result : results) {
try {
result.get();
}
}
//...
}
在Context的实现类StandardContext执行start的时候TomcatStart作为listener将会被触发。
protected synchronized void startInternal() throws LifecycleException {
//...
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
}
}
//...
}
TomcatStart则会触发它内部所有ServletContextInitializer的onStartup
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
}
其中包括ServletWebServerApplicationContext selfInitialize的执行,主要的逻辑都在这里。另外两个initializer前面已经说到一个设置session信息,一个设置initPrameter
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
selfInitialize的主要逻辑:
1. 关联spring Context与ServletContext
2. 注册application scope
3. 收集所有的ServletContextInitializer,包括FilterRegistrationBean、ServletRegistrationBean、DelegatingFilterProxyRegistrationBean、ServletListenerRegistrationBean以及其他的ServletContextInitializer。然后执行每个ServletContextInitializer的onStartup,内部实际上完成了Filter、Servlet到context的注册。
默认情况下收集到的ServletContextInitializer如下:
AbstractFilterRegistrationBean的注册代码:
protected Dynamic addRegistration(String description, ServletContext servletContext) {
Filter filter = getFilter();
return servletContext.addFilter(getOrDeduceName(filter), filter);
}
ServletRegistrationBean注册代码:
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}
上面省略了很多细节,Servlet会封装成Wrapper设置到Context中,Filter则直接保存在Filter中。
DefaultServlet悬案
前面Context配置里说到,context创建之后会先添加一个DefaultServlet,它的urlPattens是"/",但是ServletRegistrationBean把spring创建的DispatcherServlet设置到context的时候会覆盖DefaultServlet。因为它们的urlPattens都是"/",覆盖之后DefaultServlet虽然还在context中,但是已经匹配不到任何路径了。
不知道spring boot先添加再覆盖掉这么做的目的是什么,也没找到相关的资料,看了注释也理解不了。。。。。。
直接不添加DefaultServlet不行吗???
覆盖的逻辑在ApplicationServletRegistration中:
public Set<String> addMapping(String... urlPatterns) {
//...
for (String urlPattern : urlPatterns) {
String wrapperName = context.findServletMapping(urlPattern);
if (wrapperName != null) {
Wrapper wrapper = (Wrapper) context.findChild(wrapperName);
if (wrapper.isOverridable()) {
// Some Wrappers (from global and host web.xml) may be
// overridden rather than generating a conflict
context.removeServletMapping(urlPattern);
}
}
}
//...
for (String urlPattern : urlPatterns) {
context.addServletMappingDecoded(UDecoder.URLDecode(urlPattern, StandardCharsets.UTF_8), wrapper.getName());
}
//...
}
启动Connector
执行完上面的逻辑之后tomcat就已经启动的差不多了,但是非常重要的connector还没有启动。
ServletWebServerApplicationContext refresh的最后一步创建好单例bean之后,则会执行finishRefresh
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
通过startWebServer方法最终调用TomcatWebServer的start方法
public void start() throws WebServerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
addPreviouslyRemovedConnectors();
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
performDeferredLoadOnStartup();
}
checkThatConnectorsHaveStarted();
this.started = true;
}
//...
finally {
Context context = findContext();
ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
}
}
TomcatWebServer之前把删除的connect保存了起来,现在再把它们重新添加到tomcat的service中。添加的时候就会触发connect的start。
添加启动完connect之后,还会检查每个connect的状态,如果是FAILED则会抛出异常。