Введение
В прошлой статье мы в основном представили принцип автоматической настройки аннотации @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.