springboot情操陶冶-SpringApplication(二)


承接前文springboot情操陶冶-SpringApplication(一),本文將對run()方法作下詳細的解析

SpringApplication#run()

main函數經常調用的run()方法是我們分析的關鍵,先上源碼

	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		// 讀取java.awt.headless系統變量,默認為true.常用於linux圖片的渲染
		configureHeadlessProperty();
		// 獲取SpringApplicationRunListener接口集合並實例化調用公共接口starting()
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			// environment configuration
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			// spring.beaninfo.ignore屬性讀取
			configureIgnoreBeanInfo(environment);
			// springboot的banner樣圖
			Banner printedBanner = printBanner(environment);
			// 創建spring應用上下文(尚未刷新)
			context = createApplicationContext();
			// SpringBootExceptionReporter接口集合讀取
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			// applicationContext configuration
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			// 刷新spring應用上下文
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			// SpringApplicationRunListener接口的started()方法調用
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			// SpringApplicationRunListener接口的running()方法調用
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

以上的代碼注釋有點多,筆者分塊來進行羅列分析

SpringApplication#getRunListeners()

獲取SpringApplicationRunListener接口集合並實例化,根據前文得知,讀取的是META\spring.factories文件中的對應屬性,
此處筆者以org.springframework.boot.context.event.EventPublishingRunListener為例。


先觀察下其構造函數

	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.getListeners()方法,根據前文得知,其會拿到類型為ApplicationListener的集合並存入至SimpleApplicationEventMulticaster廣播類中;
其余方法,比如starting()/started()/environmentPrepared()等等方法均是由其統一調用所有的ApplicationListener接口的對應事件。筆者此處以starting()為例

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

其會找尋支持響應ApplicationStartingEvent事件的Listeners,並執行相應的事件方法。響應的監聽器有LoggingApplicationListenerLiquibaseServiceLocatorApplicationListener

SpringApplication#prepareEnvironment()

Environment環境准備工作

	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		// 解析args參數和spring.profiles.active配置讀取
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		// 觸發ApplicationEnvironmentPreparedEvent事件
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (this.webApplicationType == WebApplicationType.NONE) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

內含代碼內容過多,筆者此處針對自己的閱讀作下小結

  1. args參數是會被包裝為SimpleCommandLinePropertySource屬性源,對應key為commandLineArgs。用戶可通過系統變量設定spring.config.location/spring.config.name等屬性

  2. spring.profiles.active系統屬性讀取,用於不同條件的配置

  3. ApplicationEnvironmentPreparedEvent事件觸發,主要的有ConfigFileApplicationListener監聽類(其會默認讀取application.properties/application.xml/application.yaml配置文件)

    PS:ConfigFileApplicationListener這個類比較重要,其也會去讀取配置文件中的spring.profiles.active屬性加載Profile;
    另外也去讀取EnvironmentPostProcessor接口來統一調用。有興趣的讀者可好好分析一下

  4. 將Environment含有的屬性源綁定至key為configurationPropertiesPropertySources類型中,方便springboot全局搜索上下文所含有的屬性

SpringApplication#configureIgnoreBeanInfo()

讀取spring.beaninfo.ignore屬性,默認為true,主要是用於忽略bean的基本信息

SpringApplication#printBanner()

創建banner打印對象,其默認打印的樣圖如下(作用於console)。具體的讀者感興趣可自行閱讀原理

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.3.RELEASE)

SpringApplication#createApplicationContext()

創建應用上下文對象,其會根據判斷出來的應用類型來創建相應的上下文。

應用類型 上下文對應class類
SERVLET org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
REACTIVE org.springframework.boot.web.servlet.context.AnnotationConfigReactiveWebServerApplicationContext
NONE org.springframework.context.annotation.AnnotationConfigApplicationContext

很明顯,全部會應用注解方式來加載上下文。

SpringApplication#prepareContext()

對已創建的上下文對象作下預備工作

SpringApplication#applyInitializers()

啟動相應的初始化類,這些初始化類均是ApplicationContextInitializer接口的實現類

	protected void applyInitializers(ConfigurableApplicationContext context) {
		for (ApplicationContextInitializer initializer : getInitializers()) {
			// 檢查相應的ApplicationContextInitializer實體類所接收的泛型實體class
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
					initializer.getClass(), ApplicationContextInitializer.class);
			// 只對接收的泛型為ConfigurableApplicationContext.class進行相應的初始化
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			initializer.initialize(context);
		}
	}

針對上面的代碼注釋,初始化類基本有ConfigurationWarningsApplicationContextInitializer/ContextIdApplicationContextInitializer/DelegatingApplicationContextInitializer/ServerPortInfoApplicationContextInitializer等。
有興趣的可自行分析相應的初始化類都執行了哪些操作

SpringApplication#load()

加載beans到相應的上下文對象ApplicationContext中

	// 此處的source一般為main函數所在的class,也可通過SpringApplication#setSource()來新增
	protected void load(ApplicationContext context, Object[] sources) {
		if (logger.isDebugEnabled()) {
			logger.debug(
					"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		}
		BeanDefinitionLoader loader = createBeanDefinitionLoader(
				getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) {
			loader.setBeanNameGenerator(this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			loader.setResourceLoader(this.resourceLoader);
		}
		if (this.environment != null) {
			loader.setEnvironment(this.environment);
		}
		// 加載beans
		loader.load();
	}

筆者此處最關心這個BeanDefinitionLoader會耍什么花樣,繼續往下


先從構造函數開始

	BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
		Assert.notNull(registry, "Registry must not be null");
		Assert.notEmpty(sources, "Sources must not be empty");
		this.sources = sources;
		// 注解解析類
		this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
		// xml解析類
		this.xmlReader = new XmlBeanDefinitionReader(registry);
		if (isGroovyPresent()) {
			this.groovyReader = new GroovyBeanDefinitionReader(registry);
		}
		// classpath掃描類,並屏蔽指定的sources類
		this.scanner = new ClassPathBeanDefinitionScanner(registry);
		this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
	}
  1. AnnotatedBeanDefinitionReader注解解析類,其會注冊多個postProcessor接口供后續的上下文刷新操作被調用解析@Configuration/@Autowired/@Required

  2. XmlBeanDefinitionReaderXML解析類,其會解析XML配置

  3. ClassPathBeanDefinitionScanner掃描classpath環境下指定的包以及指定class類


再看下處理方法load()

	public int load() {
		int count = 0;
		for (Object source : this.sources) {
			count += load(source);
		}
		return count;
	}
	
	private int load(Object source) {
		Assert.notNull(source, "Source must not be null");
		if (source instanceof Class<?>) {
			return load((Class<?>) source);
		}
		if (source instanceof Resource) {
			return load((Resource) source);
		}
		if (source instanceof Package) {
			return load((Package) source);
		}
		if (source instanceof CharSequence) {
			return load((CharSequence) source);
		}
		throw new IllegalArgumentException("Invalid source type " + source.getClass());
	}
	// 筆者重點關注此處
	private int load(Class<?> source) {
		if (isGroovyPresent()
				&& GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
			// Any GroovyLoaders added in beans{} DSL can contribute beans here
			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
					GroovyBeanDefinitionSource.class);
			load(loader);
		}
		// 查看class是否被@Component修飾過。此處多用於加載main類
		if (isComponent(source)) {
			// 注冊bean
			this.annotatedReader.register(source);
			return 1;
		}
		return 0;
	}	

此處的load()方法主要是對設置的sources類進行判斷是否被@Component注解,是則注入至bean工廠。即我們常寫的main()函數類一般會加上@SpringApplication注解,其最終會被AnnotatedBeanDefinitionReader處理。

SpringApplication#refresh()

刷新上下文對象,筆者此處就不展開了,可參考之前的文章Spring源碼情操陶冶-AbstractApplicationContext

異常處理

springboot的異常處理機制是通過讀取SpringBootExceptionReporter接口類來對不同的異常進行不同的輸出,感興趣的可自行閱讀

小結

通過上述的分析,基本對springboot的工作原理有了一定的了解,最主要的其實還是其會將公共的配置放置於META\spring.factories文件中,我們以后只要多關注此文件就會明白的更多。
至於@SpringBootApplication注解是如何為springboot服務的,筆者后續再分析


免責聲明!

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



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