Spring源代碼分析-springboot啓動

一. 入口

對於Springboot工程,我們只需要一行代碼調用就可以運行起來,如下:

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

那接下來我們就要從這行代碼入手,分析其內部實現過程。

二. 源代碼分析

代碼位置:org.springframework.boot.SpringApplication

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

    /**
     * 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);
    }

上面代碼我們可以發現幾點:

  1. run方法其實是可以接受一個Class數組的
  2. 在run方法裏面,實例化了一個SpringApplication對象,然後調用它的run方法。
    那接下來,我們就要分析這兩點,初始化SpringApplication對象,以及它的run方法實現。

2.1 實例化SpringApplication對象

@SuppressWarnings({ "unchecked", "rawtypes" })
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();
    }

這裏比較重要的一個是getSpringFactoriesInstances,這個方法是根據類型去讀取spring.factories配置文件裏面對應的配置項。
比如setInitializers裏面,傳入的類型是ApplicationContextInitializer.class。
那接下來,我們來分析這個讀取流程。

spring.factories是Spring提供給廠商自定義配置的文件,我們也可以定義我們自己的spring.factories文件,然後就會運行裏面的配置項。
比如在我們自己的項目裏面,添加一個spring.factories文件,裏面配置SpringApplicationRunListener除了系統的之外,我還加了MyEventPublishListener

Spring源代碼分析-springboot啓動

在MyEventPublishListener裏面,只是打印了一下運行日誌

Spring源代碼分析-springboot啓動

Spring源代碼分析-springboot啓動

那我們的服務跑起來的時候,有如下日誌:

Spring源代碼分析-springboot啓動

也就是說我們自定義的spring.factories配置是生效了的,我們在項目啓動的時候,做一些自定義配置,比如初始化資源。

2.1.1 獲取spring.factories配置

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        // Use names and ensure unique to protect against duplicates
        // 1. 獲取配置項
        Set<String> names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 2. 初始化配置項        
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
     // 3. 排序,值越小,優先級越高
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

繼續往裏面看獲取配置項SpringFactoriesLoader.loadFactoryNames方法。

2.1.2 SpringFactoriesLoader.loadFactoryNames

SpringFactoriesLoader類定義是在Spring-core裏面,我們上一篇講過,Spring有3個核心組件,Spring-core, Spring-context, Spring-beans。他們的代碼在Spring-framework裏面。
於是,我們打開Spring-framework,找到其代碼,如下圖。
Spring源代碼分析-springboot啓動

代碼如下;

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

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

這個方法還是簡單明瞭的,從META-INF/spring.factories裏面去讀取factoryClass.getName()的配置,就像springboot裏面提供的配置一樣,ApplicationContextInitializer類型就是下面的配置,有4個配置項。

# 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.web.context.ServerPortInfoApplicationContextInitializer

所以上面setInitializers的時候,就是讀取這4個類型

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

然後實例化,存入SpringApplication裏面的成員列表裏面

private List<ApplicationContextInitializer<?>> initializers;

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

接下來還要設置listeners
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

2.1.3 獲取ApplicationListener

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

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

getSpringFactoriesInstance的實現上面已經解釋過了,在springboot的默認配置是這樣的。

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

所以listeners裏面存儲的是EventPublishingRunListener對象。

/**
 * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
 * <p>
 * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
 * before the context is actually refreshed.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @author Artsiom Yudovin
 */
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

}

EventPublishingRunListener從註釋上面來說,是使用ApplicationEventMulticaster來發布SpringApplicationEvent事件。其實EventPublishingRunListener其實就是個事件中轉站,它利用ApplicationEventMulticater來轉發事件。

ApplicationEventMulticaster是Spring-context裏面提供的一種機制,它可以根據傳入的事件類型找到相應的監聽者,這個和安卓的EventBus效果類似。
這個事件分發後面篇幅我們再說。

回到SpringApplication實例化的構造方法,最後一個行,獲取mainApplicationClass

2.1.4 獲取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;
}

這個方法的實現就有點意思了,通過構造一個異常,然後從異常堆棧裏面查詢main方法所在類,找到那個類。

那到這裏,實例化SpringApplication實例化就完成了,接下來就去看看run方法。

2.1.5 自定義項目的spring.factories文件

上面說了那麼多spring.factories文件的東西,那麼如果我們自己項目中想使用spring.factories文件,是定義到哪個文件夾位置呢?
其實需要是放在src/main/resources/META-INF目錄下面,如果沒有這個目錄,請自己創建文件夾,如下圖所示:

Spring源代碼分析-springboot啓動

2.2 SpringApplication.run

根據註釋,它是運行一個Spring application,創建和刷新一個新的ApplicationContext。代碼有點長,慢慢看。

/**
     * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
      //1. 啓動計時器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //2. 設置java.awt.headless系統屬性爲true,沒有圖形界面,適合於服務器程序開發
        configureHeadlessProperty();
        //3. 獲取spring.factories配置文件中的運行listener,一般是EventPublishingRunListener,作爲事件中轉
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //4. 通知開始starting,發出starting事件給觀察者.
        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;
    }

2.2.1 啓動計時器

StopWatch stopWatch = new StopWatch();
stopWatch.start();

StopWatch是Spring-core裏面提供的功能,可以用來統計總共運行時間

/**
 * Simple stop watch, allowing for timing of a number of tasks,
 * exposing total running time and running time for each named task.
 *
 * <p>Conceals use of {@code System.currentTimeMillis()}, improving the
 * readability of application code and reducing the likelihood of calculation errors.
 *
 * <p>Note that this object is not designed to be thread-safe and does not
 * use synchronization.
 *
 * <p>This class is normally used to verify performance during proof-of-concepts
 * and in development, rather than as part of production applications.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since May 2, 2001
 */
public class StopWatch {
    ...
    public void start(String taskName) throws IllegalStateException {
        if(this.running) {
            throw new IllegalStateException("Can't start StopWatch: it's already running");
        } else {
            this.running = true;
            this.currentTaskName = taskName;
            this.startTimeMillis = System.currentTimeMillis();
        }
    }

        ....
}

記錄開始時間 this.startTimeMillis = System.currentTimeMillis(); 這個startTimeMillis值待會在stopWatch.stop()時會使用。

2.2.2 設置成服務器模式

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.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,表示可能缺少鍵盤等支持。

2.2.3 獲取Run Listener

SpringApplicationRunListeners listeners = getRunListeners(args);

private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args));
}

getSpringFactoriesInstances在上面已經分析過了,可以根據傳入的class type獲取到spring.factories配置的類。而對於SpringApplicationRunListeners,一般配置是這樣的

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener,\

所以實例化後就是EventPublishingRunListener對象,EventPublishingRunListener是一個事件中間轉發類,利用一個SimpleApplicationEventMulticaster轉發各種事件。

大概如下:

/**
 * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
 * <p>
 * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
 * before the context is actually refreshed.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @author Artsiom Yudovin
 */
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

    private final SpringApplication application;

    private final String[] args;

    private final SimpleApplicationEventMulticaster initialMulticaster;

    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public void starting() {
        this.initialMulticaster.multicastEvent(
                new ApplicationStartingEvent(this.application, this.args));
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                this.application, this.args, environment));
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(
                this.application, this.args, context));
    }

    ...
    }   

SimpleApplicationEventMulticaster是Spring-context提供的一種消息傳遞機制,這個具體可以再分析。

2.2.4 發送starting消息

listeners.starting();

@Override
    public void starting() {
        this.initialMulticaster.multicastEvent(
                new ApplicationStartingEvent(this.application, this.args));
}

listeners 的值如下

Spring源代碼分析-springboot啓動

其中MyEventPublishListener是我們在spring.factories配置的
Spring源代碼分析-springboot啓動

而EventPublishingRunListener是SpringBoot裏面spring.factories配置的。
Spring源代碼分析-springboot啓動

所以會執行MyEventPublishListener和EventPublishingRunListener的starting方法。

2.2.5 準備環境

ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
configureIgnoreBeanInfo(environment);
private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }
private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        switch (this.webApplicationType) {
            case SERVLET:
                return new StandardServletEnvironment();
            case REACTIVE:
                return new StandardReactiveWebEnvironment();
            default:
                return new StandardEnvironment();
        }
    }

可以根據不同的項目類型創建不同的Environment,關於Environment具體可以參考下面鏈接
https://www.jb51.net/article/145192.htm

2.2.6 打印Banner

Banner printedBanner = printBanner(environment);

相信大家對Springboot啓動的圖形不會陌生,類似於下圖:
Spring源代碼分析-springboot啓動

那麼這個圖形就是從這裏打印出來的。

private Banner printBanner(ConfigurableEnvironment environment) {
        if (this.bannerMode == Banner.Mode.OFF) {
            return null;
        }
        ResourceLoader resourceLoader = (this.resourceLoader != null)
                ? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
        SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
                resourceLoader, this.banner);
        if (this.bannerMode == Mode.LOG) {
            return bannerPrinter.print(environment, this.mainApplicationClass, logger);
        }
        return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    }

printBanner會構造一個SpringApplicationBannerPrinter對象,然後根據不同的模式(this.bannerMode == Mode.LOG),通過logger或者System.out打印。
所以主要看SpringApplicationBannerPrinter的print做了什麼。

public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
        Banner banner = getBanner(environment);
        banner.printBanner(environment, sourceClass, out);
        return new PrintedBanner(banner, sourceClass);
    }

主要看getBanner方法

private Banner getBanner(Environment environment) {
        Banners banners = new Banners();
        banners.addIfNotNull(getImageBanner(environment));
        banners.addIfNotNull(getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {
            return banners;
        }
        if (this.fallbackBanner != null) {
            return this.fallbackBanner;
        }
        return DEFAULT_BANNER;
    }

這裏springboot支持自定義的啓動圖形,getImageBanner和getTextBanner。 如果沒有定義,那麼就嘗試尋找fallbackBanner,這個fallbackBanner可以通過SpringApplication設置, 一般我們都不會設置。
所以默認都會返回DEFAULT_BANNER,DEFAULT_BANNER的定義如下:

private static final Banner DEFAULT_BANNER = new SpringBootBanner();
class SpringBootBanner implements Banner {

    private static final String[] BANNER = { "",
            "  .   ____          _            __ _ _",
            " /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\",
            "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
            " \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )",
            "  '  |____| .__|_| |_|_| |_\\__, | / / / /",
            " =========|_|==============|___/=/_/_/_/" };

    private static final String SPRING_BOOT = " :: Spring Boot :: ";

    private static final int STRAP_LINE_SIZE = 42;

    @Override
    public void printBanner(Environment environment, Class<?> sourceClass,
            PrintStream printStream) {
        for (String line : BANNER) {
            printStream.println(line);
        }
        String version = SpringBootVersion.getVersion();
        version = (version != null) ? " (v" + version + ")" : "";
        StringBuilder padding = new StringBuilder();
        while (padding.length() < STRAP_LINE_SIZE
                - (version.length() + SPRING_BOOT.length())) {
            padding.append(" ");
        }

        printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,
                AnsiColor.DEFAULT, padding.toString(), AnsiStyle.FAINT, version));
        printStream.println();
    }

}

上面String[] BANNER對象是不是很熟悉呢?嗯,就是我們命令行裏面打印輸出的那個了。

得到SpringBootBanner對象後,通過調用printBanner方法打印出來,所以我們就看到命令行的輸出

banner.printBanner(environment, sourceClass, out);

2.2.7 StopWatch.stop()

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ...
        try {
            ....
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }

之前分析stopWatch.start()裏面是記錄了啓動開始時間,那麼stopWatch.stop()是做什麼呢?代碼如下:

public void stop() throws IllegalStateException {
        if(!this.running) {
            throw new IllegalStateException("Can't stop StopWatch: it's not running");
        } else {
            long lastTime = System.currentTimeMillis() - this.startTimeMillis;
            this.totalTimeMillis += lastTime;
            this.lastTaskInfo = new StopWatch.TaskInfo(this.currentTaskName, lastTime);
            if(this.keepTaskList) {
                this.taskList.add(this.lastTaskInfo);
            }

            ++this.taskCount;
            this.running = false;
            this.currentTaskName = null;
        }
    }

這裏面記錄了程序啓動時間,也就是從stopWatch.start()到stop的時間-totalTimeMillis

接下來判斷是否需要打印啓動時間

if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }

如果需要打印,那麼調用logStarted打印信息,如下:

public void logStarted(Log log, StopWatch stopWatch) {
        if (log.isInfoEnabled()) {
            log.info(getStartedMessage(stopWatch));
        }
    }
private StringBuilder getStartedMessage(StopWatch stopWatch) {
        StringBuilder message = new StringBuilder();
        message.append("Started ");
        message.append(getApplicationName());
        message.append(" in ");
        message.append(stopWatch.getTotalTimeSeconds());
        try {
            double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
            message.append(" seconds (JVM running for " + uptime + ")");
        }
        catch (Throwable ex) {
            // No JVM time available
        }
        return message;
    }

獲取的打印信息getStartedMessage,也就是如下圖所示:

Spring源代碼分析-springboot啓動

三. 參考鏈接

Spring Environment介紹

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