springboot啓動流程源碼分析(一)

前言:springboot相信基本上所有的人都使用過,但是對於一些初學者可能只是知道如何使用,但是對於它實現的原理不太熟悉,今天跟大家一起去分析下它的啓動源碼。其實也是比較簡單,相信通過這篇文章能對一些初學者有一些幫助,在學習這篇文章之前最好有spring的基礎知識。

一、引入問題

今天我們是以web應用爲例來分析springboot的啓動原理,首先我們看如下的代碼

@SpringBootApplication
public class HellobootApplication {

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

}

我們知道當我們run這個main方法時,web應用程序就能啓動。那麼首先請大家思考如下問題

1、springboot如何和spring容器關聯上
2、我們沒有看到有tomcat容器,爲什麼能夠支撐起web應用
3、當我們引用第三方的starter時,爲什麼會自動實例化一些類,我們並沒有掃描到第三方的包,甚至我們對第三方的包的路徑都不知道

帶着上面三個問題,我們一起來看下springboot的啓動的原理吧

二、源碼分析(1)

首先分析上面第一個問題(springboot如何和spring容器關聯上)

SpringApplication#run()

public static ConfigurableApplicationContext run(Class<?> primarySource,
      String... args) {
    //看這裏
   return run(new Class<?>[] { primarySource }, args);
}

run()

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
      String[] args) {
    //看這個run方法
   return new SpringApplication(primarySources).run(args);
}

繼續記者往下看

run()

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> 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);
       //從這個方法名字看,大概就能猜測出來意思了,這裏就是創建spring的context
      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;
}

這個方法,我們重點看兩個地方

(1)createApplicationContext()

(2)refreshContext(context)

如果對spring源碼有了解的話,我們大概能猜到這兩個方法的意思,第一個時創建spring上下文,第二個就是刷新上下文,這兩個地方其實就實現了springboot–>spring容器

比如我們單元測試的時候,經常編寫如下代碼進行啓動spring容器

ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("xxx.xml")

1、我們先看createApplicationContext()方法

protected ConfigurableApplicationContext createApplicationContext() {
   Class<?> contextClass = this.applicationContextClass;
   if (contextClass == null) {
      try {
         switch (this.webApplicationType) {
         case SERVLET:
            //重點看加載的這個class的路徑
            contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
            break;
         case REACTIVE:
            contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
            break;
         default:
            contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
         }
      }
      catch (ClassNotFoundException ex) {
         throw new IllegalStateException(
               "Unable create a default ApplicationContext, "
                     + "please specify an ApplicationContextClass",
               ex);
      }
   }
   //後面就是通過反射創建ApplicationContext上下文了,後面不再繼續跟了
   return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

這裏我們重點看一下加載的是哪個ApplicationContext的上下文,因爲ApplicationContext有很多子類,我們看下常量(DEFAULT_SERVLET_WEB_CONTEXT_CLASS)對應的class是哪個

/**
 * The class name of application context that will be used by default for web
 * environments.
 */
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
      + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

所以這裏創建的ApplicationContext的上下文是AnnotationConfigServletWebServerApplicationContext,這裏很重要,大家先記一下(不同的ApplicationContext有些方法實現的細節是不一樣的)

2、接着看上面的refreshContext(context)

private void refreshContext(ConfigurableApplicationContext context) {
   //繼續看該方法
    refresh(context);
   if (this.registerShutdownHook) {
      try {
         context.registerShutdownHook();
      }
      catch (AccessControlException ex) {
         // Not allowed in some environments.
      }
   }
}

refresh()

protected void refresh(ApplicationContext applicationContext) {
   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
   //這裏就調用了spring的context包中的容器刷新了
   ((AbstractApplicationContext) applicationContext).refresh();
}

分析到這裏,相信大家都知道springboot如何和spring容器相關聯的了

三、源碼分析(2)

上面介紹了springboot如何與spring容器進行關聯的,接着我們看web應用時,我們沒有將應用放入tomcat中時,爲何能實現web應用

上面有一個地方,我讓大家重點記住創建ApplicationContext時,到底是創建了哪個ApplicationContext的子類,其實是創建了AnnotationConfigServletWebServerApplicationContext那麼我們來看下ApplicationContext.refresh()方法

AbstractApplicationContext.refresh()

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         initMessageSource();

         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         //咱們重點看這裏,上面的英文註釋寫的也很詳細,可以在子類中實例化其他bean
         onRefresh();

         // Check for listener beans and register them.
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

這個方法我相信對spring源碼有了解的人都非常熟悉這個方法,這個方法就是spring代碼的入口,非常重要。這裏其實是實現了模板方法設計模式,這裏是定義了基本的骨架,有些方法可以在不同的子類有不同的實現,我們上面看到創建的springboot的web應用程序默認創建的是AnnotationConfigServletWebServerApplicationContext,我們來看下它的onRefresh()方法(在他的父類ServletWebServerApplicationContext中)

ServletWebServerApplicationContext.onRefresh()

@Override
protected void onRefresh() {
   super.onRefresh();
   try {
       //重點看這裏
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}

咱們重點看createWebServer(),這裏從方法名就能看出創建web服務

private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
      ServletWebServerFactory factory = getWebServerFactory();
       //創建web服務在這裏,這裏是工廠方法
      this.webServer = factory.getWebServer(getSelfInitializer());
   }
   else if (servletContext != null) {
      try {
         getSelfInitializer().onStartup(servletContext);
      }
      catch (ServletException ex) {
         throw new ApplicationContextException("Cannot initialize servlet context",
               ex);
      }
   }
   initPropertySources();
}

在這裏插入圖片描述

上面是工廠方法,有Jetty和tomcat,我們這裏點tomcat進去看看

TomcatServletWebServerFactory.getWebServer()

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
   Tomcat tomcat = new Tomcat();
   File baseDir = (this.baseDirectory != null) ? this.baseDirectory
         : createTempDir("tomcat");
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   Connector connector = new Connector(this.protocol);
   tomcat.getService().addConnector(connector);
   customizeConnector(connector);
   tomcat.setConnector(connector);
   tomcat.getHost().setAutoDeploy(false);
   configureEngine(tomcat.getEngine());
   for (Connector additionalConnector : this.additionalTomcatConnectors) {
      tomcat.getService().addConnector(additionalConnector);
   }
   prepareContext(tomcat.getHost(), initializers);
   return getTomcatWebServer(tomcat);
}

這裏相信大家就很清晰了,這裏自己創建了一個Tomcat,最後會調用tomcat.start()這裏我就不繼續往下跟了,感興趣的可以繼續往下看看。另外如果感興趣的同學可以看看這裏的Tomcat的設置和我們下載的tomcat中的server.xml文件進行對比一下,看看有什麼相似的地方

這篇文章就先分析到這裏吧,還留了一個最後一個問題(如何自動加載第三方的starter),我們到下一篇文章再詳細的分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章