偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

為啥一個(gè) Main 方法就能啟動(dòng)項(xiàng)目

開(kāi)發(fā) 架構(gòu)
在 IDE 中也需要對(duì) Web 容器進(jìn)行一些配置,才能夠運(yùn)行或者 Debug。而使用 Spring Boot 我們只需要像運(yùn)行普通 JavaSE 程序一樣,run 一下 main() 方法就可以啟動(dòng)一個(gè) Web 應(yīng)用了。這是怎么做到的呢?今天我們就一探究竟,分析一下 Spring Boot 的啟動(dòng)流程。

在 Spring Boot 出現(xiàn)之前,我們要運(yùn)行一個(gè) Java Web 應(yīng)用,首先需要有一個(gè) Web 容器(例如 Tomcat 或 Jetty),然后將我們的 Web 應(yīng)用打包后放到容器的相應(yīng)目錄下,最后再啟動(dòng)容器。

在 IDE 中也需要對(duì) Web 容器進(jìn)行一些配置,才能夠運(yùn)行或者 Debug。而使用 Spring Boot 我們只需要像運(yùn)行普通 JavaSE 程序一樣,run 一下 main() 方法就可以啟動(dòng)一個(gè) Web 應(yīng)用了。這是怎么做到的呢?今天我們就一探究竟,分析一下 Spring Boot 的啟動(dòng)流程。

概覽

回看我們寫的第一個(gè) Spring Boot 示例,我們發(fā)現(xiàn),只需要下面幾行代碼我們就可以跑起一個(gè) Web 服務(wù)器:

@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}

去掉類的聲明和方法定義這些樣板代碼,核心代碼就只有一個(gè) @SpringBootApplication 注解和 SpringApplication.run(HelloApplication.class, args) 了。而我們知道注解相當(dāng)于是一種配置,那么這個(gè) run() 方法必然就是 Spring Boot 的啟動(dòng)入口了。

接下來(lái),我們沿著 run() 方法來(lái)順藤摸瓜。進(jìn)入 SpringApplication 類,來(lái)看看 run() 方法的具體實(shí)現(xiàn):

public class SpringApplication {
......
public ConfigurableApplicationContext run(String... args) {
// 1 應(yīng)用啟動(dòng)計(jì)時(shí)開(kāi)始
StopWatch stopWatch = new StopWatch();
stopWatch.start();

// 2 聲明上下文
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;

// 3 設(shè)置 java.awt.headless 屬性
configureHeadlessProperty();

// 4 啟動(dòng)監(jiān)聽(tīng)器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 5 初始化默認(rèn)應(yīng)用參數(shù)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

// 6 準(zhǔn)備應(yīng)用環(huán)境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);

// 7 打印 Banner(Spring Boot 的 LOGO)
Banner printedBanner = printBanner(environment);

// 8 創(chuàng)建上下文實(shí)例
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);

// 9 構(gòu)建上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

// 10 刷新上下文
refreshContext(context);

// 11 刷新上下文后處理
afterRefresh(context, applicationArguments);

// 12 應(yīng)用啟動(dòng)計(jì)時(shí)結(jié)束
stopWatch.stop();
if (this.logStartupInfo) {
// 13 打印啟動(dòng)時(shí)間日志
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}

// 14 發(fā)布上下文啟動(dòng)完成事件
listeners.started(context);

// 15 調(diào)用 runners
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 16 應(yīng)用啟動(dòng)發(fā)生異常后的處理
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}


try {
// 17 發(fā)布上下文就緒事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
......
}

Spring Boot 啟動(dòng)時(shí)做的所有操作都這這個(gè)方法里面,當(dāng)然在調(diào)用上面這個(gè) run() 方法之前,還創(chuàng)建了一個(gè) SpringApplication 的實(shí)例對(duì)象。因?yàn)樯厦孢@個(gè) run() 方法并不是一個(gè)靜態(tài)方法,所以需要一個(gè)對(duì)象實(shí)例才能被調(diào)用。

可以看到,方法的返回值類型為
ConfigurableApplicationContext,這是一個(gè)接口,我們真正得到的是 AnnotationConfigServletWebServerApplicationContext 的實(shí)例。通過(guò)類名我們可以知道,這是一個(gè)基于注解的 Servlet Web 應(yīng)用上下文(我們知道上下文(context)是 Spring 中的核心概念)。

上面對(duì)于 run() 方法中的每一個(gè)步驟都做了簡(jiǎn)單的注釋,接下來(lái)我們選擇幾個(gè)比較有代表性的來(lái)詳細(xì)分析。

應(yīng)用啟動(dòng)計(jì)時(shí)

在 Spring Boot 應(yīng)用啟動(dòng)完成時(shí),我們經(jīng)常會(huì)看到類似下面內(nèi)容的一條日志:

Started AopApplication in 2.732 seconds (JVM running for 3.734)

應(yīng)用啟動(dòng)后,會(huì)將本次啟動(dòng)所花費(fèi)的時(shí)間打印出來(lái),讓我們對(duì)于啟動(dòng)的速度有一個(gè)大致的了解,也方便我們對(duì)其進(jìn)行優(yōu)化。記錄啟動(dòng)時(shí)間的工作是 run() 方法做的第一件事,在編號(hào) 1 的位置由 stopWatch.start() 開(kāi)啟時(shí)間統(tǒng)計(jì),具體代碼如下:

public void start(String taskName) throws IllegalStateException {
if (this.currentTaskName != null) {
throw new IllegalStateException("Can't start StopWatch: it's already running");
}
// 記錄啟動(dòng)時(shí)間
this.currentTaskName = taskName;
this.startTimeNanos = System.nanoTime();
}

然后到了 run() 方法的基本任務(wù)完成的時(shí)候,由 stopWatch.stop()(編號(hào) 12 的位置)對(duì)啟動(dòng)時(shí)間做了一個(gè)計(jì)算,源碼也很簡(jiǎn)單:

public void stop() throws IllegalStateException {
if (this.currentTaskName == null) {
throw new IllegalStateException("Can't stop StopWatch: it's not running");
}
// 計(jì)算啟動(dòng)時(shí)間
long lastTime = System.nanoTime() - this.startTimeNanos;
this.totalTimeNanos += lastTime;
......
}

最后,在 run() 中的編號(hào) 13 的位置將啟動(dòng)時(shí)間打印出來(lái):

if (this.logStartupInfo) {
// 打印啟動(dòng)時(shí)間
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}

打印 Banner

Spring Boot 每次啟動(dòng)是還會(huì)打印一個(gè)自己的 LOGO,如圖 8-6:

圖 8-6 Spring Boot Logo

這種做法很常見(jiàn),像 Redis、Docker 等都會(huì)在啟動(dòng)的時(shí)候?qū)⒆约旱?LOGO 打印出來(lái)。Spring Boot 默認(rèn)情況下會(huì)打印那個(gè)標(biāo)志性的“樹(shù)葉”和 “Spring” 的字樣,下面帶著當(dāng)前的版本。

在 run() 中編號(hào) 7 的位置調(diào)用打印 Banner 的邏輯,最終由 SpringBootBanner 類的 printBanner() 完成。這個(gè)圖案定義在一個(gè)常量數(shù)組中,代碼如下:

class SpringBootBanner implements Banner {
private static final String[] BANNER = {
"",
" . ____ _ __ _ _",
" /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\",
"( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
" \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )",
" ' |____| .__|_| |_|_| |_\\__, | / / / /",
" =========|_|==============|___/=/_/_/_/"
};
......

public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
for (String line : BANNER) {
printStream.println(line);
}
......
}
}

手工格式化了一下 BANNER 的字符串,輪廓已經(jīng)清晰可見(jiàn)了。真正打印的邏輯就是 printBanner() 方法里面的那個(gè) for 循環(huán)。

記錄啟動(dòng)時(shí)間和打印 Banner 代碼都非常的簡(jiǎn)單,而且都有很明顯的視覺(jué)反饋,可以清晰的看到結(jié)果。拿出來(lái)咱們做個(gè)熱身,配合斷點(diǎn)去 Debug 會(huì)有更加直觀的感受,尤其是打印 Banner 的時(shí)候,可以看到整個(gè)內(nèi)容被一行一行打印出來(lái),讓我想起了早些年用那些配置極低的電腦(還是 CRT 顯示器)運(yùn)行著 Win98,經(jīng)常會(huì)看到屏幕內(nèi)容一行一行加載顯示。

創(chuàng)建上下文實(shí)例

下面我們來(lái)到 run() 方法中編號(hào) 8 的位置,這里調(diào)用了一個(gè) createApplicationContext() 方法,該方法最終會(huì)調(diào)用 ApplicationContextFactory 接口的代碼:

ApplicationContextFactory DEFAULT = (webApplicationType) -> {
try {
switch (webApplicationType) {
case SERVLET:
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
return new AnnotationConfigApplicationContext();
}
}
catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
};

這個(gè)方法就是根據(jù) SpringBootApplication 的 webApplicationType 屬性的值,利用反射來(lái)創(chuàng)建不同類型的應(yīng)用上下文(context)。而屬性 webApplicationType 的值是在前面執(zhí)行構(gòu)造方法的時(shí)候由
WebApplicationType.deduceFromClasspath() 獲得的。通過(guò)方法名很容易看出來(lái),就是根據(jù) classpath 中的類來(lái)推斷當(dāng)前的應(yīng)用類型。

我們這里是一個(gè)普通的 Web 應(yīng)用,所以最終返回的類型為 SERVLET。所以會(huì)返回一個(gè)
AnnotationConfigServletWebServerApplicationContext 實(shí)例。

構(gòu)建容器上下文

接著我們來(lái)到 run() 方法編號(hào) 9 的 prepareContext() 方法。通過(guò)方法名,我們也能猜到它是為 context 做上臺(tái)前的準(zhǔn)備工作的。

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
......
// 加載資源
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}

在這個(gè)方法中,會(huì)做一些準(zhǔn)備工作,包括初始化容器上下文、設(shè)置環(huán)境、加載資源等。

加載資源

上面的代碼中,又調(diào)用了一個(gè)很關(guān)鍵的方法——load()。這個(gè) load() 方法真正的作用是去調(diào)用 BeanDefinitionLoader 類的 load() 方法。源碼如下:

class BeanDefinitionLoader {
......
void load() {
for (Object source : this.sources) {
load(source);
}
}
private void load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class<?>) {
load((Class<?>) source);
return;
}
if (source instanceof Resource) {
load((Resource) source);
return;
}
if (source instanceof Package) {
load((Package) source);
return;
}
if (source instanceof CharSequence) {
load((CharSequence) source);
return;
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
......
}

可以看到,load() 方法在加載 Spring 中各種資源。其中我們最熟悉的就是 load((Class<?>) source) 和 load((Package) source) 了。一個(gè)用來(lái)加載類,一個(gè)用來(lái)加載掃描的包。

load((Class<?>) source) 中會(huì)通過(guò)調(diào)用 isComponent() 方法來(lái)判斷資源是否為 Spring 容器管理的組件。isComponent() 方法通過(guò)資源是否包含 @Component 注解(@Controller、@Service、@Repository 等都包含在內(nèi))來(lái)區(qū)分是否為 Spring 容器管理的組件。

而 load((Package) source) 方法則是用來(lái)加載 @ComponentScan 注解定義的包路徑。

刷新上下文

run() 方法編號(hào)10 的 refreshContext() 方法是整個(gè)啟動(dòng)過(guò)程比較核心的地方。像我們熟悉的 BeanFactory 就是在這個(gè)階段構(gòu)建的,所有非懶加載的 Spring Bean(@Controller、@Service 等)也是在這個(gè)階段被創(chuàng)建的,還有 Spring Boot 內(nèi)嵌的 Web 容器要是在這個(gè)時(shí)候啟動(dòng)的。

跟蹤源碼你會(huì)發(fā)現(xiàn)內(nèi)部調(diào)用的是
ConfigurableApplicationContext.refresh(),ConfigurableApplicationContext 是一個(gè)接口,真正實(shí)現(xiàn)這個(gè)方法的有三個(gè)類:AbstractApplicationContext、ReactiveWebServerApplicationContext 和 ServletWebServerApplicationContext。

AbstractApplicationContext 為后面兩個(gè)的父類,兩個(gè)子類的實(shí)現(xiàn)比較簡(jiǎn)單,主要是調(diào)用父類實(shí)現(xiàn),比如 ServletWebServerApplicationContext 中的實(shí)現(xiàn)是這樣的:

public final void refresh() throws BeansException, IllegalStateException {
try {
super.refresh();
}
catch (RuntimeException ex) {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.stop();
}
throw ex;
}
}

主要的邏輯都在AbstractApplicationContext 中:

@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 1 準(zhǔn)備將要刷新的上下文
prepareRefresh();
// 2 (告訴子類,如:ServletWebServerApplicationContext)刷新內(nèi)部 bean 工廠
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3 為上下文準(zhǔn)備 bean 工廠
prepareBeanFactory(beanFactory);
try {
// 4 允許在子類中對(duì) bean 工廠進(jìn)行后處理
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 5 調(diào)用注冊(cè)為 bean 的工廠處理器
invokeBeanFactoryPostProcessors(beanFactory);
// 6 注冊(cè)攔截器創(chuàng)建的 bean 處理器
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 7 初始化國(guó)際化相關(guān)資源
initMessageSource();
// 8 初始化事件廣播器
initApplicationEventMulticaster();
// 9 為具體的上下文子類初始化特定的 bean
onRefresh();
// 10 注冊(cè)監(jiān)聽(tīng)器
registerListeners();
// 11 實(shí)例化所有非懶加載的單例 bean
finishBeanFactoryInitialization(beanFactory);
// 12 完成刷新發(fā)布相應(yīng)的事件(Tomcat 就是在這里啟動(dòng)的)
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 遇到異常銷毀已經(jīng)創(chuàng)建的單例 bean
destroyBeans();
// 充值 active 標(biāo)識(shí)
cancelRefresh(ex);
// 將異常向上拋出
throw ex;
} finally {
// 重置公共緩存,結(jié)束刷新
resetCommonCaches();
contextRefresh.end();
}
}
}

簡(jiǎn)單說(shuō)一下編號(hào) 9 處的 onRefresh() 方法,該方法父類未給出具體實(shí)現(xiàn),需要子類自己實(shí)現(xiàn),ServletWebServerApplicationContext 中的實(shí)現(xiàn)如下:

protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
......
if (webServer == null && servletContext == null) {
......
// 根據(jù)配置獲取一個(gè) web server(Tomcat、Jetty 或 Undertow)
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
......
}
......
}

factory.getWebServer(getSelfInitializer()) 會(huì)根據(jù)項(xiàng)目配置得到一個(gè) Web Server 實(shí)例,這里跟下一篇將要談到的自動(dòng)配置有點(diǎn)關(guān)系。

責(zé)任編輯:姜華 來(lái)源: 今日頭條
相關(guān)推薦

2011-09-19 16:47:18

vista

2021-10-19 18:22:50

Map 注冊(cè)表源碼

2024-02-19 00:21:45

開(kāi)源圖片

2021-11-23 23:01:40

Windows微軟系統(tǒng)

2019-05-27 08:29:32

啟動(dòng)項(xiàng)目PMP

2020-08-17 15:25:25

HTMLPython網(wǎng)頁(yè)

2014-03-31 10:20:12

2021-04-08 09:49:49

MySQL索引數(shù)據(jù)庫(kù)

2019-12-24 11:00:51

FedoraLive CD系統(tǒng)運(yùn)維

2015-07-29 10:00:16

開(kāi)源項(xiàng)目

2017-06-20 14:29:12

Rec開(kāi)源項(xiàng)目

2014-10-21 10:25:50

程序員

2019-04-30 09:05:16

項(xiàng)目啟動(dòng)PMP

2013-03-08 02:52:03

個(gè)人開(kāi)發(fā)項(xiàng)目糾錯(cuò)

2016-01-05 13:52:05

Kotlin掌握語(yǔ)言

2018-10-28 17:28:48

2009-05-08 09:32:27

JavaWeb編程框架

2012-11-29 09:49:17

軟件項(xiàng)目項(xiàng)目

2022-06-14 10:47:27

項(xiàng)目日志PUT

2011-08-25 09:03:40

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)