Анализ исходного кода Spring Boot — процесс запуска

Spring Boot

Spring BootКак самая популярная среда разработки Java в настоящее время, она придерживается принципа «конвенция важнее конфигурации», что значительно упрощает разработку.Spring MVCсложныйXMLКонфигурация файла, в основном реализация стартового проекта с нулевой конфигурацией.

Эта статья основана наSpring Boot 2.1.0.RELEASEпонимание версииSpring BootКак начать

Сначала рассмотрим простейшийSpring Bootкод запуска

@SpringBootApplication
public class DemoApplication {

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

каждый использованныйSpring BootИзучающие вышеизложенное должны быть хорошо знакомы с приведенным выше кодом, и вы можете запустить его с помощью этого кода.Spring Bootприменение. ТакSpringApplication.run(DemoApplication.class, args)Внутри, в конце концов, что он сделал?

Прежде чем рассматривать конкретный код, давайте посмотримSpringApplicationВнутренний процесс выполнения примерно следующий

执行流程

Как видно из приведенного выше рисункаrun()является точкой входа всего приложения, а затем инициализируетсяSpringApplicationRunListener,EnvironmentДождитесь, пока экземпляра, затем создайте объект контекста приложения, «подготовить» и «Обновить» контекст, перейдите сюдаSpringКонтейнер был в основном запущен, и, наконец, отправка событий уведомляет каждый компонент о выполнении соответствующего действия.

Анализ исходного кода

Поняв общий процесс, давайте приступим к углубленному анализу исходного кода.Spring BootКонкретный процесс запуска, сначала введите метод вводаrun

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();
    // ...

StopWatchОн в основном используется для подсчета времени выполнения каждой задачи, напримерSpring BootОбщее время, необходимое для запуска.

Started DemoApplication in 4.241 seconds (JVM running for 5.987)

getRunListeners()законченныйSpringApplicationRunListenerКак сделано мнение? Взгляд внутри метода

private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
}

SpringApplicationRunListenersиSpringApplicationRunListenerНе тот же класс, у них очень похожие имена

ПроверятьSpringApplicationRunListenersисходный код

SpringApplicationRunListeners(Log log,
			Collection<? extends SpringApplicationRunListener> listeners) {
	this.log = log;
	this.listeners = new ArrayList<>(listeners);
}

public void starting() {
	for (SpringApplicationRunListener listener : this.listeners	 {
		listener.starting();
	}
}
public void environmentPrepared() {
	// ....
}
public void contextPrepared() {
	// ....
}
public void contextLoaded() {
	// ....
}
public void started() {
	// ....
}
public void running() {
	// ....
}

этоSpringApplicationRunListenerколлекция

наблюдатьSpringApplicationRunListenersВсе методы можно увидеть, что он фактически используется для передачиSpringApplicationRunListenerУтилиты для связанных событий

Затем продолжайте наблюдатьgetSpringFactoriesInstancesИсходный код, чтобы увидеть, как он создает экземпляр объекта (этот метод используется во многих последующих местах)

private <T> Collection<T> getSpringFactoriesInstance(Class<T> type,
		Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = getClassLoader();
	// 加载对象名称
	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ПолучатьtypeсоответствующийFactoryNamesЯ не понимаю, что такое использование? Взгляд внутри метода

public static List<String> loadFactoryNames(Class<?>factoryClass, @Nullable ClassLoader classLoader) {
	String factoryClassName = factoryClass.getName();
	return loadSpringFactories(classLoader).getOrDefaul(factoryClassName, Collections.emptyList());
}

Продолжайте вloadSpringFactoriesвнутри метода

public static final String FACTORIES_RESOURCE_LOCATION ="META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactorie(@Nullable ClassLoader classLoader) {
	MultiValueMap<String, String> result = cache.ge(classLoader);
	if (result != null) {
		return result;
	}
	try {
		// 获取 META-INF/spring.factories 对应的资源
		Enumeration<URL> urls = (classLoader != null ?
				classLoader.getResource(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResource(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.entrySe()) {
				String factoryClassName = ((String)entry.getKey()).trim();
				for (String factoryName :StringUtils.commaDelimitedListToStringArray(String) entry.getValue())) {
					// 获取 factoryClassName 对应的多个valu(多个value用逗号分隔)
					result.add(factoryClassName,factoryName.trim());
				}
			}
		}
		// 缓存已经读取到的内容
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to loadfactories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

Вы можете быть сбиты с толку, когда увидите этоMETA-INF/spring.factoriesГде файл?文件里面有什么内容?

На самом деле этот файл хранится вSpring BootиSpring Boot autoconfigureБарная упаковка внутри (заинтересованные студенты могут скачать и распаковывать самообладание JAR),Spring BootСодержимое файла следующее:

# 完整内容请查看原文件

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

# 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

можно увидетьSpringApplicationRunListenerСоответствующее значениеEventPublishingRunListener

назадSpringFactoriesLoader.loadFactoryNamesВнутри метода можно обнаружить, что значение, полученное методом, на самом делеfactoryClassсуществуетMETA-INF/spring.factoriesКоллекция соответствующих достижений

Поняв этот метод, вернитесь кgetSpringFactoriesInstancesметод

private <T> Collection<T> getSpringFactoriesInstance(Class<T> type,
		Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = getClassLoader();
	// 获取 SpringApplicationRunListener 对应的实现类的名称集合
	Set<String> names = new LinkedHashSet<>(
			SpringFactoriesLoader.loadFactoryNames(type,classLoader));
	// 通过反射实例化对象
	List<T> instances = createSpringFactoriesInstances(type parameterTypes,
			classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

ужеgetRunListenersзаконченныйSpringApplicationRunListenerсоответствует экземпляру класса реализации и вызывает егоstartingметод

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

Из приведенного выше анализа мы видим, что фактический вызовEventPublishingRunListenerизstartingМетод, что ты делал в пути?

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

отправилApplicationStartingEventсобытие

продолжай искатьApplicationStartingEventпотребители событий, отspring.factoriesВсе предопределенные потребители событий можно найти в

# 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
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

Следующее, что нужно сделать, это выяснить у этих потребителейApplicationStartingEventПотребитель события (процесс поиска опущен), найдите следующих двух потребителей

  • LoggingApplicationListener Инициализировать систему ведения журнала

  • LiquibaseServiceLocatorApplicationListener (параметр liquibase.servicelocator.ServiceLocator) Если он существует, вместо этого используйте версию, связанную с Springboot.

пониматьApplicationStartingEventПосле мероприятия вернитесь кrunМетод продолжает исследоватьprepareEnvironment

private ConfigurableEnvironment prepareEnvironment(
		SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// 创建Environment对象
	ConfigurableEnvironment environment =getOrCreateEnvironment();
	configureEnvironment(environment,applicationArguments.getSourceArgs());
	// 发布ApplicationEnvironmentPreparedEvent事件
	listeners.environmentPrepared(environment);
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverte(getClassLoader())
				.convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}

Вот еще один постApplicationEnvironmentPreparedEventСобытие, продолжить поиск прослушивателей событий

  • FileEncodingApplicationListener Проверить, соответствует ли формат кодировки системного файла формату кодировки файла, настроенному в переменной окружения (при наличии соответствующей настройки - spring.mandatory-file-encoding), если кодировка не совпадает, сгенерировать исключение для предотвращенияSpringзапускать
  • AnsiOutputApplicationListener Включить ли AnsiOutput
  • Делегирование прослушивателя приложений Слушатели, настроенные прокси-сервером context.listener.classes
  • ClasspathLoggingApplicationListener Путь к классам вывода журнала
  • LoggingApplicationListener Настройте систему ведения журнала, logging.config, logging.level... и т. д.
  • ConfigfileApplicationListener. Это относительно важный объект мониторинга, конкретный метод реализован следующим образом
private void onApplicationEnvironmentPreparedEvent(
		ApplicationEnvironmentPreparedEvent event) {
	List<EnvironmentPostProcessor> postProcessors =loadPostProcessors();
	postProcessors.add(this);
	AnnotationAwareOrderComparator.sort(postProcessors);
	for (EnvironmentPostProcessor postProcessor :postProcessors) {
		postProcessor.postProcessEnvironmen(event.getEnvironment(),
				event.getSpringApplication());
	}
}

List<EnvironmentPostProcessor> loadPostProcessors() {
	return SpringFactoriesLoader.loadFactorie(EnvironmentPostProcessor.class,
			getClass().getClassLoader());
}

пройти черезspring.factories, вы можете увидеть здесь, чтобы загрузить следующееEnvironmentPostProcessorобъект

  • CloudFoundryVcapEnvironmentPostProcessor
  • SpringApplicationJsonEnvironmentPostProcessor
  • SystemEnvironmentPropertySourceEnvironmentPostProcessor
  • ConfigFileApplicationListener

Многие студенты могут задаться вопросомConfigFileApplicationListenerне существуетspring.factoriesфайл, зачем он там?

ФактическиConfigFileApplicationListenerсуществуетonApplicationEnvironmentPreparedEventметод, добавить себя вEnvironmentPostProcessorв списке объектов.

Наше основное вниманиеConfigFileApplicationListenerизpostProcessEnvironmentметод

public void postProcessEnvironment(ConfigurableEnvironmentenvironment,
		SpringApplication application) {
	addPropertySources(environment,application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironmentenvironment,
		ResourceLoader resourceLoader) {
	RandomValuePropertySource.addToEnvironment(environment);
	// 读取applicaiton.yml, application.properties等配置文件
	new Loader(environment, resourceLoader).load();
}

ConfigFileApplicationListenerслушалApplicationEnvironmentPreparedEventПосле того, как событие начнет чтение локального файла конфигурации

оSpringКак прочитать локальный файл конфигурации, перейдите кАнализ исходного кода Spring Boot — принцип загрузки конфигурационного файла

СоздайтеApplicationContextобъект

protected ConfigurableApplicationContextcreateApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			// 根据webApplicationType创建对应上下文对象
			switch (this.webApplicationType) {
			case SERVLET:
				contextClass = Class.forNam(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
				break;
			case REACTIVE:
				contextClass = Class.forNam(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
				break;
			default:
				contextClass = Class.forNam(DEFAULT_CONTEXT_CLASS);
			}
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
					"Unable create a defaultApplicationContext, "
							+ "please specify anApplicationContextClass",
					ex);
		}
	}
	return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

Вот по словамwebApplicationTypeРешите, какой типApplicationContextобъект, тоwebApplicationTypeКогда он был назначен?

public SpringApplication(ResourceLoader resourceLoader,Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must notbe null");
	this.primarySources = new LinkedHashSet<>(Arrays.asLis(primarySources));
	// 初始化webApplicationType
	this.webApplicationType =WebApplicationType.deduceFromClasspath();
	setInitializers((Collection) getSpringFactoriesInstance(
			ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstance(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass(;
}

Из вышеизложенного видно, что черезWebApplicationType.deduceFromClasspathметод инициализированwebApplicationType, продолжить код отслеживания

private static final String WEBFLUX_INDICATOR_CLASS = "org."
			+ "springframework.web.reactive.DispatcherHandler";
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
			+ "web.servlet.DispatcherServlet";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

static WebApplicationType deduceFromClasspath() {
	if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
			&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS null)
			&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS null)) {
		return WebApplicationType.REACTIVE;
	}
	for (String className : SERVLET_INDICATOR_CLASSES) {
		if (!ClassUtils.isPresent(className, null)) {
			return WebApplicationType.NONE;
		}
	}
	return WebApplicationType.SERVLET;
}

Как видно из кода вышеSpringПо токуclasspathЕсть ли соответствующий класс подwebApplicationTypeтип

инициализацияApplicationContextобъект

private void prepareContext(ConfigurableApplicationContextcontext,
		ConfigurableEnvironment environment,SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments, BannerprintedBanner) {
	// 初始化context
	context.setEnvironment(environment);
	postProcessApplicationContext(context);
	applyInitializers(context);
	// 发送ApplicationContextInitializedEvent消息
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans
	ConfigurableListableBeanFactory beanFactory =context.getBeanFactory();
	beanFactory.registerSingleto("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner",printedBanner);
	}
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverridin(this.allowBeanDefinitionOverriding);
	}
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	// 注册DemoApplication
	load(context, sources.toArray(new Object[0]));
	listeners.contextLoaded(context);
}

зарегистрирован здесьDemoApplicationприбытьSpringконтейнер, при подготовке к последующему сканированию бобов

Продолжайте идти дальшеrefreshContextметод, можно обнаружить, что он действительно выполняетсяAbstractApplicationContext.refreshметод

public void refresh() throws BeansException,IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		prepareRefresh();
		ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();
		prepareBeanFactory(beanFactory);
		try {
			postProcessBeanFactory(beanFactory);
			// 完成bean的加载
			invokeBeanFactoryPostProcessors(beanFactory);
			registerBeanPostProcessors(beanFactory);
			initMessageSource();
			initApplicationEventMulticaster();
			onRefresh();
			registerListeners();
			finishBeanFactoryInitialization(beanFactory);
			finishRefresh();
		}
		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered duringcontext initialization - " +
						"cancelling refresh attempt: " + ex;
			}
			destroyBeans();
			cancelRefresh(ex);
			throw ex;
		}
		finally {
			resetCommonCaches();
		}
	}
}

refreshМногое делается внутри метода. Например: полныйBeanFactoryнастраивать,BeanFactoryPostProcessor,BeanPostProcessorобратный вызов интерфейса,Beanзагрузка, настройка интернационализации и т. д.

ужеSpringИнициализация контейнера в основном завершена, и, наконец, вызовcallRunnersметод, выполнитьApplicationRunner,CommandLineRunnerинтерфейс.

private void callRunners(ApplicationContext context,ApplicationArguments args) {
	List<Object> runners = new ArrayList<>();
	runners.addAll(context.getBeansOfTyp(ApplicationRunner.class).values());
	runners.addAll(context.getBeansOfTyp(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);
		}
	}
}

Основным методом всего процесса запуска являетсяrefresh, внутри которого выполняется большая часть работы, необходимой для запуска контейнера. Из-за нехватки места последующие действияrefreshВнутренний анализ исходного кода для пониманияSpring BootнагрузкаBeanвесь процесс.