SpringBoot源碼:啓動過程分析(一)

    本文主要分析 SpringBoot 的啓動過程。java

    SpringBoot的版本爲:2.1.0 release,最新版本。git

一.時序圖

    仍是老套路,先把分析過程的時序圖擺出來:時序圖-SpringBoot2.10啓動分析github

 

二.源碼分析

    首先從咱們的一個SpringBoot Demo開始,這裏使用 SPRING INITIALIZR 網站生成的starter開始的:web

@SpringBootApplication
public class SpringBootDemoApplication {
	public static void main(String[] args) {
        // 分析的入口,從 run 方法開始
		SpringApplication.run(SpringBootDemoApplication.class, args);
	}
}

    通過SpringApplication多個重載的構造方法,最後到達:spring

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        // 從 run() 傳入的 resourceLoader 此處爲 null
		this.resourceLoader = resourceLoader;
        // 使用斷言判斷 resourceLoader 不爲空
		Assert.notNull(primarySources, "PrimarySources must not be null");
        // 把 primarySources 數組轉爲List,最後放入 primarySources 的一個LinkedHashSet中
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 判斷應用的類型:REACTIVE NONE SERVLET
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 實現 SpringBoot 自動裝配的基礎,此處Spring本身實現的SPI(從META-INF/spring.factories加載class)
        // 加載並實例化以 ApplicationContextInitializer 爲key的類
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
        // 加載並實例化以 ApplicationListener 爲key的類
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 獲取程序當前運行堆棧,看是運行的是哪一個類的 main 方法,保存到上下文中
		this.mainApplicationClass = deduceMainApplicationClass();
	}

    看一眼,WebApplicationType#deduceFromClasspath ,deduce意爲推斷,即根據classpath下的內容推斷出應用的類型。實現是經過ClassUtils#isPresent來嘗試加載表明不一樣應用類型特徵的Class文件:數組

static WebApplicationType deduceFromClasspath() {// 判斷應用的類型
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)// 加載到DispatcherHandler
				&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)// mvc的DispatcherServlet
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {// jersey的ServletContainer
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {// 遍歷數組:Servlet和ConfigurableWebApplicationContext
			if (!ClassUtils.isPresent(className, null)) {// 沒有加載到Servlet相關的class
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

    SpringApplication#getSpringFactoriesInstances,從類路徑下 META-INF/spring.factories 下加載 SpringFactory 實例,相似的操做在 Dubbo SPI中也有:緩存

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
        // 獲取類加載器
		ClassLoader classLoader = getClassLoader();
        // 此處調用了SpringFactoriesLoader的loadFactoryNames()
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // Use names and ensure unique to protect against duplicates
        // 實例化獲取到的類
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);// 排序
		return instances;// 返回實例化好的對象
	}

    SpringFactoriesLoader#loadFactoryNames,加載工廠名字:bash

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

    繼續捉迷藏,到了 SpringFactoriesLoader#loadSpringFactories:下面的內容就是找到全部classpath下的 spring.factories 文件,讀取裏面的內容,放到緩存中,此處和Dubbo SPI中ExtensionLoader#loadDirectory幾乎是如出一轍,能夠參考我寫過的 Dubbo源碼 裏面的註釋。mvc

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        // 從緩存中獲取Map,key爲classLoader
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
		try {
            // 加載資源的urls,被加載的資源爲 "META-INF/spring.factories"
            //先從Resources中加載,沒有加載到再從SystemResources中加載
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {// 遍歷加載到的 spring.factories 文件
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
                // 讀取文件到內存爲Properties對象
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    // Entry的key做爲工程Class的名字
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        // 若是有多個value,都放在Map中,注意此處爲 MultiValueMap ,不是普通的Map,其實現內容的value對應一個LinkedList
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
            // 最後把讀取配置的結果都放入緩存中,cache對象爲一個ConcurrentReferenceHashMap
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

    咱們也來看一下上面讀取的文件 spring.factories 的內容,大概長這個樣子:函數

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
......

    是時候跳出來了,回到主線,返回實例化對象後,到了 SpringApplication#deduceMainApplicationClass,獲取程序當前運行堆棧,看如今運行的是哪一個類的 main 方法,而後保存到上下文:

private Class<?> deduceMainApplicationClass() {
		try {
            // 拿到運行時的堆棧信息
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
                // 若是發現哪一個堆棧元素裏面有運行了main方法,則返回該類
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

 

    至此,SpringApplication的構造函數的分析完成,後面咱們繼續分析SpringApplication的run()方法中作了哪些操做。

相關文章
相關標籤/搜索