二、Spring Boot 核心之理解SpringApplication

一、SpringApplication基本使用

運行
        // 運行
        SpringApplication.run(SpringApplicationApplication.class, args);
自定義
        // 自定義
        SpringApplication springApplication = new SpringApplication(SpringApplicationApplication.class);
        springApplication.setBannerMode(Banner.Mode.CONSOLE);
        // 設置運行環境    【NONE:非Web環境,SERVLET:WebServlet,REACTIVE:WebFlux異步】
        springApplication.setWebApplicationType(WebApplicationType.NONE);
        // 設置配置文件環境
        springApplication.setAdditionalProfiles("prod");
        // Headless模式是系統的一種配置模式。在系統可能缺少顯示設備、鍵盤或鼠標這些外設的情況下可以使用該模式。
        // 在開發圖形化界面時可設置爲false
        springApplication.setHeadless(true);
通過 SpringApplicationBuilder API 調整
        new SpringApplicationBuilder(SpringApplicationApplication.class)
                .bannerMode(Banner.Mode.CONSOLE)
                .web(WebApplicationType.NONE)
                .profiles("prod")
                .headless(true);

二、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();
	}
配置 Spring Boot Bean 源

Java 配置 Class 或 XML 上下文配置文件集合,用於 Spring Boot BeanDefinitionLoader 讀取 ,並且將配置源解析加載爲

  • Spring Bean 定義
    • 數量:一個或多個以上
啓動配置Bean源
  • 第一種@SpringBootApplication註解標註的類,不僅限於本身,eg:

        public static void main(String[] args) {
            // 運行
            SpringApplication.run(ApplicationApplication.class, args);
        }
    
        @SpringBootApplication
        public static class ApplicationApplication{
    
        }
    
  • 第二種通過setSources配置:``,eg:

setSources源碼:
在這裏插入圖片描述

public static void main(String[] args) {
        // 運行
//        SpringApplication.run(ApplicationApplication.class, args);
        Set<String> sourceSet = new HashSet<>();
        sourceSet.add(ApplicationApplication.class.getName());
        SpringApplication springApplication = new SpringApplication();
        //  sources can be:a class name, package name, or an XML resource location.
        // 源可以是一個類的名字、包名、本地xml文件路徑
        springApplication.setSources(sourceSet);
        springApplication.run(args);
    }

    @SpringBootApplication
    public static class ApplicationApplication{

    }
推斷 Web 應用類型

根據是否存在某個類來判斷當前應用類型:WebServlet、WebFlux和普通應用

  1. org.springframework.boot.SpringApplication#SpringApplication

  2. SpringApplication:this.webApplicationType = WebApplicationType.deduceFromClasspath();

  3. org.springframework.boot.WebApplicationType#deduceFromClasspath方法代如下:

    private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
    		"org.springframework.web.context.ConfigurableWebApplicationContext" };
    
    private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
    
    private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
    
    private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
    
    private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
    
    private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
    
    static WebApplicationType deduceFromClasspath() {
    	if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) // 1
    			&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
    		return WebApplicationType.REACTIVE;
    	}
    	for (String className : SERVLET_INDICATOR_CLASSES) { // 2
    		if (!ClassUtils.isPresent(className, null)) {
    			return WebApplicationType.NONE;
    		}
    	}
    	return WebApplicationType.SERVLET; // 3
    }
    

由以上代碼可知:

  1. web.reactive和web.servlet共存時,返回SERVLET應用環境,只存在WEBFLUX_INDICATOR_CLASS時,才爲WebApplicationType.REACTIVE
  2. SERVLET_INDICATOR_CLASSES中類均不存在時,返回return WebApplicationType.NONE
  3. 默認返回WebApplicationType.SERVLET
加載應用上下文初始器 ( ApplicationContextInitializer ) & 加載應用事件監聽器( ApplicationListener )

利用 Spring 工廠加載機制,實例化 ApplicationContextInitializer 實現類,並排序對象集合。

  • 實現類: org.springframework.core.io.support.SpringFactoriesLoader
  • 配置資源: META-INF/spring.factories
  • 排序: AnnotationAwareOrderComparator#sort
    SpringApplicationg構造函數中加載代碼如下:
  1. org.springframework.boot.SpringApplication#SpringApplication
  2. SpringApplication:
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

實際調用方法:

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		// 通過工廠機制獲取實現類名字 SpringFactoriesLoader->META-INF/spring.factories->names 
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		//  創建實例
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		// 根據order排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}
推斷引導類(Main Class)

通過異常堆棧來判斷符合條件(包含main方法)的類

  1. org.springframework.boot.SpringApplication#SpringApplication
  2. SpringApplication:this.mainApplicationClass = deduceMainApplicationClass();
  3. 源碼和debug->stackTrace截圖如下
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;
	}

在這裏插入圖片描述

三、SpringApplication.run運行階段

核心代碼

public ConfigurableApplicationContext run(String... args) {
		// 計時器初始化
        StopWatch stopWatch = new StopWatch();
        // 開始計時
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        // 配置Headless參數
        this.configureHeadlessProperty();
        // 加載監聽器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        // 運行監聽器
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }
加載 SpringApplication 運行監聽器( SpringApplicationRunListeners )

核心代碼

        // 加載監聽器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        // 運行監聽器
        listeners.starting();

getRunListeners:

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
        // 通過工廠機制加載SpringApplicationRunListener的實現類,實際即爲:org.springframework.boot.context.event.EventPublishingRunListener
        // 加載完畢之後,構造SpringApplicationRunListeners組合對象(設計模式),
        return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }
運行 SpringApplication 運行監聽器( SpringApplicationRunListeners )

SpringApplicationRunListener 監聽多個運行狀態方法:

監聽方法 階段說明 Spring Boot 起始版本
starting() Spring 應用剛啓動 1.0
environmentPrepared(ConfigurableEnvironment) ConfigurableEnvironment 準備妥當,允許將其調整 1.0
contextPrepared(ConfigurableApplicationContext) ConfigurableApplicationContext 準備妥當,允許將其調整 1.0
contextLoaded(ConfigurableApplicationContext) ConfigurableApplicationContext 已裝載,但仍未啓動 1.0
started(ConfigurableApplicationContext) ConfigurableApplicationContext 已啓動,此時 Spring Bean 已初始化完成 2.0
running(ConfigurableApplicationContext) Spring 應用正在運行 2.0
failed(ConfigurableApplicationContext,Throwable) Spring 應用運行失敗 2.0
監聽 Spring Boot 事件 / Spring 事件

Spring Boot 通過 SpringApplicationRunListener 的實現類 EventPublishingRunListener 利用 Spring Framework 事件API ,廣播 Spring Boot 事件。

Spring Framework 事件/監聽器編程模型
  • Spring 應用事件
    • 普通應用事件: ApplicationEvent
    • 應用上下文事件: ApplicationContextEvent
  • Spring 應用監聽器
    • 接口編程模型: ApplicationListener
    • 註解編程模型: @EventListener
  • Spring 應用事廣播器
    • 接口: ApplicationEventMulticaster
    • 實現類: SimpleApplicationEventMulticaster
    • 執行模式:同步或異步
EventPublishingRunListener 監聽方法與 Spring Boot 事件對應關係
監聽方法 Spring Boot 事件 Spring Boot 起始版本
starting() ApplicationStartingEvent 1.5
environmentPrepared(ConfigurableEnvironment) ApplicationEnvironmentPreparedEvent 1.0
contextPrepared(ConfigurableApplicationContext)
contextLoaded(ConfigurableApplicationContext) ApplicationPreparedEvent 1.0
started(ConfigurableApplicationContext) ApplicationStartedEvent 2.0
running(ConfigurableApplicationContext) ApplicationReadyEvent 2.0
failed(ConfigurableApplicationContext,Throwable) ApplicationFailedEvent 1.0

contextPrepared不對應事件,contextLoaded對應ApplicationPreparedEvent事件

創建 Environment

根據準備階段的推斷 Web 應用類型創建對應的 ConfigurableEnvironment 實例:

  • Web Reactive: StandardEnvironment
  • Web Servlet: StandardServletEnvironment
  • 非 Web: StandardEnvironment
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

prepareEnvironment:

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

getOrCreateEnvironment:

	private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}

創建 Spring 應用上下文( ConfigurableApplicationContext )

根據準備階段的推斷 Web 應用類型創建對應的 ConfigurableApplicationContext 實例:

  • Web Reactive: AnnotationConfigReactiveWebServerApplicationContext
  • Web Servlet: AnnotationConfigServletWebServerApplicationContext
  • 非 Web: AnnotationConfigApplicationContext
context = createApplicationContext();

createApplicationContext:

	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				// 根據構造階段推斷的 Web 應用類型來創建Spring應用上下文
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章