SpringBoot啓動源碼閱讀筆記

前言

一直在用SpringBoot,所以好奇這玩意到底是怎麼玩的,看文章不如自己動手跟一遍... 

容器初始化之前發生了什麼?

想要看懂這裏,需要先複習一下觀察者模式。在這裏就不再贅述了...

一切從run方法開始

可以看到它事實上new了SpringApplication,調用了run方法


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

先看看SpringApplication的構造方法

	/**
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified primary sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * @param resourceLoader the resource loader to use
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #setSources(Set)
	 */
	@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();
	}

這裏使用SPI機制讀取了一些配置在META-INF下的信息,具體來說就是一些類,包括了下面這些內容

現在我們不知道上面的這些東西到底是做什麼的,但看名字大概可以知道是一堆初始化器和一堆監聽器,比如服務器端口號信息上下文初始化器、父容器關閉監聽器、文件編碼監聽器、日誌監聽器什麼的。

這些信息讀取出來之後被創建了實例(看名字getxxxInstance也能猜出來),然後通過set方法被放進了ApplicationLinstener的成員變量中。至於有什麼用現在還不清楚。

嗯好了,繼續跟進run方法,我們就打開了新世界得大門

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

註釋說得很清楚,這裏啓動了springapplication,創建並刷新了 ApplicationContext(看過Spring源碼得應該都知道這代表了什麼)

接下來我們看一下這裏發生了什麼

run方法本體

public ConfigurableApplicationContext run(String... args) {
        //整了個計時器,用大腿想想應該是用來給我們計算應用啓動時間的
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
        //這個東西在Spring源碼裏就有,就是應用上下文
		ConfigurableApplicationContext context = null;
        //異常報告器們
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //awt相關的一些東西 應該不重要
		configureHeadlessProperty();
        //接下來我們看一下這個getRunlinstenner方法
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
                ...
}

我們看看上面這裏的getRunListenners方法

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

可以看到和一般的get方法沒什麼區別,無非就是new一個返回回去。那麼這個ApplicationRunListeners到底是個什麼東西,其實看名字大概能知道是應用運行的監聽器集合。這裏又看到了熟悉的getSprinGFacotiesInstances方法,這次要加載的是SprinApplicationRunlistener.class。這裏我用debug跟進來發現其實只有一個監聽器被創建了,截圖如下。

所以我們看一下這個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 {
    ...
}

按照慣例先看註釋。這是一個用來法部事件的監聽器,使用了一個內置的事件廣播器來爲那些在上下文實際初始化之前被髮出的事件做廣播。好了,聯繫一下之前初始化的一大堆監聽器,現在我們可以大膽猜測,這個東西就是在應用啓動的各個階段把相應的階段通過事件的形式做出廣播來通知其他監聽器進行處理。 現在我們來看一下它的成員變量和構造方法

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

很明確它與application是一個聚合關係,並且它實現了SpringApplicationRunListener這個接口。那麼它在進行監聽的時候到底做了什麼動作呢。事實上很簡單,隨便拿出兩個方法來舉例:

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

        ...
	@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));
	}
        ...

}

無非是通過事件廣播器來通知其他監聽器,讓我們來驗證一下我們的想法,打開這個multicastEvent方法

	/**
 * Simple implementation of the {@link ApplicationEventMulticaster} interface.
 *
 * <p>Multicasts all events to all registered listeners, leaving it up to
 * the listeners to ignore events that they are not interested in.
 * Listeners will usually perform corresponding {@code instanceof}
 * checks on the passed-in event object.
 *
 * <p>By default, all listeners are invoked in the calling thread.
 * This allows the danger of a rogue listener blocking the entire application,
 * but adds minimal overhead. Specify an alternative task executor to have
 * listeners executed in different threads, for example from a thread pool.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 * @see #setTaskExecutor
 */
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
        ...

        @Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}
        ...
}

好了,invokeListenner(listener,event)。已經很明確了。

好了到現在爲止我們已經理清楚了以上代碼到底在做什麼。但請你注意一點,那就是雖然我們註冊了監聽器,但事實上還沒有發生事件。接下來的一行代碼又爲我們的思路提供了證明:

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其實只有一個listener嗎
		listeners.starting();
                ...
}

我們追進這個starting()方法就可以發現,它利用廣播器廣播了一個容器啓動事件

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

也就是說關注容器啓動事件的監聽器將會執行它們對應的方法。

舉個例子

現在讓我們就以ApplicationListener作爲例子看看監聽器到底做了什麼事

點開onApplicationEvent方法,我們可以看到:


	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationStartingEvent) {
			onApplicationStartingEvent((ApplicationStartingEvent) event);
		}
		else if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		else if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent((ApplicationPreparedEvent) event);
		}
		else if (event instanceof ContextClosedEvent
				&& ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
			onContextClosedEvent();
		}
		else if (event instanceof ApplicationFailedEvent) {
			onApplicationFailedEvent();
		}
	}

已知當前被廣播的事件是ApplicationStartingEvent,所以將要執行的方法將是onApplicationStartingEvent,追進去,執行了如下代碼

	private void onApplicationStartingEvent(ApplicationStartingEvent event) {
		this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
		this.loggingSystem.beforeInitialize();
	}

嗯,設置了日誌系統。點進beforeInitialize方法看到在抽象方法的註釋中有如下描述:

	/**
	 * Reset the logging system to be limit output. This method may be called before
	 * {@link #initialize(LoggingInitializationContext, String, LogFile)} to reduce
	 * logging noise until the system has been fully initialized.
	 */
	public abstract void beforeInitialize();

重置日誌系統到受限的輸出,可以在 initialize(LoggingInitializationContext,String, LogFile)調用之前被調用,以此減少日誌噪音直到系統完全初始化完畢。

好了例子看完了,其他監聽器按理說也只是做了他們職責內應該做的事情而已。接下來

監聽器已就緒

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 {
            //默認的應用參數們 注意將args傳了進去 說明這裏將使用我們的命令行參數
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //準備環境 這裏將剛纔的listensers傳了進去 說明還有事情要廣播
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            //配置忽略bean信息
			configureIgnoreBeanInfo(environment);
            //打印banner。。。就是啓動界面那個SpringBoot吧 當然你也可以自定義 因爲這裏將environment交了進去
			Banner printedBanner = printBanner(environment);
            //創建上下文
			context = createApplicationContext();
            //又是SPI 獲取異常報告器
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
            //準備上下文
			prepareContext(context, environment, listeners, applicationArguments, 
printedBanner);

<...未完待續 上面代碼的分析以後補上,今天寫累了>

接下來就是刷新上下文了,這部分內容放在spring源碼解讀裏更合適,所以候後再更新吧!Yes!

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