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
весь процесс.