首頁 > 軟體

Springboot啟動原理詳細講解

2022-07-18 14:05:16

主啟動類方法:

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

點選進入方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}

先看看new SpringApplication(primarySources)裡做了什麼?

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判斷當前web容器的型別,一般返回SERVLET,標識是web型別
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
//獲取META-INF/spring.factories檔案中以//org.springframework.boot.Bootstrapper和
//org.springframework.boot.BootstrapRegistryInitializer為key的class
//建立物件,然後裝入物件屬性中;
   this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
   setInitializers((Collection) 
//獲取META-INF/spring.factories檔案中以
//org.springframework.context.ApplicationContextInitializer的key的對
//存入到initializers集合屬性中;
getSpringFactoriesInstances(ApplicationContextInitializer.class));
//獲取META-INF/spring.factories檔案中以
//org.springframework.context.ApplicationListener的key的對
//存入到listeners集合屬性中;
   setListeners((Collection) 
getSpringFactoriesInstances(ApplicationListener.class));
//找到main方法的啟動主類class物件賦值到物件屬性中。
   this.mainApplicationClass = deduceMainApplicationClass();
}

spring.factories讀取了對外擴充套件的ApplicationContextInitializer ,ApplicationListener 對外擴充套件, 對類解耦(比如全域性組態檔、熱部署外掛)

所以我們可以利用這一特性,在容器載入的各個階段進行擴充套件。

上面讀取到的物件getSpringFactoriesInstances(ApplicationContextInitializer.class))

存入到initializers集合中的物件

getSpringFactoriesInstances(ApplicationListener.class));存入到listeners集合屬性中的物件

再看一下最重要的run方法:

public ConfigurableApplicationContext run(String... args) {
// 用來記錄當前springboot啟動耗時 
   StopWatch stopWatch = new StopWatch();
// 就是記錄了啟動開始時間 
   stopWatch.start();
//建立DefaultBootstrapContext bootstrapContext = new 
//DefaultBootstrapContext();物件,然後迴圈執行上面檔案中賦值的bootstrapRegistryInitializers集合中物件的方法,入參就是bootstrapContext物件,利用此處我們可以擴充套件改變bootstrapContext物件中的一項屬性值等,在這裡還不知道此bootstrapContext物件的用處。
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 它是任何spring上下文的介面, 所以可以接收任何ApplicationContext實現 
   ConfigurableApplicationContext context = null;
// 開啟了Headless模式,暫時不知道此模式的作用 
   configureHeadlessProperty();
//去spring.factroies中讀取了 org.springframework.boot.SpringApplicationRunListener為key的物件,預設是EventPublishingRunListener物件,然後封裝進SpringApplicationRunListeners 物件中,此物件還是比較有用的,用來發布springboot啟動進行中的各個狀態的事件,上面方法中讀取到的監聽器就可以監聽到這些事件,所以可以運用這些特性進行自己的擴充套件。
   SpringApplicationRunListeners listeners = getRunListeners(args);
// 釋出1.ApplicationStartingEvent事件,在執行開始時傳送 ,傳送springboot啟動開始事件;
   listeners.starting(bootstrapContext, this.mainApplicationClass);
   try {
//根據啟動專案命令所帶的引數建立applicationArguments 物件
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//初始化環境變數:讀取系統環境變數、傳送了一個ApplicationEnvironmentPreparedEvent事件,利用相關監聽器來解析專案中組態檔中的設定,(EnvironmentPostProcessorApplicationListener
監聽器解析的組態檔)
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 忽略beaninfo的bean 
      configureIgnoreBeanInfo(environment);
// 列印Banner 橫幅 
      Banner printedBanner = printBanner(environment);
//根據webApplicationType 容器型別,建立對應的spring上下文,一般是AnnotationConfigServletWebServerApplicationContext;
      context = createApplicationContext();
//給spring上下文賦值DefaultApplicationStartup物件
      context.setApplicationStartup(this.applicationStartup);
//預初始化上下文,這裡做了給上下文新增environment物件,上面獲取到的initializers集合中的ApplicationContextInitializer物件執行其入參為上下文initialize方法,對上下文做編輯,所以此處我們可以做擴充套件;傳送ApplicationContextInitializedEvent容器初始化事件;傳送BootstrapContextClosedEvent事件;最重要的一個方法就是把啟動設定類註冊成了beanDefinition;傳送ApplicationPreparedEvent事件,並把listeners集合屬性中的事件新增到上下文中;
	prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
	//重新整理容器:和spring中的refresh()方法的作用是一樣的,主要的作用就是讀取所有的bean轉成beanDefinition然後再建立bean物件;不過這個容器重寫了其中的onRefresh()方法,在此方法中,建立了springboot內建的tomcat物件並進行啟動;接下來特別說明一下這個內建tomcat
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
//列印啟動時間;
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
//釋出容器啟動事件ApplicationStartedEvent;釋出AvailabilityChangeEvent容器可用實踐;
      listeners.started(context);
//執行容器中ApplicationRunner、ApplicationRunner型別物件的run方法
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
   }
   try {
//釋出ApplicationReadyEvent容器已經正常事件;
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

prepareEnvironment方法

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   // 根據webApplicationType 建立Environment  建立就會讀取: java環境變數和系統環境變數
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   // 將命令列引數讀取環境變數中
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   // 將@PropertieSource的設定資訊 放在第一位, 因為讀取組態檔@PropertieSource優先順序是最低的
   ConfigurationPropertySources.attach(environment);
   // 釋出了ApplicationEnvironmentPreparedEvent 的監聽器  讀取了全域性組態檔
   listeners.environmentPrepared(environment);
   // 將所有spring.main 開頭的設定資訊系結SpringApplication
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }
   //更新PropertySources
   ConfigurationPropertySources.attach(environment);
   return environment;
}

lprepareContext

l預初始化上下文

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
   // 拿到之前讀取到所有ApplicationContextInitializer的元件, 迴圈呼叫initialize方法
   applyInitializers(context);
   // 釋出了ApplicationContextInitializedEvent
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // 獲取當前spring上下文beanFactory (負責建立bean)
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   // 在Spring下 如果出現2個重名的bean, 則後讀取到的會覆蓋前面
   // 在SpringBoot 在這裡設定了不允許覆蓋, 當出現2個重名的bean 會丟擲異常
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   // 設定當前spring容器是不是要將所有的bean設定為懶載入
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   // Load the sources
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   // 讀取主啟動類,將它註冊為BD、就像我們以前register(啟動類);一個意思 (因為後續要根據設定類解析設定的所有bean)
   load(context, sources.toArray(new Object[0]));
   //4.讀取完設定類後傳送ApplicationPreparedEvent。
   listeners.contextLoaded(context);
}

1.初始化SpringApplication 從spring.factories 讀取 listener ApplicationContextInitializer 。

2.執行run方法

3.讀取 環境變數 設定資訊…

4.建立springApplication上下文:ServletWebServerApplicationContext

5.預初始化上下文 : 讀取啟動類

6.呼叫refresh 載入ioc容器

7.載入所有的自動設定類

8.建立servlet容器

9.ps.在這個過程中springboot會呼叫很多監聽器對外進行擴充套件

看一下內建tomcat如何啟動的:

Springboot的spring容器ServletWebServerApplicationContext物件重新了refresh()方法中的onRefresh()放用來啟動tomcat

@Override
protected void onRefresh() {
   super.onRefresh();
   try {
//建立tomcat
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}

createWebServer建立tomcat的方法(也可以是Jetty,根據設定)

private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
//如果servletContext 為null說明是內建tomcat,不為null,則使用的是外接tomcat,這個servletContext 是tomcat建立傳入的;
   if (webServer == null && servletContext == null) {
      StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
//獲取servlet容器建立工廠,tomcat的是TomcatServletWebServerFactory
      ServletWebServerFactory factory = getWebServerFactory();
      createWebServer.tag("factory", factory.getClass().toString());
//建立內建tomcat物件,並啟動;getSelfInitializer()這個方法也是很重要的,返回的是ServletWebServerApplicationContext#selfInitialize方法的參照函數,其作用是tomcat啟動時會回撥此方法,並傳入servletContext物件,進行DispacherServlet新增到servletContext中,把當前spring容器和filter過濾器也新增到servletContext中
      this.webServer = factory.getWebServer(getSelfInitializer());
      createWebServer.end();
      getBeanFactory().registerSingleton("webServerGracefulShutdown",
            new WebServerGracefulShutdownLifecycle(this.webServer));
      getBeanFactory().registerSingleton("webServerStartStop",
            new WebServerStartStopLifecycle(this, this.webServer));
   }
   else if (servletContext != null) {
      try {
         getSelfInitializer().onStartup(servletContext);
      }
      catch (ServletException ex) {
         throw new ApplicationContextException("Cannot initialize servlet context", ex);
      }
   }
   initPropertySources();
}

selfInitializ方法

private void selfInitialize(ServletContext servletContext) throws ServletException {
//把spring容器和servletContext進行相互參照
   prepareWebApplicationContext(servletContext);
   registerApplicationScope(servletContext);
   WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
//servletContext中新增Dispacherservlet和多個fileter
   for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
      beans.onStartup(servletContext);
   }
}

到此這篇關於Springboot啟動原理詳細講解的文章就介紹到這了,更多相關Springboot啟動原理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com