Springboot簡介
相信大多數(shù)開發(fā)者對Springboot比較熟悉了,它能夠快速地創(chuàng)建一個spring應用怎诫,能夠完全摒棄XML的配置方式虐唠,并且內(nèi)嵌了Tomcat、Jetty這樣的Servlet容器少态,即無需再將應用打包成war部署城侧。
在Springboot之前,部署一個應用如下
而現(xiàn)在彼妻,由于Springboot內(nèi)嵌了Servlet容器嫌佑,于是可以將應用打包成jar,直接運行一個jar包就能啟動一個web服務侨歉。
Springboot是如何做到的呢屋摇?接下來進入今天的正題
Tomcat-embed
Springboot能夠?qū)omcat內(nèi)嵌,是因為Tomcat提供了一套JavaAPI幽邓,能夠通過Tomcat tomcat = new Tomcat()來創(chuàng)建一個Tomcat容器炮温。
只需要引入Maven依賴
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
接下來我們通過Springboot源碼來看看,spring是如何使用這套API與自身結(jié)合的
Springboot源碼解讀
首先牵舵,任意一個Springboot應用柒啤,都有一個main()函數(shù)作為應用的啟動方法倦挂,里面調(diào)用了SpringApplication.run(MyApplication.class, args),我們就從這個run()開始白修,解密spring容器如何啟動Tomcat妒峦。
這個run()的實現(xiàn)代碼如下,這里去掉了一些與主線邏輯無關(guān)的代碼
/**
* Run the Spring application, creating and refreshing a new
* {@linkApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
ConfigurableApplicationContext context = null;
// 創(chuàng)建spring容器對象 ApplicationContext
context = createApplicationContext();
// 做一些初始化容器之前的預備操作
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 啟動spring容器核心方法兵睛,包括啟動tomcat
refreshContext(context);
// 做一些容器啟動之后的操作(當前這個方法是空的)
afterRefresh(context, applicationArguments);
return context;
}
看過Spring源碼的同學應該清楚肯骇,啟動spring容器的核心方法就是refresh(),而這里的方法名叫refreshContext(context)祖很,不過是springboot對refresh()的封裝罷了笛丙,所以內(nèi)部依然是調(diào)用的refresh(),于是我們來到這個核心方法內(nèi)部
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 在容器啟動之前做一些準備操作
prepareRefresh();
// 通知子類去刷新實例工廠
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 初始化當前容器中的實例工廠
prepareBeanFactory(beanFactory);
try {
// 允許容器中的基類對實例工廠進行后置處理
postProcessBeanFactory(beanFactory);
// 調(diào)用實例工廠中的后置處理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注冊實例的后置處理器假颇,在創(chuàng)建實例時進行攔截
registerBeanPostProcessors(beanFactory);
// 初始化消息資源
initMessageSource();
// 初始化容器中的事件處理器
initApplicationEventMulticaster();
// 初始化一些在特定容器中特殊的實例
onRefresh();
// 檢查監(jiān)聽器的實例并注冊它們
registerListeners();
// 實例化所有非懶加載的實例
finishBeanFactoryInitialization(beanFactory);
// 最后一步:發(fā)布一些響應事件
finishRefresh();
}
catch (BeansException ex) {
...
}
finally {
...
}
}
}
當這個方法調(diào)用完胚鸯,spring容器就基本完成了初始化過程,tomcat當然也是在這個方法內(nèi)部完成的創(chuàng)建
創(chuàng)建WebServer容器
Springboot內(nèi)嵌的各種web容器實例笨鸡,都是在onRefresh()中進行創(chuàng)建的姜钳。
查看方法實現(xiàn)可以發(fā)現(xiàn)這個方法是個空方法
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
但是其子類的都實現(xiàn)了這個方法,子類列表如下
因為Tomcat是一個Servlet容器形耗,所以我們直接看ServletWebServerApplicationContext這個類中是如何實現(xiàn)的
進入這個類中哥桥,該方法的實現(xiàn)代碼比較容易看懂,重點在createWebServer()激涤,這里同樣只保留了主要代碼
@Override
protected void onRefresh() {
super.onRefresh();
createWebServer();
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 通過工廠創(chuàng)建WebServer實例
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
}
前面1-5步都是在配置Tomcat拟糕,而完成Tomcat是在第6步getTomcatWebServer(tomcat)完成的,我們接著進去看看
getTomcatWebServer(tomcat)方法返回一個TomcatWebServer對象
TomcatWebServer對象是springboot對Tomcat對象的封裝倦踢,內(nèi)部存了tomcat實例的引用送滞,這里執(zhí)行的是TomcatWebServer的構(gòu)造方法,
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
構(gòu)造方法內(nèi)部如下
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
我們主要來看initialize()辱挥,在這個方法內(nèi)部會調(diào)用tomcat.start()
private void initialize() throws WebServerException {
synchronized (this.monitor) {
try {
// 給Engine命名
addInstanceIdToEngineName();
// 獲取Host中的Context
Context context = findContext();
// 綁定Context的生命周期監(jiān)聽器
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
removeServiceConnectors();
}
});
// 啟動tomcat犁嗅,觸發(fā)初始化監(jiān)聽器
this.tomcat.start();
// 啟動過程中子線程的異常從主線程拋出
rethrowDeferredStartupExceptions();
// 給當前Context綁定類加載器
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
// tomcat的所有線程都是守護線程,這里啟動一個阻塞的非守護線程來確保tomcat能及時停止
startDaemonAwaitThread();
}
catch (Exception ex) {
...
}
}
}
要注意的是這個方法啟動的是tomcat工作需要的一些守護線程晤碘,并非是web服務愧哟;我們可以來看看start()的定義
/**
* Prepare for the beginning of active use of the public methods other than
* property getters/setters and life cycle methods of this component. This
* method should be called before any of the public methods other than
* property getters/setters and life cycle methods of this component are
* utilized. The following {@link LifecycleEvent}s will be fired in the
* following order:
* <ol>
* <li>BEFORE_START_EVENT: At the beginning of the method. It is as this
* point the state transitions to
* {@link LifecycleState#STARTING_PREP}.</li>
* <li>START_EVENT: During the method once it is safe to call start() for
* any child components. It is at this point that the
* state transitions to {@link LifecycleState#STARTING}
* and that the public methods other than property
* getters/setters and life cycle methods may be
* used.</li>
* <li>AFTER_START_EVENT: At the end of the method, immediately before it
* returns. It is at this point that the state
* transitions to {@link LifecycleState#STARTED}.
* </li>
* </ol>
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
public void start() throws LifecycleException;
啟動Web服務
真正完成springboot啟動的方法,依然是由TomcatWebServer這個類完成的哼蛆,這個類封裝了控制整個web服務聲明周期的方法蕊梧,比如initialize(), start(), stop()等等,而這個start()顯然就是web服務啟動的方法了腮介。
@Override
public void start() throws WebServerException {
synchronized (this.monitor) {
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
performDeferredLoadOnStartup();
}
logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
+ getContextPath() + "'");
}
}
看到這個方法里的這行l(wèi)og肥矢,大家應該很熟悉了,說明最終啟動web容器的函數(shù)就是performDeferredLoadOnStartup()
performDeferredLoadOnStartup()方法內(nèi)部邏輯如下
private void performDeferredLoadOnStartup() {
for (Container child : this.tomcat.getHost().findChildren()) {
if (child instanceof TomcatEmbeddedContext) {
((TomcatEmbeddedContext) child).deferredLoadOnStartup();
}
}
}
public void deferredLoadOnStartup() throws LifecycleException {
doWithThreadContextClassLoader(getLoader().getClassLoader(),
() -> getLoadOnStartupWrappers(findChildren()).forEach(this::load));
}
getLoadOnStartupWrappers()負責將Wrapper按照loadOnStartup的優(yōu)先級進行重排序,這里的主要邏輯就是按照Wrapper的優(yōu)先級甘改,依次調(diào)用wrapper.load()旅东,然后打印"Tomcat started on port …"完成web容器啟動。
最后十艾,還有一個問題未解答抵代,就是start()是何時調(diào)用的?
start()何時調(diào)用
要解答這個問題忘嫉,可以通過觀察springboot的啟動日志荤牍,看"Tomcat started on port …"這行日志是何時打印的。
按照這個方法庆冕,我們不難發(fā)現(xiàn)康吵,這行日志仍然是在spring容器的refresh()里打印出來的,繼續(xù)深入最終是鎖定了refresh()里的最后一步访递,也就是finishRefresh()
ServletWebServerApplicationContext重寫了finishRefresh()
@Override
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.start();
}
return webServer;
}
上面方法中的WebServer是一個接口晦嵌,里面有三個方法start(), stop(), getPort() TomcatWebServer對其進行了實現(xiàn),其中的start()就是在這里調(diào)用的拷姿,這里也就是最終啟動web服務的地方惭载。
至此,Springboot內(nèi)嵌web服務器的秘密解開了响巢。
總結(jié)
Spring框架基于接口的設計模式描滔,使得整個框架具有良好的擴展性。我們再來回顧下spring是如何整合tomcat的抵乓。
首先伴挚,通過繼承AbstractApplicationContext靶衍,重寫onRefresh()對web容器進行初始化灾炭,重寫finishRefresh()啟動web服務。
其次颅眶,spring抽象了WebServer接口蜈出,提供了“啟動”和“停止”兩個基本方法,具體方法由不同的web容器各自實現(xiàn)涛酗,其中tomcat的實現(xiàn)類叫TomcatWebServer铡原。
最后,TomcatWebServer在構(gòu)造方法中調(diào)用initialize()初始化tomcat商叹,調(diào)用start()方法啟動web服務燕刻,在spring容器銷毀之前調(diào)用stop()完成tomcat生命周期。