在springboot项目中,我们只需要引入spring-boot-starter-web依赖,启动服务成功,我们一个web服务就搭建好了,没有明显的看到tomcat。其实打开spring-boot-starter-web依赖,我们可以看到:依赖了tomcat。
1.进入Springboot启动类。我们加入Springboot最核心的注解@SpringBootApplication,源码如下图:重点看注解@EnableAutoConfiguration,
2.进入注解@EnableAutoConfiguration,如下图:该注解通过@Import注解导入了AutoConfigurationImportSelector类。其实这个类,就是导入通过加载配置文件,加载了很多工厂方法的配置类。
3.进入AutoConfigurationImportSelector类。首先调用selectImport()方法,在该方法中调用了 getAutoConfigurationEntry()方法,在之中又调用了getCandidateConfigurations()方法, getCandidateConfigurations()方法就去META-INF/spring.factory配置文件中加载相关配置类。
详细讲解如下:也就是下图的,方法1调用方法2,方法2调用方法3:
到了这里加载了 META-INF/spring.factories文件:
4.我们看到,加载了ServletWebServerFactoryAutoConfiguration这个配置类,web工厂配置类。
@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 {
...
}
从这个配置工厂类,我们看出通过@Import注解加载了tomcat,jetty,undertow三个web服务器的配置类。由于没有导入jetty和undertow的相关jar包,这两个类实例的不会真正的加载。
5.进入EmbeddedTomcat类,创建了TomcatServletWebServerFactory类的对象。
@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider connectorCustomizers,
ObjectProvider contextCustomizers,
ObjectProvider> 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;
}
}
6.进入TomcatServletWebServerFactory类,关注getWebServer()方法:
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
//实例化一个Tomcat
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
//设置Tomcat的工作临时目录
tomcat.setBaseDir(baseDir.getAbsolutePath());
//默认使用Http11NioProtocal实例化Connector
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
//给Service添加Connector
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
//关闭热部署
tomcat.getHost().setAutoDeploy(false);
//配置Engine
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
// 实例化TomcatWebServer时会将DispatcherServlet以及一些Filter添加到Tomcat中
return getTomcatWebServer(tomcat);
}
getWebServer()方法在当前类,调用了getTomcatWebServer()方法,其实又new TomcatWebServer()对象:
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
7.进入TomcatWebServer类,这个类才是真正的做tomcat启动的类:
(1)构造方法:调用了initialize()方法
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
(2)进入initialize()方法,这个方法:this.tomcat.start(),启动tomcat容器了。
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();
}
});
// 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
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
上面分析了tomcat的配置到启动的方法,我们现在来分析,tomcat是何时启动的。
1.首先进入SpringBoot启动类的run方法:
public static void main(String[] args) {
SpringApplication.run(SpringBootMytestApplication.class, args);
}
最终调用了本类的一个同名方法:
public ConfigurableApplicationContext run(String... args) {
//记录程序运行时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// ConfigurableApplicationContext Spring 的上下文
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//【1、获取并启动监听器】
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//【2、构造应用上下文环境】
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//处理需要忽略的Bean
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
///【3、初始化应用上下文】
context = createApplicationContext();
//实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//【4、刷新应用上下文前的准备阶段】
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//【5、刷新应用上下文】
refreshContext(context);
//【6、刷新应用上下文后的扩展接口】
afterRefresh(context, applicationArguments);
//时间记录停止
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//发布容器启动完成事件
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
这个方法大概做了以下几件事
1)获取并启动监听器 通过加载META-INF/spring.factories 完成了 SpringApplicationRunListener实例化工作
2)构造容器环境,简而言之就是加载系统变量,环境变量,配置文件
3).创建容器
4) 实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
5)准备容器
6) 刷新容器
7)刷新容器后的扩展接口
2.那么内置tomcat启动源码,就是隐藏在上诉第六步:refreshContext方法里面,该方法最终会调 用到AbstractApplicationContext类的refresh()方法,进入refreshContext()方法,如图:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
refreshContext()调用了refresh()方法:
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
refresh()方法调用了this.onRefresh():
@Override
protected void onRefresh() {
super.onRefresh();
try {
//核心方法:会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
如下面的代码:createWebServer() 方法调用了一个factory.getWebServer()。
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
//先获取嵌入式Servlet容器工厂
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();
}
到了这里getWebServer()方法,下一步就是创建TomcatWebServer对象,创建该对象,就在构造方法启动了Tomcat。详细代码在第一部分有。