springboot 之 tomact 啟動
“先脈絡後細節”
我們知道自servlet3.0開始已經支援@WebServlet 用于将一個類聲明為 Servlet,該注解将會在部署時被容器處理,容器将根據具體的屬性配置将相應的類部署為 Servlet。也就是說我們可以不像以前那樣将servlet、listener配置在web.xml中,使用注解的方式提供了非常友善的擴充性。比如我們即将和後續研究的 springboot 啟動tomact并加載servlet(DispatcherServlet)
話不多說,直奔主題~
springboot 項目啟動後會通過 SpringApplication.run() 方法啟動服務,我們從入口進入springboot 的源碼。通過run 方法一層一層跟入(
中間廢柴代碼略~
)在SpringApplication找到了如下代碼
- 建立SpringApplication對象
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
//建立SpringApplication對象,并調用run()
return new SpringApplication(primarySources).run(args);
}
- 自動裝載(先不深究自動裝載哪些類)
我們看一下通過有參構造建立SpringApplication都做了哪事情:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//設定classloader
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//從META-INF/spring.factories 中擷取 ApplicationContextInitializer,而且這個中涉及到了一個重要的類 SpringFactoriesLoader(為了不打斷主流程,這個類後續單獨介紹)
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//從META-INF/spring.facotories 中擷取 ApplicationContextLinister
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxydJRUT4lFRPhmVIFmb1clWv50MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0YDNzITN1YTMxEzNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
通過上邊的代碼我們知道,在建立SpringApplication過程中自動裝載了spring.factories 的相關,我們繼續完後走
-
調用run方法
在步驟1中建立對象後調用run方法
public ConfigurableApplicationContext run(String... args) {
//StopWatch 用于啟動計時
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//擷取SpringApplicationRunListeners,從類路徑下的META-INF/spring.factories
SpringApplicationRunListeners listeners = getRunListeners(args);
//回調所有的擷取SpringApplicationRunListener.starting()方法
listeners.starting();
try {
//封裝指令行參數
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//加載外部化配置的資源到environment,并觸發ApplicationEnvironmentPreparedEvent事件
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 列印banner圖
Banner printedBanner = printBanner(environment);
//建立ApplicationContext,并通過webApplicationType 決定傳回ConfigurableApplicationContext的具體類型
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//ConfigurableApplicationContext 配置或處理
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//重新整理ConfigurableApplicationContext
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//所有的SpringApplicationRunListener回調finished方法
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;
}
- 啟動重點擷取ConfigurableApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
//web 項目傳回 AnnotationConfigServletWebServerApplicationContext
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
ex);
}
}
//并初始化
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
AnnotationConfigServletWebServerApplicationContext 通過這個東東的名字我們大概就能猜出和web容器的加載、啟動有關,我們看這哥們究竟是幹什麼的
-
create web server & start
我們看代碼發現WebApplicationContextServletContextAwareProcessor 繼承自 ServletContextAwareProcessor,我們繼續看 ServletContextAwareProcessor 裡都有什麼
看圖中的方法你是不是已經看到些端倪了,我們通過上面的 第4步 已經知道,createApplicationContext 如果是web項目會傳回AnnotationConfigServletWebServerApplicationContext,而AnnotationConfigServletWebServerApplicationContex繼承自ServletWebServerApplicationContext,在 第3步 中傳回AnnotationConfigServletWebServerApplicationContext 後執行了 refreshContext(context);springboot 之 tomact 啟動 我們繼續看refersh,發現代碼springboot 之 tomact 啟動
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//ApplicationContxt refresh
((AbstractApplicationContext) applicationContext).refresh();
}
refresh 方法中的context 即 AnnotationConfigServletWebServerApplicationContext ,是以AnnotationConfigServletWebServerApplicationContext.refresh()即調用了父類的refresh
- refresh()
public final void refresh() throws BeansException, IllegalStateException {
try {
super.refresh();
}
catch (RuntimeException ex) {
stopAndReleaseWebServer();
throw ex;
}
}
我勒個去這是個啥(有點兒繞 =_=!! ~),别急,繼續看super.refresh()
開始有點兒柳暗花明了 _~
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
上下兩張圖有木有~ 有木有~ 如果之前不清楚如何啟動的你是否已經開始“哦~”
-
擷取 TomactServletWebServerFactory 、WebServer
createWebServer() 方法就是tomact啟動的關鍵入口,這裡邊有兩個比較關鍵的步驟
1). getWebServerFactory()
2). getSelfInitializer().onStartup(servletContext);
我們先看getWebServerFactory(),在看這個方法之前我們先看一下這個方法的傳回值
ServletWebServerFactory ,看接口的實作你應該大概看到你想要東東了
但Springboot 是如何知道執行個體化TomactServletWebServerFacotry的呢,我們看一下getWebServerFactory裡做了什麼springboot 之 tomact 啟動 protected ServletWebServerFactory getWebServerFactory() { // 通過beanFactory 擷取ServletWebServerFactory 類型的bean,經過跟代碼我們知道,實際上傳回的是 tomcatServletWebServerFactory String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean."); } if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); }
springboot 之 tomact 啟動 那 TomcatServletWebServerFactory 是如何(何時)被加載的呢?
根據我們的猜測一般提供多種實作方式,但最終使用哪種實作一般會根據什麼congfig或滿足什麼條件來決定,我們通過查找代碼發現了(當然可能有其他捷徑) ServletWebServerFactoryConfiguration
springboot 之 tomact 啟動 不用多說一看就明白~
擷取TomactServlerWebServerFactory後,我們看類的getWebServer方法都做了什麼
public WebServer getWebServer(ServletContextInitializer... initializers) {
//終于。。。終于。。。。
Tomcat tomcat = new Tomcat();
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);
//上邊對tomact進行的封裝,再通過調用getTomcatWebServer傳回tomact,我們看一下這個方法
return getTomcatWebServer(tomcat);
}
......
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();
}
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
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);
}
}
}
至此我們已經将tomact的啟動流程介紹完了,當然後續還有還有其他的流程:tomact 的啟動配置、servlet / filter 加載還有一些啟動過程中的事件廣播等不在本文中展開長篇大論,本文旨在梳理流程 ”先脈絡後細節“,也是為了将自己日常的梳理進行一個記錄~