(一)SpringBoot啓動過程的分析-啓動流程概覽

-- 如下內容均基於2.1.8.RELEASE版本java

經過粗粒度的分析SpringBoot啓動過程當中執行的主要操做,能夠很容易劃分它的大流程,每一個流程只關注重要操做爲後續深刻學習創建一個大綱。web


官方示例-使用SpringBoot快速構建一個Web服務

@RestController
@SpringBootApplication
public class Example {

	@RequestMapping("/")
	String home() {
		return "Hello World!";
	}

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

}

由代碼可知SpringBoot應用程序入口爲SpringApplication.java,由其run()方法開始。spring

SpringApplication.java

構造方法

public class SpringApplication {

    // 省略部分代碼

	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();
	}
}

由代碼可得知,它未實現接口、未繼承其它類。由構造方法能夠看出它主要作了以下幾件事:bootstrap

  • 獲取當前應用類型(NONE, SERVLET, REACTIVE其中之一)。
  • 經過SPI獲取ApplicationContextInitializer接口的實現類,其配置在MATE-INF/spring.factories文件中。
  • 經過SPI獲取ApplicationListener接口的實現類,同上。
  • 獲取啓動入口(main函數)

run(...args) 方法

整個應用的啓動將會在run方法內部完成。去除那些枝葉,只取最主要的內容來看。數組

/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

經過方法上面的註釋描述能夠看出它就是用於啓動並刷新容器。在run方法內部經過SPI獲取SpringApplicationRunListener接口的實現類,它用於觸發全部的監聽器。EventPublishingRunListener做爲一個實現類,從名稱上來看其主要用於運行時的事件發佈。在SpringBoot的各個生命週期來觸發相對的事件,調用處理事件的監聽器來完成每一個階段的操做。springboot

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

獲取全部的SpringApplicationRunListener接口實例,此處的SpringApplicationRunListeners它包裝了SpringApplicationRunListener對象。以下:app

class SpringApplicationRunListeners {

	private final Log log;

	private final List<SpringApplicationRunListener> listeners;

	SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
		this.log = log;
		this.listeners = new ArrayList<>(listeners);
	}
	// 省略部分代碼
}

接着調用了starting()方法,實際上仍是調用SpringApplicationRunListener的starting方法。以下:less

public void starting() {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.starting();
	}
}

實際上SpringApplicationRunListener並不止這一個方法:ide

void starting();
運行一開始觸發,屬於最先期的事件,處理ApplicationStartingEvent事件函數

void environmentPrepared(ConfigurableEnvironment environment);
當環境準備好的時候調用,可是在ApplicationContext建立以前。

void contextPrepared(ConfigurableApplicationContext context);
當ApplicationContext準備好的時候調用,可是在sources加載以前。

void contextLoaded(ConfigurableApplicationContext context);
當ApplicationContext準備好的時候調用,可是在它refresh以前。

void started(ConfigurableApplicationContext context);
上下文已被刷新,應用程序已啓動,但CommandLineRunners 和ApplicationRunner還沒被調用。

void running(ConfigurableApplicationContext context);
在run方法結束以前,而且全部的CommandLineRunners 和ApplicationRunner都被調用的時候調用。

根據方法的註釋能夠得知他們執行的時機,並得知他們所處理的事件類型。接下來看看EventPublishingRunListener的staring方法內部作了什麼操做:

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

這個initialMulticaster是幹啥的?看看它的構造方法吧

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);
	}
}

此處它並無直接本身來操做這些監聽器,而是在初始化的時候將全部監聽器給了SimpleApplicationEventMulticaster,由它來執行觸發,此處先不作深刻探討,只須要知道它會觸發事件就行。如今把關注點放在具體的事件觸發上this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));這行代碼在處理事件的時候new了一個ApplicationStartingEvent,由此得知它的每個類型的處理方法都會傳入一個指定的事件。

public void multicastEvent(ApplicationEvent event,  ResolvableType eventType) {
    ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
    Executor executor = this.getTaskExecutor();
    Iterator var5 = this.getApplicationListeners(event, type).iterator();
    while(var5.hasNext()) {
        ApplicationListener<?> listener = (ApplicationListener)var5.next();
        if (executor != null) {
            executor.execute(() -> {
                this.invokeListener(listener, event);
            });
        } else {
            this.invokeListener(listener, event);
        }
    }
}

能夠看出在處理事件的方法內部作了兩件事(multicastEvent方法內部標紅的方法調用):

獲取全部的監聽器
調用監聽器

獲取全部的監聽器

在獲取監聽器的過程當中,會循環判斷監聽器聲明的事件類型是否和本次處理的事件類型相同,本次處理的類型爲ApplicationStartingEvent,不符合事件類型的事件會被排除,只調用聲明瞭此類型的監聽器。
代碼片斷:

AbstractApplicationEventMulticaster.retrieveApplicationListeners(ResolvableType eventType, Class<?> sourceType, ListenerRetriever retriever)

for (ApplicationListener<?> listener : listeners) {
	if (supportsEvent(listener, eventType, sourceType)) {
		if (retriever != null) {
			retriever.applicationListeners.add(listener);
		}
		allListeners.add(listener);
	}
}

循環判斷全部的監聽器(ApplicationListener)判斷其是否支持當前所處理的事件(ApplicationStartingEvent)。

protected boolean supportsEvent(
		ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {

	GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
			(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
	return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

在判斷當前監聽器是否支持指定事件以前,將當前監聽器轉換爲了GenericApplicationListener
而後在進行判斷,看看它轉換爲泛型監聽器的時候做了什麼。

public GenericApplicationListenerAdapter(ApplicationListener<?> delegate) {
    Assert.notNull(delegate, "Delegate listener must not be null");
    this.delegate = delegate;
    this.declaredEventType = resolveDeclaredEventType(this.delegate);
}

經過GenericApplicationListener的構造方法能夠看出它獲取了監聽器聲明的事件類型.

private static ResolvableType resolveDeclaredEventType(ApplicationListener<ApplicationEvent> listener) {
		ResolvableType declaredEventType = resolveDeclaredEventType(listener.getClass());
		if (declaredEventType == null || declaredEventType.isAssignableFrom(ApplicationEvent.class)) {
			Class<?> targetClass = AopUtils.getTargetClass(listener);
			if (targetClass != listener.getClass()) {
				declaredEventType = resolveDeclaredEventType(targetClass);
			}
		}
		return declaredEventType;
	}

根據代碼能夠看出它比較了當前處理的事件和監聽器處理的事件是否相符

public boolean supportsEventType(ResolvableType eventType) {
	if (this.delegate instanceof SmartApplicationListener) {
		Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.resolve();
		return (eventClass != null && ((SmartApplicationListener) this.delegate).supportsEventType(eventClass));
	}
	else {
		return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
	}
}

經過筆者分析代碼時的應用程序執行狀況來看,筆者的應用程序在處理一共捕獲了以下監聽器,他們都監聽了ApplicationStartingEvent事件

筆者的處理ApplicationStartingEvent監聽器列表:

LoggingApplicationListener
BackgroundPreinitializer
DelegatingApplicationListener
LiquibaseServiceLocatorApplicationListener

能夠簡單來看看這些監聽器都有什麼特色:

LoggingApplicationListener監聽器,能夠看出它內部聲明瞭須要關注的事件類型數組包含ApplicationStartingEvent。

public class LoggingApplicationListener implements GenericApplicationListener
public boolean supportsEventType(ResolvableType resolvableType) {
    return this.isAssignableFrom(resolvableType.getRawClass(), EVENT_TYPES);
}

static {
    // 省略部分無關代碼……
    EVENT_TYPES = new Class[]{ApplicationStartingEvent.class, ApplicationEnvironmentPreparedEvent.class, ApplicationPreparedEvent.class, ContextClosedEvent.class, ApplicationFailedEvent.class};
    SOURCE_TYPES = new Class[]{SpringApplication.class, ApplicationContext.class};
    shutdownHookRegistered = new AtomicBoolean(false);
}

BackgroundPreinitializer監聽器:

public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent>

public void onApplicationEvent(SpringApplicationEvent event) {
    if (!Boolean.getBoolean("spring.backgroundpreinitializer.ignore") && event instanceof ApplicationStartingEvent && preinitializationStarted.compareAndSet(false, true)) {
        this.performPreinitialization();
    }

}

經過上面兩個被篩選出來的處理ApplicationStartingEvent事件的監聽器案例會發現他們一個實現了ApplicationListener接口,一個實現了GenericApplicationListener接口,後者繼承了前者,多了兩個判斷事件類型的方法和默認的排序優先級(默認最低)

小結

由一個最早執行的Starting事件咱們能夠得知SpringBoot是如何處理事件,以及事件的匹配是如何實現。後續的其餘事件處理都是一樣的方式。

在run方法的try catch代碼塊內部,開始處理有關上下文的一些流程。SpringBoot也設計了精簡的流程來處理不一樣的任務,具體來講就是以下幾個方法共同完成每一個階段的任務。

prepareEnvironment(listeners, applicationArguments);
prepareContext(context, environment, listeners, applicationArguments, printedBanner)
refreshContext(context);
afterRefresh(context, applicationArguments);

在如上幾個階段中能夠發如今準備環境和準備上下文的過程當中都傳入了監聽器,意味着它們會被調用。

環境準備階段

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;
}

在方法內部能夠看到它建立了環境對象,並在建立完畢的時候調用了listeners.environmentPrepared(environment)方法,觸發了ApplicationEnvironmentPreparedEvent事件。通知其餘監聽器環境信息準備完畢。

上下文準備階段

建立ApplicationContext

根據當前應用類型建立指定的上下文容器,供後續準備上下文使用

protected ConfigurableApplicationContext createApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			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);
}

準備上下文

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
		SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	context.setEnvironment(environment);
	postProcessApplicationContext(context);
	applyInitializers(context);
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	// Load the sources
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	load(context, sources.toArray(new Object[0]));
	listeners.contextLoaded(context);
}

在此環節下,將環境對象放入上下文中,初始化BeanNameGenerator、ResourceLoader、ConversionService。執行ApplicationContextInitializer接口的實現類中的initialize()方法。接着調用了listeners.contextPrepared(context)方法,此方法對應處理ApplicationContextInitializedEvent事件,通知其餘註冊了此事件的監聽器。

protected void applyInitializers(ConfigurableApplicationContext context) {
   for (ApplicationContextInitializer initializer : getInitializers()) {
      Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
            ApplicationContextInitializer.class);
      Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
      initializer.initialize(context);
   }
}

ApplicationContextInitializer接口也是Spring的重要擴展接口之一,著名的配置中心:攜程Apollo中就有很棒的應用。能夠展現一下代碼片斷:

public class ApolloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
   
	private static final Logger logger = LoggerFactory.getLogger(ApolloApplicationContextInitializer.class);
    private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();

    private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector
            .getInstance(ConfigPropertySourceFactory.class);

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        ConfigurableEnvironment environment = context.getEnvironment();
        String enabled = environment.getProperty(PropertySourcesConstants.SURK_BOOTSTRAP_ENABLED, "false");
        if (!Boolean.valueOf(enabled)) {
            logger.debug("Apollo bootstrap config is not enabled for context {}, see property: ${{}}", context, PropertySourcesConstants.SURK_BOOTSTRAP_ENABLED);
            return;
        }
        logger.debug("Apollo bootstrap config is enabled for context {}", context);

        if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
            //already initialized
            return;
        }

        String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
        logger.debug("Apollo bootstrap namespaces: {}", namespaces);
        List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);

        CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
        for (String namespace : namespaceList) {
            Config config = ConfigService.getConfig(namespace);

            composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
        }

        environment.getPropertySources().addFirst(composite);
    }
}

Apollo實現此接口的目的是爲了實如今應用還未啓動,容器還未刷新,Bean實例還未裝載的時候就將配置獲取到放入環境信息中,待使用這些配置的Bean真正建立的時候就能夠直接使用,實現了優先加載配置的能力。

回到當前階段的處理,完成了ApplicationContextInitializedEvent事件通知以後,開始加載BeanDefinition,此處不做分析,緊接着調用了listeners.contextLoaded(context)方法處理ApplicationPreparedEvent事件。完成了上下文準備工做。

刷新容器

private void refreshContext(ConfigurableApplicationContext context) {
	refresh(context);
	if (this.registerShutdownHook) {
		try {
			context.registerShutdownHook();
		}
		catch (AccessControlException ex) {
			// Not allowed in some environments.
		}
	}
}

protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
}

底層仍是調用ApplicationContext的.refresh()方法,此處不做解讀。刷新完畢以後觸發ApplicationStartedEvent事件,通知其餘監聽器做相應處理。

最後調用Runner

private void callRunners(ApplicationContext context, ApplicationArguments args) {
	List<Object> runners = new ArrayList<>();
	runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
	runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
	AnnotationAwareOrderComparator.sort(runners);
	for (Object runner : new LinkedHashSet<>(runners)) {
		if (runner instanceof ApplicationRunner) {
			callRunner((ApplicationRunner) runner, args);
		}
		if (runner instanceof CommandLineRunner) {
			callRunner((CommandLineRunner) runner, args);
		}
	}
}

因而可知,Runner優先級最低。在容器刷新完畢以後才調用,能夠實現一些容器加載完畢以後的邏輯。例如spring-batch就有一個JobLauncherCommandLineRunner用於批處理。至此run方法執行完畢。

總結:
經過簡要的分析SpringBoot啓動過程,能夠發現,在應用啓動過程當中涉及到多個事件,經過EventPublishingRunListener來觸發他們,同時又調用了ApplicationContextInitializer接口完成一些特定操做。

大致步驟能夠總結爲:開始啓動-> 準備環境 -> 準備上下文 -> 刷新上下文 -> 後置處理。經過監聽容器啓動相關的事件能夠在容器啓動的各個階段進行功能擴展,同時也展現了Apollo是如何使用本文涉及到的擴展接口。

相關文章
相關標籤/搜索