Глубокое понимание основных принципов SpringBoot (2) -------- процесс инициализации (метод запуска)

Spring Boot

Введение

  В прошлой статье мы в основном представили принцип автоматической настройки аннотации @SpringbootApplication, поэтому сначала давайте рассмотрим, что эта аннотация в основном делает для нашего проекта springboot: Мы можем резюмировать ключевые шаги автоматической настройки и соответствующие аннотации следующим образом:

1. @Configuration& и @Bean------>>> bean-конфигурация на основе java-кода

2.@Conditional--------->>>>>>Установить условные зависимости автоматической конфигурации

3. @EnableConfigurationProperties и @ConfigurationProperties->чтение файлов конфигурации в bean-компоненты.

4. @EnableAutoConfiguration, @AutoConfigurationPackage и @Import-> реализуют обнаружение и загрузку компонентов.

  Сегодня давайте проанализируем процесс его запуска через исходный код.

Эта статья основана на версии 2.0.4.RELEASE. Чтобы прочитать эту статью, вам необходимо иметь некоторые основы Java и Spring Framework. Если вы не знаете, что такое Spring Boot, рекомендуется прочитать учебник по Spring Boot на официальный сайт.

2. Начальный класс Spring Boot

Выше приведен самый простой и наиболее распространенный начальный класс для Spring Boot. Требования к начальному классу: первый класс с основным методом в пакете верхнего уровня, использование аннотации @SpringBootApplication для включения функций Spring Boot и использование метода SpringApplication.run для запуска проекта Spring Boot.

Во-первых, давайте взглянем на вызывающий исходный код метода run в этом классе:

Первый параметр primarySource: загружен основной класс ресурсов;

Второй параметр args: параметры приложения, передаваемые приложению.

Сначала используйте класс основного ресурса для создания экземпляра объекта SpringApplication, а затем вызовите метод run этого объекта, поэтому мы анализируем исходный код запуска в два этапа.

3. Процесс создания экземпляра SpringApplication

Выполните приведенный выше запуск, чтобы ввести следующий метод:

Войдя в SpringApplication, вы можете увидеть следующий исходный код:

Как видно из приведенного выше исходного кода, весь процесс создания экземпляра состоит из 7 шагов:

1. Загрузчик ресурсов инициализации ресурсов имеет значение null

this.resourceLoader = resourceLoader;

2. Утвердить, что класс основного загруженного ресурса не может быть нулевым, иначе будет сообщено об ошибке

Assert.notNull(primarySources, "PrimarySources must not be null");

3. Инициализируйте коллекцию классов ресурсов основной нагрузки и выполните дедупликацию.

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

4. Сделайте вывод о текущем типе веб-приложения

this.webApplicationType = WebApplicationType.deduceFromClasspath();

Здесь перейдите к методу WebApplicationType, чтобы увидеть исходный код и связанные с ним методы построения:

public enum WebApplicationType {

	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 */
	NONE,

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 */
	 
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 */
	REACTIVE;

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

	static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
		if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
			return WebApplicationType.SERVLET;
		}
		if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
			return WebApplicationType.REACTIVE;
		}
		return WebApplicationType.NONE;
	}

	private static boolean isAssignable(String target, Class<?> type) {
		try {
			return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
		}
		catch (Throwable ex) {
			return false;
		}
	}

}

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

5. Установите инициализатор контекста приложения

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

Войдя в ApplicationContextInitializer, мы можем узнать его роль:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);

}

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

Давайте посмотрим на исходный код метода setInitializers, который на самом деле предназначен для инициализации коллекции экземпляров инициализатора контекста приложения ApplicationContextInitializer.

	public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>();
		this.initializers.addAll(initializers);
	}

Наконец, давайте взглянем на основной метод getSpringFactoriesInstances, Исходный код выглядит следующим образом:

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

Настройка инициализатора контекста приложения может быть разделена на следующие 5 шагов. Вот ядро ​​инстанцирования:

5.1) Получить текущий загрузчик класса контекста потока

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

5.2) Получите набор имен экземпляров ApplicationContextInitializer и удалите дубликаты.

Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

Исходный код loadFactoryNames выглядит следующим образом:

	public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}
	
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(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.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

Разрешает и получает все настроенные имена путей к классам для интерфейса ApplicationContextInitializer в соответствии с файлом META-INF/spring.factories в пути к классам.

Конфигурация spring-boot-autoconfigure-2.0.4.RELEASE.jar!/META-INF/spring.factories, связанная с инициализатором, выглядит следующим образом:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# Auto Configure
......

5.3) Создайте список экземпляров инициализатора на основе указанного выше пути к классам.

List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

5.4) Порядок списка экземпляров инициализатора

AnnotationAwareOrderComparator.sort(instances);

5.5) Вернуть экземпляр объекта

return instances;

6. Настройте прослушиватель

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

Какова роль ApplicationListener? Исходный код выглядит следующим образом. (Я напишу еще одно приложение прослушивателя springboot2, когда у меня будет время)

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

}

Глядя на исходный код, этот интерфейс наследует интерфейс java.util.EventListener JDK и реализует режим наблюдателя. Обычно он используется для определения интересующего типа события. Тип события ограничен подклассом ApplicationEvent, который также наследует java.util.EventListener интерфейса JDK.util.EventObject.

Метод установки слушателя и установки вызова инициализатора одинаковый, но входящий тип отличается Тип интерфейса установки слушателя: getSpringFactoriesInstances, соответствующий spring-boot-autoconfigure-2.0.4.RELEASE.jar!/ Содержимое конфигурации файла META -INF/spring.factories находится в указанном выше файле конфигурации: Application Listeners.

Из файла видно, что на данный момент есть только один прослушиватель BackgroundPreinitializer.

7. Определите основной класс приложения входа

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

Путем создания исключения во время выполнения, обхода имен методов в стеке исключений, получения кадра стека с именем метода main, а затем возврата к классу после получения имени класса входа.

4. Резюме

Сегодня я в основном анализирую исходный код экземпляра инициализации SpringBoot.В этой главе временно анализируются:

В следующей главе мы продолжим анализ метода run.