本文主要分析 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()方法中作了哪些操做。