springboot源码解读之内嵌tomcat的启动

目录

BeanDefinition注册

创建配置Tomcat

Tomcat基本组件配置

Context配置

 TomcatStart中的initializer

启动tomcat

Service启动

容器启动

DefaultServlet悬案

启动Connector


在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则会抛出异常。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值