SpringBoot原始碼解析 內建Tomcat啟動流程
開啟原始碼過程略去不談,找到入口方法之後發現有兩次呼叫,而我們實際需要開始關注的是下面這個方法。
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return (new SpringApplication(sources)).run(args);
}
可以看到首先呼叫了有參構造方法,然後呼叫run(String[] args)
進行後續操作。
構造方法中需要關注的是initialize()
方法,其中跟此啟動內建Tomcat相關的則是deduceWebEnvironment()
//專案初始化
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//確定是否是Web環境
this.webEnvironment = this.deduceWebEnvironment();
this .setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
//跟Web環境相關Class
private static final String[] WEB_ENVIRONMENT_CLASSES = new String[]{"javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext"};
//判斷是否web環境 只有陣列中的Class全部存在才會判定為Web環境
private boolean deduceWebEnvironment() {
String[] var1 = WEB_ENVIRONMENT_CLASSES;
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
String className = var1[var3];
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return false;
}
}
return true;
}
構造方法完成之後則是run(String[] args)
方法。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
Banner printedBanner = this.printBanner(environment);
//建立ApplicationContext
context = this.createApplicationContext();
new FailureAnalyzers(context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//重新整理Context
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
listeners.finished(context, (Throwable)null);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
throw new IllegalStateException(var9);
}
}
其中跟啟動內建Tomcat相關的則是createApplicationContext()
與refreshContext(context)
方法,第一個方法大家顧名思義,功能在此不過多解釋。下面是它的具體實現。
//建立AnnotationConfigEmbeddedWebApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment ? "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext" : "org.springframework.context.annotation.AnnotationConfigApplicationContext");
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass);
}
可以看到,其主體是一個基於this.webEnvironment
變數的判斷,此處我們獲取到的ConfigurableApplicationContext
的具體實現是AnnotationConfigEmbeddedWebApplicationContext
。然而Tomcat是在哪裡啟動的呢?接下來讓我們看下refreshContext(context)
的實現。
private void refreshContext(ConfigurableApplicationContext context) {
this.refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException var3) {
;
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext)applicationContext).refresh();
}
可以看到,其首先呼叫的refresh
方法,而在此方法中,applicationContext
被轉形為AbstractApplicationContext
,然後呼叫了其中的refresh()
方法。
開啟原始碼瞄兩眼,原來是個抽象類。嗯,依賴抽象而不是實現。再看下依賴關係,
AnnotationConfigEmbeddedWebApplicationContext
赫然在列。稍微看下refresh()
,其中關鍵是onRefresh()
這個方法。
/**
* Template method which can be overridden to add context-specific refresh work.
* Called on initialization of special beans, before instantiation of singletons.
* <p>This implementation is empty.
* @throws BeansException in case of errors
* @see #refresh()
*/
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
但是我們在AnnotationConfigEmbeddedWebApplicationContext
其實是沒有找到關於onRefresh()
的實現的。那父類呢?我們可以看到其是繼承於EmbeddedWebApplicationContext
的,觀察下其關於onRefresh()
的具體實現。
protected void onRefresh() {
super.onRefresh();
try {
//建立Web容器
this.createEmbeddedServletContainer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start embedded container", var2);
}
}
從命名可以看出,這裡是真正Tomcat開始建立的入口,其實現如下:
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = this.getServletContext();
if (localContainer == null && localServletContext == null) {
EmbeddedServletContainerFactory containerFactory = this.getEmbeddedServletContainerFactory();
this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (localServletContext != null) {
try {
this.getSelfInitializer().onStartup(localServletContext);
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
this.initPropertySources();
}
至此,Spring boot內建ServletContainer啟動流程完成。其流程如下: