(一)SpringBoot启动过程的分析-启动流程概览

-- 以下内容均基于2.1.8.RELEASE版本

通过粗粒度的分析SpringBoot启动过程中执行的主要操作,可以很容易划分它的大流程,每个流程只关注重要操作为后续深入学习建立一个大纲。


官方示例-使用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()方法开始。

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

由代码可得知,它未实现接口、未继承其它类。由构造方法可以看出它主要做了如下几件事:

  • 获取当前应用类型(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的各个生命周期来触发相对的事件,调用处理事件的监听器来完成每个阶段的操作。

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

获取所有的SpringApplicationRunListener接口实例,此处的SpringApplicationRunListeners它包装了SpringApplicationRunListener对象。如下:

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方法。如下:

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

实际上SpringApplicationRunListener并不止这一个方法:

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是如何使用本文涉及到的扩展接口。

posted @ 2021-02-10 11:40  不会发芽的种子  阅读(910)  评论(2编辑  收藏  举报