SpringBoot之所以能有使用java -jar命令启动, 是因为内部嵌入了web容器jar包, 如tomcat, jetty… 分析下SpringBoot中tomcat加载流程.

SpringBoot自动依赖spring-boot-autoconfigure-2.1.2.RELEASE.jar(参考版本), 在META-INF文件夹spring.factories文件, 开启了自动配置类ServletWebServerFactoryAutoConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\

ServletWebServerFactoryAutoConfiguration类通过Import注解 依次向Spring容器中注入EmbeddedTomcat, EmbeddedJetty, EmbeddedUndertow

org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguratio

@Configuration
@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 {

	@Bean
	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(
			ServerProperties serverProperties) {
		return new ServletWebServerFactoryCustomizer(serverProperties);
	}

	@Bean
	@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
	public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
			ServerProperties serverProperties) {
		return new TomcatServletWebServerFactoryCustomizer(serverProperties);
	}

	...

}

ConditionalOnMissingBean表示如果在Spring容器中没有找到对应的bean, 如果没有找到则加载加载当前bean

而TomcatServletWebServerFactory, JettyServletWebServerFactory, UndertowServletWebServerFactory都实现了ServletWebServerFactory接口, 所以Spring自动装配后只会加载一个Web服务
在这里插入图片描述
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration


@Configuration
class ServletWebServerFactoryConfiguration {

	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
			return new TomcatServletWebServerFactory();
		}

	}

	/**
	 * Nested configuration if Jetty is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
			WebAppContext.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedJetty {

		@Bean
		public JettyServletWebServerFactory JettyServletWebServerFactory() {
			return new JettyServletWebServerFactory();
		}

	}

	/**
	 * Nested configuration if Undertow is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedUndertow {

		@Bean
		public UndertowServletWebServerFactory undertowServletWebServerFactory() {
			return new UndertowServletWebServerFactory();
		}

	}

}

SpringBoot 默认会装载TomcatWeb服务, 在Spring容器Refresh过程中, 通过onRefresh方法对TomcatServletWebServerFactory创建对象, 并创建并启动tomcat

调用栈:

initialize:106, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
<init>:86, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
getTomcatWebServer:415, TomcatServletWebServerFactory (org.springframework.boot.web.embedded.tomcat)
getWebServer:174, TomcatServletWebServerFactory (org.springframework.boot.web.embedded.tomcat)
createWebServer:181, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
onRefresh:154, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:540, AbstractApplicationContext (org.springframework.context.support)
refresh:142, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:775, SpringApplication (org.springframework.boot)
refreshContext:397, SpringApplication (org.springframework.boot)
run:316, SpringApplication (org.springframework.boot)
run:1260, SpringApplication (org.springframework.boot)
run:1248, SpringApplication (org.springframework.boot)
main:28, CoreServiceApplication (com.cloud.demo.core)

org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer

public WebServer getWebServer(ServletContextInitializer... initializers) {
	Tomcat tomcat = new Tomcat();
	// 创建tomcat临时工作目录 C:\Users\GuanDS\AppData\Local\Temp\tomcat.4335440695790229478.9999
	File baseDir = (this.baseDirectory != null) ? this.baseDirectory
			: createTempDir("tomcat");
	tomcat.setBaseDir(baseDir.getAbsolutePath());
	Connector connector = new Connector(this.protocol);
	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);
	// 启动tomcat
	return getTomcatWebServer(tomcat);
}

prepareContext Tomcat配置上下文
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#prepareContext

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
	File documentRoot = getValidDocumentRoot();
	TomcatEmbeddedContext context = new TomcatEmbeddedContext();
	if (documentRoot != null) {
		context.setResources(new LoaderHidingResourceRoot(context));
	}
	context.setName(getContextPath());
	context.setDisplayName(getDisplayName());
	context.setPath(getContextPath());
	File docBase = (documentRoot != null) ? documentRoot
			: createTempDir("tomcat-docbase");
	context.setDocBase(docBase.getAbsolutePath());
	context.addLifecycleListener(new FixContextListener());
	context.setParentClassLoader(
			(this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
					: ClassUtils.getDefaultClassLoader());
	resetDefaultLocaleMapping(context);
	addLocaleMappings(context);
	context.setUseRelativeRedirects(false);
	configureTldSkipPatterns(context);
	WebappLoader loader = new WebappLoader(context.getParentClassLoader());
	loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
	loader.setDelegate(true);
	context.setLoader(loader);
	if (isRegisterDefaultServlet()) {
		addDefaultServlet(context);
	}
	if (shouldRegisterJspServlet()) {
		addJspServlet(context);
		addJasperInitializer(context);
	}
	context.addLifecycleListener(new StaticResourceConfigurer(context));
	ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
	host.addChild(context);
	configureContext(context, initializersToUse);
	postProcessContext(context);
}

org.springframework.boot.web.embedded.tomcat.TomcatWebServer#TomcatWebServer(org.apache.catalina.startup.Tomcat, boolean)

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
	Assert.notNull(tomcat, "Tomcat Server must not be null");
	this.tomcat = tomcat;
	this.autoStart = autoStart;
	initialize();
}

private void initialize() throws WebServerException {
	logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
	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
			// 启动了tomcat服务
			this.tomcat.start();

			// We can re-throw failure exception directly in the main thread
			rethrowDeferredStartupExceptions();

			try {
				ContextBindings.bindClassLoader(context, context.getNamingToken(),
						getClass().getClassLoader());
			}
			catch (NamingException ex) {
				// Naming is not enabled. Continue
			}

			// Unlike Jetty, all Tomcat threads are daemon threads. We create a
			// blocking non-daemon to stop immediate shutdown
			// 阻塞tomcat 调用 TomcatWebServer.this.tomcat.getServer().await();
			startDaemonAwaitThread();
		}
		catch (Exception ex) {
			stopSilently();
			throw new WebServerException("Unable to start embedded Tomcat", ex);
		}
	}
}

至此, Tomcat容器已完成启动.

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐