1. 程式人生 > >Spring Boot啟動流程詳解(一)

Spring Boot啟動流程詳解(一)

轉載:http://www.cnblogs.com/xinzhao/p/5551828.html

環境

本文基於Spring Boot版本1.3.3, 使用了spring-boot-starter-web。

配置完成後,編寫了程式碼如下:


@SpringBootApplication
public class Application {
    publicstaticvoidmain(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@RestController
public class RootController { public static final String PATH_ROOT = "/"; @RequestMapping(PATH_ROOT) public String welcome() { return "Welcome!"; } }

雖然只有幾行程式碼,但是這已經是一個完整的Web程式,當訪問url的path部分為"/"時,返回字串"Welcome!"。

首先是一個非常普通的java程式入口,一個符合約定的靜態main方法。在這個main方法中,呼叫了SpringApplication的靜態run方法,並將Application類物件和main方法的引數args作為引數傳遞了進去。

然後是一個使用了兩個Spring註解的RootController類,我們在main方法中,沒有直接使用這個類。

SpringApplication類的靜態run方法


以下程式碼摘自:org.springframework.boot.SpringApplicationpublicstatic ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
}

publicstatic ConfigurableApplicationContext 
run(Object[] sources, String[] args) { return new SpringApplication(sources).run(args); }

在這個靜態方法中,建立SpringApplication物件,並呼叫該物件的run方法。

構造SpringApplication物件


以下程式碼摘自:org.springframework.boot.SpringApplicationpublicSpringApplication(Object... sources) {
    initialize(sources);
}

privatevoidinitialize(Object[] sources) {
    // 為成員變數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();
}

建構函式中呼叫initialize方法,初始化SpringApplication物件的成員變數sources,webEnvironment,initializers,listeners,mainApplicationClass。sources的賦值比較簡單,就是我們傳給SpringApplication.run方法的引數。剩下的幾個,我們依次來看看是怎麼做的。

首先是webEnvironment:


以下程式碼摘自:org.springframework.boot.SpringApplication

private boolean webEnvironment; 

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

privatevoidinitialize(Object[] sources) {
    ...
        // 為成員變數webEnvironment賦值
        this.webEnvironment = deduceWebEnvironment();
    ...
}

privatebooleandeduceWebEnvironment() {
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return false;
        }
    }
    return true;
}

可以看到webEnvironment是一個boolean,該成員變數用來表示當前應用程式是不是一個Web應用程式。那麼怎麼決定當前應用程式是否Web應用程式呢,是通過在classpath中檢視是否存在WEB_ENVIRONMENT_CLASSES這個陣列中所包含的類,如果存在那麼當前程式即是一個Web應用程式,反之則不然。
在本文的例子中webEnvironment的值為true。

然後是initializers:

initializers成員變數,是一個ApplicationContextInitializer型別物件的集合。 顧名思義,ApplicationContextInitializer是一個可以用來初始化ApplicationContext的介面。


以下程式碼摘自:org.springframework.boot.SpringApplication

private List<ApplicationContextInitializer<?>> initializers;

privatevoidinitialize(Object[] sources) {
    ...
    // 為成員變數initializers賦值
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    ...
}

publicvoidsetInitializers(
        Collection<? extends ApplicationContextInitializer<?>> initializers) {
    this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
    this.initializers.addAll(initializers);
}

可以看到,關鍵是呼叫getSpringFactoriesInstances(ApplicationContextInitializer.class),來獲取ApplicationContextInitializer型別物件的列表。


以下程式碼摘自:org.springframework.boot.SpringApplication

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new 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.loadFactoryNames(type, classLoader)來獲取所有Spring Factories的名字,然後呼叫createSpringFactoriesInstances方法根據讀取到的名字建立物件。最後會將建立好的物件列表排序並返回。


以下程式碼摘自:org.springframework.core.io.support.SpringFactoriesLoader

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

publicstatic 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);
    }
}

可以看到,是從一個名字叫spring.factories的資原始檔中,讀取key為org.springframework.context.ApplicationContextInitializer的value。而spring.factories的部分內容如下:


以下內容摘自spring-boot-1.3.3.RELEASE.jar中的資原始檔META-INF/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.web.ServerPortInfoApplicationContextInitializer

可以看到,最近的得到的,是ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer這四個類的名字。

接下來會呼叫createSpringFactoriesInstances來建立ApplicationContextInitializer例項。


以下程式碼摘自:org.springframework.boot.SpringApplication

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set<String> names) {
    List<T> instances = new ArrayList<T>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getConstructor(parameterTypes);
            T instance = (T) constructor.newInstance(args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException(
                    "Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

所以在我們的例子中,SpringApplication物件的成員變數initalizers就被初始化為,ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer這四個類的物件組成的list。

下圖畫出了載入的ApplicationContextInitializer,並說明了他們的作用。至於何時應用他們,且聽後面慢慢分解。

接下來是成員變數listeners


以下程式碼摘自:org.springframework.boot.SpringApplication

private List<ApplicationListener<?>> listeners;

privatevoidinitialize(Object[] sources) {
    ...
    // 為成員變數listeners賦值
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    ...
}

publicvoidsetListeners(Collection<? extends ApplicationListener<?>> listeners) {
    this.listeners = new ArrayList<ApplicationListener<?>>();
    this.listeners.addAll(listeners);
}

listeners成員變數,是一個ApplicationListener<?>型別物件的集合。可以看到獲取該成員變數內容使用的是跟成員變數initializers一樣的方法,只不過傳入的型別從ApplicationContextInitializer.class變成了ApplicationListener.class。

看一下spring.factories中的相關內容:


以下內容摘自spring-boot-1.3.3.RELEASE.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,並說明了他們的作用。至於他們何時會被觸發,等事件出現時,我們再說明。

最後是mainApplicationClass


以下程式碼摘自:org.springframework.boot.SpringApplication

private Class<?> mainApplicationClass;

privatevoidinitialize(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類。

SpringApplication物件的run方法

經過上面的初始化過程,我們已經有了一個SpringApplication物件,根據SpringApplication類的靜態run方法一節中的分析,接下來會呼叫SpringApplication物件的run方法。我們接下來就分析這個物件的run方法。


以下程式碼摘自:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.started();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        context = createAndRefreshContext(listeners, applicationArguments);
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, ex);
        throw new IllegalStateException(ex);
    }
}
  • 可變個數引數args即是我們整個應用程式的入口main方法的引數,在我們的例子中,引數個數為零。

  • StopWatch是來自org.springframework.util的工具類,可以用來方便的記錄程式的執行時間。

SpringApplication物件的run方法建立並重新整理ApplicationContext,算是開始進入正題了。下面按照執行順序,介紹該方法所做的工作。

headless模式


以下程式碼摘自:org.