springboot情操陶冶-SpringApplication(一)


SpringApplication是所有springboot的入口類,分析此類有助於我們了解springboot的工作機制。本文以2.0.3.REALEASE版本作分析

SpringApplication

調用實例如下

package com.example.demospringbootweb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoSpringbootWebApplication {

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

調用的是SpringApplication.run()方法進行應用程序的啟動。代碼很簡單也容易讓用戶上手,筆者這就進入其具體的類以探瑰寶。

注釋描述

先看下其官方注釋,有助於我們入門。由於注釋過長,筆者此處只對其主要內容作下翻譯總結

  1. 可以簡單的通過main()函數來輔助啟動一個spring應用程序。默認情況下其會按照以下步驟來輔助我們創建的應用

    • 創建一個關聯的ApplicationContext實例
    • 注冊CommandLinePropertySource實例暴露命令行的參數作為spring的屬性
    • 刷新ApplicationContext,並加載所有的單例beans
    • 觸發實現了CommandLineRunner的實例beans
  2. SpringApplications可以讀取來自不同源的beans。官方建議用戶使用@Configuration注解相應的啟動類,當然也支持從以下方式加載相應的beans

    • AnnotatedBeanDefinitionReader加載指定的類
    • XmlBeanDefinitionReader加載XML的配置信息或者GroovyBeanDefinitionReader加載groovy腳本資源
    • ClassPathBeanDefinitionScanner掃描指定的包加載相應bean

過於抽象,筆者繼續通過源碼來對上述的內容進行回顧

構造函數

	/**
	 * 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));
		// 推斷是否為web環境
		this.webApplicationType = deduceWebApplicationType();
		// 加載ApplicationContextInitializer接口類
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		// 加載ApplicationListener接口類
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		// 推斷主函數類
		this.mainApplicationClass = deduceMainApplicationClass();
	}

對上述的注釋作下簡單的解釋

SpringApplication#deduceWebApplicationType()

推斷是否為web環境,源碼如下

	private WebApplicationType deduceWebApplicationType() {
		if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
				&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : WEB_ENVIRONMENT_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

從代碼層看總共有三種應用類型,也代表了三個環境類型

  • WebApplicationType.REACTIVE reactive web應用(classpath環境下須有org.springframework.web.reactive.DispatcherHandler)
  • WebApplicationType.SERVLET servlet web應用(classpath環境下存在javax.servlet.Servlet或者org.springframework.web.context.ConfigurableWebApplicationContext)
  • WebApplicationType.NONE 簡單的JAVA應用(classpath環境不存在上述的類)

SpringApplication#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#getSpringFactoriesInstances()

找尋相應的接口實現類,源碼如下

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		// 上下文classLoader
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// 通過SpringFactoriesLoader來加載相應的類
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

進而查看相應的靜態方法SpringFactoriesLoader.loadFactoryNames(),源碼如下

	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 {
			// 找尋所有classpath下的"META-INF/spring.factories"文件
			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()) {
					// 對含有,的進行分隔並轉為list集合
					List<String> factoryClassNames = Arrays.asList(
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
					result.addAll((String) entry.getKey(), factoryClassNames);
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

由此我們得出結論,classpath環境下所有含META-INF/spring.factories的文件,里面約定了默認的實現。筆者以spring-boot-2.0.3.REALEASE.jar為例

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

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

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# 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

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
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.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer

# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

因此SpringApplication構造函數中加載的ApplicationContextInitializer類有如下

  • ConfigurationWarningsApplicationContextInitializer (對ComponentScan指定的值為"org"等進行報警輸出)
  • ContextIdApplicationContextInitializer (創建默認名為application的ContextId對象,也可通過spring.application.name指定)
  • DelegatingApplicationContextInitializer (對context.initializer.classes指定的class集合進行加載)
  • ServerPortInfoApplicationContextInitializer (將local.server.port設置為指定的web端口,默認為8080)


而加載的ApplicationListener類有如下

  • ClearCachesApplicationListener (反射工具緩存清空事件)
  • ParentContextCloserApplicationListener (父ApplicationContext關閉事件)
  • FileEncodingApplicationListener (系統變量配置的file.encoding值是否與環境變量spring.mandatory-file-encoding一致事件)
  • AnsiOutputApplicationListener (控制台彩色輸出事件,可通過spring.output.ansi.enabled來指定)
  • ConfigFileApplicationListener (讀取spring.profile.active/spring.profile.include配置)
  • DelegatingApplicationListener (委托事件處理類)
  • ClasspathLoggingApplicationListener (打印classpath信息,級別為debug)
  • LoggingApplicationListener (日志處理事件)
  • LiquibaseServiceLocatorApplicationListener (classpath是否存在liquibase的CustomResolverServiceLocator類判斷事件)

其中ApplicationListener所綁定事件的觸發順序小結如下

1.ApplicationStartingEvent[應用程序啟動事件starting]

2.ApplicationEnvironmentPreparedEvent[環境變量配置事件environmentPrepared]

3.ApplicationPreparedEvent[spring上下文加載前事件contextPrepared]

4.ApplicationStartedEvent[spring上下文加載完畢事件contextLoaded]此事件之后會統一調用ApplicationRunner/CommandLineRunner的實現類

5.ApplicationReadyEvent[應用准備事件running]

6.ApplicationFailedEvent/ContextClosedEvent[拋異常或者啟動失敗調用]

小結

由此SpringApplication構造函數完成了一些必要的初始化,重點在於ApplicationContextInitializerApplicationListener接口類。並且通過構造函數反射來進行實例化

限於篇幅過長,筆者將對SpringApplication#run()方法的具體解析放於下一章節來分析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM