SpringBoot運行過程從SpringApplication開始解讀


[TOC]
## 簡述
    前面我們講到了springboot的啟動流程,可以說是加載的是SpringBoot的包,現在我們從我們寫的Main方法SpringApplication.run(DemoApplication.class, args)開始解讀。
## 啟動過程
    ###  直接運行的Main函數是應用自己的Main函數
    ```
@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
    ```
入口是一個 SpringApplication類,我們debug一步一步的往下走看看。
### 1.調用SpringApplication類的靜態方法run()
```
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
```
因為,啟動的時候可以傳入多個class,我們只把當前的傳入過來,然后調用run(Object[] sources, String[] args)方法。這前面的都很容易看懂,主要的來了,在靜態的run方法里開始新建一個 SpringApplication對象,並且調用run對象的run方法。
### 2.創建SpringApplication對象過程。
    SpringApplication構造方法里,先調用 initialize()方法,該方法主要是來判斷是不是Web環境,如果是的話,則會創建AnnotationConfigEmbeddedWebApplicationContext,否則Spring context就是AnnotationConfigApplicationContext:
 
代碼如下
```
    private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };
    private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        this.webEnvironment = deduceWebEnvironment();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }
 
    private boolean deduceWebEnvironment() {
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return false;
            }
        }
        return true;
    }
 
    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }
    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                contextClass = Class.forName(this.webEnvironment
                        ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, "
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    }
```
### 3. 創建ApplicationContextInitializer列表
    創建ApplicationContextInitializer時先 調用SpringFactoriesLoader.loadFactoryNames(type, classLoader)來獲取所有Spring Factories的名字,然后為每一個Spring Factories根據讀取到的名字創建其對象
初始化的對象如下:
 Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer

 
從所有jar獲取所有的META-INF/spring-factories文件。
遍歷每一個spring-factories文件,並獲取其下key為factoryClass.getName()(這里是入參
org.springframework.context.ApplicationContextInitializer)的value(這里有以上四個ApplicationContextInitializer實現類)
```
//SpringApplication.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
//SpringFactoriesLoader.class

/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
```

### 4.初始化ApplicationListener列表

```
private List<ApplicationListener<?>> listeners;
 
private void initialize(Object[] sources) {
    ...
    // 為成員變量listeners賦值
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    ...
}
 
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
    this.listeners = new ArrayList<ApplicationListener<?>>();
    this.listeners.addAll(listeners);
}
```

   listeners成員變量,是一個ApplicationListener<?>類型對象的集合。可以看到獲取該成員變量內容使用的是跟成員變量initializers一樣的方法,只不過傳入的類型從ApplicationContextInitializer.class變成了ApplicationListener.class。
看一下spring.factories中的相關內容:
 
以下內容摘自spring-boot.jar中的資源文件META-INF/spring.factories
 Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
 
也就是說,在我們的例子中,listener最終會被初始化為ParentContextCloserApplicationListener,FileEncodingApplicationListener,AnsiOutputApplicationListener,ConfigFileApplicationListener,DelegatingApplicationListener,LiquibaseServiceLocatorApplicationListener,ClasspathLoggingApplicationListener,LoggingApplicationListener這幾個類的對象組成的list。
下圖畫出了加載的ApplicationListener,並說明了他們的作用。至於他們何時會被觸發,等事件出現時,我們再說明。
 
###   5.最后是mainApplicationClass

```
private Class<?> mainApplicationClass;
 
private void initialize(Object[] sources) {
    ...
    // 為成員變量mainApplicationClass賦值
    this.mainApplicationClass = deduceMainApplicationClass();
    ...
}
 
private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}
```
   
在deduceMainApplicationClass方法中,通過獲取當前調用棧,找到入口方法main所在的類,並將其復制給SpringApplication對象的成員變量mainApplicationClass。在我們的例子中mainApplicationClass即是我們自己編寫的Application類。

###  6.SpringApplication對象的run方法


 
可變個數參數args即是我們整個應用程序的入口main方法的參數,在我們的例子中,參數個數為零。
StopWatch是來自org.springframework.util的工具類,可以用來方便的記錄程序的運行時間。
SpringApplication對象的run方法創建並刷新ApplicationContext,算是開始進入正題了。下面按照執行順序,介紹該方法所做的工作。

#### 7.headless模式

```
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private boolean headless = true;
 
public ConfigurableApplicationContext run(String... args) {
    ...
    //設置headless模式
        configureHeadlessProperty();
    ...
}
 
private void configureHeadlessProperty() {
    System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
            SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
```
實際上是就是設置系統屬性java.awt.headless,在我們的例子中該屬性會被設置為true,因為我們開發的是服務器程序,一般運行在沒有顯示器和鍵盤的環境。關於java中的headless模式,更多信息可以參考 這里

#### 8.SpringApplicationRunListeners

```
public ConfigurableApplicationContext run(String... args) {
    ...
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.started();
    /**
         * 創建並刷新ApplicationContext
         * context = createAndRefreshContext(listeners, applicationArguments);
        **/
    listeners.finished(context, null);
    ...
}
 
private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}
```
run方法中,加載了一系列SpringApplicationRunListener對象,在創建和更新ApplicationContext方法前后分別調用了listeners對象的started方法和finished方法, 並在創建和刷新ApplicationContext時,將listeners作為參數傳遞到了createAndRefreshContext方法中,以便在創建和刷新ApplicationContext的不同階段,調用listeners的相應方法以執行操作。所以,所謂的SpringApplicationRunListeners實際上就是在SpringApplication對象的run方法執行的不同階段,去執行一些操作,並且這些操作是可配置的。
同時,可以看到,加載SpringApplicationRunListener時,使用的是跟加載ApplicationContextInitializer和ApplicationListener時一樣的方法。那么加載了什么,就可以從spring.factories文件中看到了

##總結
在調用SpringApplication的run方法之前都做了哪些事情,首先調用void initialize(Object[] sources)方法,這個方法主要是先判斷是不是WEB環境,然后創建ApplicationContextInitializer列表,再 初始化ApplicationListener列表,最后 初始化主類mainApplicationClass.

前面是自己一點一點的寫,后面發現有寫的比教好的,就直接過了一遍。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM