предисловие
Начнем с класса запуска SpringBoot, В этой статье в основном анализируется SpringApplication в классе запуска.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Видно, что главное в функции mainSpringApplication.run()
, который можно обсудить в двух частях:
- Процесс построения SpringApplication
- Метод run() SpringApplication
Инициализация SpringApplication
Сначала введите конструктор SpringApplication, сначала конструктор с одним параметром, а затем введите конструктор с двумя параметрами. ResourceLoader — это загрузчик ресурсов Spring. Пользовательский ResourceLoader не передается, поэтому он равен NULL, а параметр primarySources — это то, что мы Войдите в класс запуска Application.class.
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//1. 推断应用类型
this.webApplicationType = deduceWebApplicationType();
//2. initializer初始化模块,加载ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//3. 加载监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//4. 配置应用main方法所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
Инициализация SpringApplication в основном включает следующие 4 шага:
- Вывод типа приложения
- Загрузите инициализатор ApplicationContextInitializer
- Создать прослушиватель приложений
- Установите класс, в котором находится метод main() приложения.
1. Определите тип приложения
this.webEnvironment=deduceWebApplicationType();Определите тип приложения, будь то приложение сервлета, реактивное приложение или нет,Эти три типа определены в webEnvironment.
2. Загрузите инициализатор ApplicationContextInitializer
setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class)):Найдите и загрузите все доступные ApplicationContextInitializers в пути к классам приложения через SpringFactoriesLoader.
в метод loadFactoryNames, затемВведите метод loadSpringFactories, чтобы получить информацию о конфигурации всех файлов META-INF/spring.factories в текущем ClassLoader.затем черезloadSpringFactories(classloader).getOrDefault(factoryClassName,Collections.emptyList())Получить значение указанной фабрики из карты информации о конфигурации всех файлов META-INF/spring.factories.
По умолчанию есть четыре класса с ключом ApplicationContextInitializer, найденным из файла spring.factories, как показано на рисунке выше.Для ApplicationContextInitializer это инициализатор приложения, выполняющий некоторую работу по инициализации.
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
3. Создайте прослушиватель приложений
Метод setListeners() аналогичен методу setInitializers(), за исключением того, что он использует SpringFactoriesLoader для поиска и загрузки всех доступных ApplicationListeners в META-INT/spring.factories в пути к классам приложения.
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
ApplicationListener, прослушиватель событий приложения (ApplicationEvent):
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
Более подробный анализ можно найти в моей предыдущей статье:Инициализировать данные при запуске в серии статей springboot
4. Установите класс, в котором находится метод main() приложения.
На последнем шаге конструктора SpringApplication выведите и установите класс определения основного метода в соответствии со стеком вызовов.
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;
}
Метод запуска SpringApplication
После инициализации и настройки экземпляра SpringApplication можно приступить к логике метода запуска, для этого метода запуска я разделю его на следующие пункты для пошагового анализа, а StopWatch — это класс инструмента, который в основном используется для записи времени работы программы Подробно не представлено.
public ConfigurableApplicationContext run(String... args) {
//构造一个任务执行观察期
StopWatch stopWatch = new
StopWatch();
//开始执行,记录开始时间
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//1
configureHeadlessProperty();
//2
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//3
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//4
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//5
configureIgnoreBeanInfo(environment);
//6
Banner printedBanner = printBanner(environment);
//7
context = createApplicationContext();
//8
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//9
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//10
refreshContext(context);
//2.0版本中是空实现
//11
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
//12
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;
}
Метод запуска SpringApplication в основном делится на следующие шаги:
- Настройки безголового режима
- Загрузите прослушиватель SpringApplicationRunListeners
- Инкапсулирует объект ApplicationArguments
- Настройка модулей среды
- Настройте информацию о компонентах для игнорирования на основе информации о среде
- Баннер настраивает яйца SpringBoot
- Создать контекст приложения ApplicationContext
- Загрузить SpringBootExceptionReporter
- Базовая конфигурация свойства ApplicationContext
- Обновить контекст приложения
- Узнайте, зарегистрированы ли CommandLineRunner/ApplicationRunner
1. Настройки безголового режима
configureHeadlessProperty()
Установите безголовый режим, то есть установите системное свойство java.awt.headless, которое является режимом J2SE, используемым для конфигурации системы, когда экран дисплея, клавиатура или мышь отсутствуют, для этого свойства будет установлено значение true, больше информация может относиться кздесь
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
...
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
2. Загрузите прослушиватель SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
getRunListeners(args)
также черезSpringFactoriesLoader
отMETA-INF/spring.factories
нашел и загрузилSpringApplicationRunListener
. Этот класс фактически отслеживает выполнение метода запуска SpringApplication.
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
.....
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
//通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListner
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
Прослушиватель SpringApplicationRunListener здесь отличается от прослушивателя ApplicationListener, загруженного в SpringApplication. SpringApplicationRunListener — это новый класс SpringBoot. SpringApplicationRunListener в настоящее время имеет только один класс реализации, EventPublishingRunListener. Хотя это новое дополнение, между ними существует связь, и связь между ними связана с помощью трансляции SpringApplicationEvent от ApplicationEventMulticaster.
Для более подробного анализа см.:Процесс запуска SpringBoot анализа исходного кода SpringBoot
3. Инкапсулируйте объект ApplicationArguments
Инкапсулируйте параметр args какApplicationArguments
объект
public DefaultApplicationArguments(String[] args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
Официальный сайтApplicationArguments
Объяснение следующее
4. Настройте модуль среды
в соответствии сlisteners
иapplicationArguments
Создайте и настройте среду, которую будет использовать текущее приложение SpringBoot.
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
Повторить все вызовы SpringApplicationRunListenerenviromentPrepared()
Метод заключается в том, чтобы объявить, что среда, используемая текущим приложением SpringBoot, готова.
5. Настройте игнорирование информации о компонентах в соответствии с информацией о среде.
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
if (System.getProperty(
CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
Boolean.class, Boolean.TRUE);
System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
ignore.toString());
}
}
6. Баннер настройки SpringBoot egg
Печать логотипа баннера — это то, что появляется при запуске проекта SpringBoot.Spring
Слова, конечно, мы тоже можем настроить под баннер, так что больше тут говорить не буду
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null)
? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
7. Создайте контекст приложения ApplicationContext
createApplicationContext()
В зависимости от того, установил ли пользователь явно тип applicationContextClass и результата логического вывода фазы инициализации SpringApplication, определяется, какой тип ApplicationContext следует создать для текущего приложения SpringBoot, и создание завершается.
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
protected ConfigurableApplicationContext createApplicationContext() {
//用户是否明确设置了applicationContextClass,在SpringApplication中有对应的setter方法
Class<?> contextClass = this.applicationContextClass;
//如果没有主动设置
if (contextClass == null) {
try {
//判断当前应用的类型,也就是之前SpringApplication初始化阶段的推断结果
switch (this.webApplicationType) {
//servlet应用程序
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
//reactive响应式程序
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);
}
Тип ApplicationContext на официальном сайте SpringBoot определяется следующим образом:
- Когда SpringMVC существует, используйте AnnotationConfigServletWebServerApplicationContext
- Когда SpringMVC не существует, когда существует реактивный Spring WebFlux, используйте AnnotationConfigReactiveWebServerApplicationContext
- Если ничего из перечисленного выше, используйте AnnotationConfigApplicationContext по умолчанию.
- Существует метод установки ApplicationContext в SpringApplication.При использовании SpringApplication в тестах JUnit обычно необходимо установить ApplicationContext
8. Загрузите SpringBootExceptionReporter
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
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;
}
Вот также значение значения полного имени класса, ключ SpringBootExceptionReporter в META-INF/spring.factories через SpringFactoriesLoader
-
SpringBootExceptionReporter
это интерфейс обратного вызова, используемый для поддержкиSpringApplication
Запуск пользовательского отчета об ошибках. Существует способ сообщить об ошибке запуска - Его класс реализации:
org.springframework.boot.diagnostics.FailureAnalyzers
Используется для запуска загрузки из spring.factoriesFailureAnalyzer
иFailureAnalysisReporter
пример
9. Базовая конфигурация свойства ApplicationContext
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置应用的环境
context.setEnvironment(environment);
//对 context 进行了预设置
postProcessApplicationContext(context);
applyInitializers(context);
遍历调用SpringApplicationRunListener的contextPrepared()方法,通告SpringBoot应用使用的ApplicationContext准备好了
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
//遍历调用SpringApplicationRunListener的contextLoaded()方法,通告ApplicationContext装填完毕
listeners.contextLoaded(context);
}
1). applyInitializers(context);
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);
}
}
Перейдите и вызовите метод initialize(applicationContext) этих ApplicationContextInitializers для дальнейшей обработки уже созданного ApplicationContext.
2). load(ApplicationContext context, Object[] sources)
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
Настройте загрузчик ресурсов для загрузки различных bean-компонентов в объект ApplicationContext.
10. Обновите контекст приложения
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
Введите внутренний метод refresh(), подготовьте фабрику компонентов, требуемую средой, и сгенерируйте компоненты, требуемые средой, через фабрику.
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
11. afterRefresh()
Этот метод вызывается после обновления контекста, и в настоящее время нет операции
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}
12. callRunner()
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);
}
}
}
Узнайте, зарегистрирован ли в текущем ApplicationContext объект CommandLineRunner или ApplicationRunner, и если да, пройдите и выполните их.
Краткое описание процесса запуска SpringBoot
Приведенный выше анализ от инициализации SpringApplication до выполнения метода SpringApplication.run() в основном анализируется шаг за шагом в соответствии с порядком вызовов его внутренних функций.Содержимого так много, что легко запутать людей. Нашла картинку в интернете, картинка оттудаАнализ процесса запуска SpringBoot, рисунок более четкий, и включен весь процесс запуска SpringBoot
Затем суммируйте наиболее важные шаги в методе run:- Загрузите прослушиватель SpringApplicationRunListeners
- Настройка модулей среды
- Создать контекст приложения ApplicationContext
- Базовая конфигурация свойства ApplicationContext
- Обновите контекст приложения, чтобы сгенерировать bean-компоненты, требуемые средой.
резюме
Приведенный выше анализ основан на версии SpringBoot 2.0.В предыдущих версиях могут быть некоторые отклонения в содержании, но общая идея аналогична. В процессе чтения исходного кода я прочитал много статей в блогах, проанализированных предшественниками, и я также извлек уроки из процесса их анализа.