1. 程式人生 > 其它 >main run方法沒用_Springboot之main函式詳解

main run方法沒用_Springboot之main函式詳解

技術標籤:main run方法沒用

此前一直好奇,為什麼Springboot的main函式只有簡單的一行,它的資源載入,bean的載入和初始化究竟是怎麼執行的?今天跟大家一塊分析下原始碼,一探究竟。

如下圖所示:

175c5e8f198765abb90b8676b961b2ca.png

圖1 main函式

如圖所示, 整個初始啟動類中,只有一個@SpringBootApplication的註解,用於標識這個是系統的入口類。它其實是一個組合註解,包含了EnableAutoConfiguration(排除自動configuration註解類或者名字)和ComponentScan(基本包和基本類的掃描)。具體作用是掃描並注入@Configuration 和@Companent修飾的類,到Bean容器中。

main方法中的SpringApplication類用於從main方法中啟動和載入Spring應用,它包括以下幾個步驟去啟動我們的應用程式:

1、建立一個應用上下文例項。這依賴於我們配置的classpath資訊。

2、註冊一個CommandLinePropertySource,以暴露命令列引數作為Spring的屬性。

3、更新應用上下文,以單例的形式載入所有的bean.

4、觸發CommandLineRunner的beans

SpringApplication 可以從各種不同來源載入bean,不過它通常推薦一@Configuration註解的方式去配置一個單例的bean。除此之外,還可以通過設定getSources 方法的方式從:

1、通過AnnotatedBeanDefinitionReader載入完全限定類名。

2、通過XmlBeanDefinitionReader bean載入器載入制定未知的xml源類資訊,或者GroovyBeanDefinitionReader類載入器載入groovy指令碼中的類資訊。

3、通過ClassPathBeanDefinitionScanner 類掃描器掃描制定包下的類。

在該類下,還包含一些比較重要的靜態屬性,如:DEFAULT_CONTEXT_CLASS 預設的上下文類路徑,以及 DEFAULT_SERVLET_WEB_CONTEXT_CLASS預設的servlet web類路徑等。run方法是它的一個靜態方法,用於實際執行spring的初始化和啟動任務。它最終呼叫的是如下方法:

/** * Static helper that can be used to run a {@link SpringApplication} from the * specified sources using default settings and user supplied arguments. * @param primarySources the primary sources to load * @param args the application arguments (usually passed from a Java main method) * @return the running {@link ApplicationContext} */public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}而SpringApplication的初始化函式實際上執行了一些配置檔案的載入、bean的載入和監聽器的配置操作。如下所示:public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass();}

在這段程式碼中,我們重點關注deduceFromClasspath方法和 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class))。

1)deduceFromClasspath的作用是根據classpath資訊推斷當前的web型別。目前springboot的型別一共分為三種: None, Servlet和Reactive(即webflux)。預設是servlet.

2)getSpringFactoriesInstances()方法的作用是呼叫 List loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) 方法,根據工廠類名資訊和類載入器,將bean容器(實質上是一個ConcurrentReferrencedMap)中的類資訊,獲取並存放在一個set集合中,如果沒有獲取

到,則會重新載入一次類路徑下的所有的bean資訊。setListeners()方法,則將容器中載入的所有bean設定為監聽的物件。

3)deduceMainApplicationClass() 方法,則根據我們執行時的棧資訊,推斷我們的main方法所在類資訊。

再回到之前的new SpringApplication(primarySources).run(args)方法,它是我們整個程式的主流程和方法,它的主要作用是建立我們的spring 應用並更新spring容器。 程式碼詳情如下:

public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); 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;}

其中,StopWatch 的作用是監聽每個任務執行的時間資訊。

prepareEnvironment(listeners,applicationArguments)的作用是根據當前程式的web型別,載入相應的環境資訊。如servlet 或者reactive。

prepareContext(context, environment, listeners, applicationArguments,printedBanner) 的作用是初始化容器和上下文環境,然後將其新增到待監聽列表。

refreshContext(context); 它的作用是更新spring的上下文資訊,並新增shutdown鉤子。

afterRefresh(context, applicationArguments);一個沒有任何實現的 protected方法,可以新增一些我們自定義的實現。

至此,springboot 的main方法的執行過程已經全部講完了,由於水平有限,有些地方難免有些紕漏和粗糙,不足之處,希望大家多多指教。

1、