# Практический анализ внешней конфигурации Spring Boot

Spring Boot задняя часть сервер Spring

Источник: технология CreditEase.college.creditease.cn/Автор: Ши Цзяньвэй

1. Анализ процесса

процедура въезда

существуетSpringApplication#run(String... args)В методе ключевой процесс внешней конфигурации разделен на следующие четыре шага.

public ConfigurableApplicationContext run(String... args) {
    ...
    SpringApplicationRunListeners listeners = getRunListeners(args); // 1
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                                                                 applicationArguments); // 2
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments,
                       printedBanner); // 3
        refreshContext(context); // 4
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                .logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    ...
}

Карта ключевых процессов

Подробное объяснение ключевых процессов

Для четырех шагов, отмеченных в программе входа, анализ выглядит следующим образом.

1,SpringApplication#getRunListeners

нагрузкаMETA-INF/spring.factoriesПолучатьSpringApplicationRunListenerКоллекция экземпляров хранимого объектаEventPublishingRunListenerтип и пользовательскийSpringApplicationRunListenerТип реализации

2,SpringApplication#prepareEnvironment

prepareEnvironmentВ методе основные три шага заключаются в следующем.

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment(); // 2.1
    configureEnvironment(environment, applicationArguments.getSourceArgs()); // 2.2
    listeners.environmentPrepared(environment); // 2.3
    ...
    return environment;
}
2.1,getOrCreateEnvironmentметод

существуетWebApplicationType.SERVLETПод типом веб-приложения он создастStandardServletEnvironment, эта статья начинается сStandardServletEnvironmentНапример, иерархия классов выглядит следующим образом.

при созданииStandardServletEnvironment,StandardServletEnvironmentотецAbstractEnvironmentпередачаcustomizePropertySourcesметод, выполнитStandardServletEnvironment#customizePropertySourcesа такжеStandardEnvironment#customizePropertySources, исходный код выглядит следующим образом

AbstractEnvironment

public AbstractEnvironment() {
    customizePropertySources(this.propertySources);
    if (logger.isDebugEnabled()) {
        logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
    }
}

StandardServletEnvironment#customizePropertySources

/** Servlet context init parameters property source name: {@value} */
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";

/** Servlet config init parameters property source name: {@value} */
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";

/** JNDI property source name: {@value} */
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
    propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
    }
    super.customizePropertySources(propertySources);
}

StandardEnvironment#customizePropertySources

/** System environment property source name: {@value} */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

/** JVM system properties property source name: {@value} */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
    propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,getSystemEnvironment());
}

PropertySourcesзаказ:

  1. servletConfigInitParams
  2. servletContextInitParams
  3. jndiProperties
  4. systemProperties
  5. systemEnvironment

PropertySourcesа такжеPropertySourceОтношение 1 к N

2.2,configureEnvironmentметод

передачаconfigurePropertySources(environment, args), заданный в методеEnvironmentизPropertySources, ВключатьdefaultPropertiesа такжеSimpleCommandLinePropertySource(командаLineArgs),PropertySourcesДобавить кdefaultPropertiesВ конце добавитьSimpleCommandLinePropertySource(commandLineArgs) на передний план

PropertySourcesзаказ:

  1. commandLineArgs

  2. servletConfigInitParams

  3. servletContextInitParams

  4. jndiProperties

  5. systemProperties

  6. systemEnvironment

  7. defaultProperties

2.3,listeners.environmentPreparedметод

будет выполняться в приоритетном порядкеSpringApplicationRunListener#environmentPrepared,НапримерEventPublishingRunListenerи обычайSpringApplicationRunListener

  • EventPublishingRunListenerвыпускатьApplicationEnvironmentPreparedEventмероприятие

    • ConfigFileApplicationListenerмониторApplicationEventсобытие, обработкаApplicationEnvironmentPreparedEventсобытие, загрузить всеEnvironmentPostProcessorВключите себя, затем сделайте обратные вызовы методов по порядку
      • ConfigFileApplicationListener#postProcessEnvironmentобратный вызов метода, затемaddPropertySourcesвызов методаRandomValuePropertySource#addToEnvironment, добавьте random после systemEnvironment, а затем добавьте источник атрибута файла конфигурации (подробности см. в исходном кодеConfigFileApplicationListener.Loader#load()
  • точка расширения

    • настроитьSpringApplicationRunListener, переписатьenvironmentPreparedметод

    • настроитьEnvironmentPostProcessor

    • настроитьApplicationListenerмониторApplicationEnvironmentPreparedEventмероприятие

ConfigFileApplicationListener, то естьEnvironmentPostProcessor,Опять такиApplicationListener, иерархия классов выглядит следующим образом

@Override
public void onApplicationEvent(ApplicationEvent event) {
    // 处理 ApplicationEnvironmentPreparedEvent 事件
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent(
            (ApplicationEnvironmentPreparedEvent) event);
    }
    // 处理 ApplicationPreparedEvent 事件
    if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent(event);
    }
}

private void onApplicationEnvironmentPreparedEvent(
    ApplicationEnvironmentPreparedEvent event) {
    // 加载 META-INF/spring.factories 中配置的 EnvironmentPostProcessor
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    // 加载自己 ConfigFileApplicationListener
    postProcessors.add(this);
    // 按照 Ordered 进行优先级排序
    AnnotationAwareOrderComparator.sort(postProcessors);
    // 回调 EnvironmentPostProcessor
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessEnvironment(event.getEnvironment(),
                                             event.getSpringApplication());
    }
}

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

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
                                   SpringApplication application) {
    addPropertySources(environment, application.getResourceLoader());
}

/**
  * Add config file property sources to the specified environment.
  * @param environment the environment to add source to
  * @param resourceLoader the resource loader
  * @see #addPostProcessors(ConfigurableApplicationContext)
  */
protected void addPropertySources(ConfigurableEnvironment environment,
                                  ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    // 添加配置文件的属性源
    new Loader(environment, resourceLoader).load();
}

RandomValuePropertySource

public static void addToEnvironment(ConfigurableEnvironment environment) {
    // 在 systemEnvironment 后面添加 random
    environment.getPropertySources().addAfter(
        StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
        new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
    logger.trace("RandomValuePropertySource add to Environment");
}

Добавьте источник свойств для файла конфигурации:

воплощать в жизньnew Loader(environment, resourceLoader).load();, передачаload(Profile, DocumentFilterFactory, DocumentConsumer)(GetSearchLocations() Получает расположение файла конфигурации, можно указать параметры spring.config.additional-location, spring.config.location, spring.config.name или использовать значение по умолчанию), затем вызватьaddLoadedPropertySources -> addLoadedPropertySource(загрузить найденноеPropertySourceприбытьPropertySources, и обязательно поместите его перед defaultProperties )

Место поиска по умолчанию, настроенное как"classpath:/,classpath:/config/,file:./,file:./config/", порядок поиска от конца к началу

PropertySourcesзаказ:

  1. commandLineArgs
  2. servletConfigInitParams
  3. servletContextInitParams
  4. jndiProperties
  5. systemProperties
  6. systemEnvironment
  7. random
  8. application.properties ...
  9. defaultProperties

3.SpringApplication#prepareContext

prepareContextВ методе основные три шага заключаются в следующем.

private void prepareContext(ConfigurableApplicationContext context,
                            ConfigurableEnvironment environment, 
                            SpringApplicationRunListeners listeners,
                            ApplicationArguments applicationArguments, 
                            Banner printedBanner) {
    ...
    applyInitializers(context); // 3.1
    listeners.contextPrepared(context); //3.2
    ...
    listeners.contextLoaded(context); // 3.3
}
3.1,applyInitializersметод

будет перебирать всеApplicationContextInitializer#initialize

  • точка расширения
    • настроитьApplicationContextInitializer
3.2,listeners.contextPreparedметод

будет выполняться в приоритетном порядкеSpringApplicationRunListener#contextPrepared,НапримерEventPublishingRunListenerи обычайSpringApplicationRunListener

  • точка расширения
    • настроитьSpringApplicationRunListener, переписатьcontextPreparedметод
3.3,listeners.contextLoadedметод

будет выполняться в приоритетном порядкеSpringApplicationRunListener#contextLoaded,НапримерEventPublishingRunListenerи обычайSpringApplicationRunListener

  • EventPublishingRunListenerвыпускатьApplicationPreparedEventмероприятие

    • ConfigFileApplicationListenerмониторApplicationEventобработка событийApplicationPreparedEventмероприятие
  • точка расширения

    • настроитьSpringApplicationRunListener, переписатьcontextLoadedметод
    • настроитьApplicationListener, мониторApplicationPreparedEventмероприятие

ConfigFileApplicationListener

@Override
public void onApplicationEvent(ApplicationEvent event) {
    // 处理 ApplicationEnvironmentPreparedEvent 事件
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent(
            (ApplicationEnvironmentPreparedEvent) event);
    }
    // 处理 ApplicationPreparedEvent 事件
    if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent(event);
    }
}

private void onApplicationPreparedEvent(ApplicationEvent event) {
    this.logger.replayTo(ConfigFileApplicationListener.class);
    addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
}

// 添加 PropertySourceOrderingPostProcessor 处理器,配置 PropertySources
protected void addPostProcessors(ConfigurableApplicationContext context) {
    context.addBeanFactoryPostProcessor(
        new PropertySourceOrderingPostProcessor(context));
}

PropertySourceOrderingPostProcessor

// 回调处理(在配置类属性源解析)
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
    throws BeansException {
    reorderSources(this.context.getEnvironment());
}

// 调整 PropertySources 顺序,先删除 defaultProperties, 再把 defaultProperties 添加到最后
private void reorderSources(ConfigurableEnvironment environment) {
    PropertySource<?> defaultProperties = environment.getPropertySources()
        .remove(DEFAULT_PROPERTIES);
    if (defaultProperties != null) {
        environment.getPropertySources().addLast(defaultProperties);
    }
}

PropertySourceOrderingPostProcessorдаBeanFactoryPostProcessor

4.SpringApplication#refreshContext

будет осуществляться@ConfigurationАнализ источника атрибута класса конфигурации, обработка@PropertySource annotations on your @Configurationклассы, но порядок после defaultProperties, следующее будет корректировать defaultProperties до конца

AbstractApplicationContext#refreshпередачаinvokeBeanFactoryPostProcessors (PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors), затем сделайтеBeanFactoryPostProcessorобработка обратного вызова, напримерPropertySourceOrderingPostProcessorобратный вызов (см. исходный код выше)

PropertySourcesзаказ:

  1. commandLineArgs

  2. servletConfigInitParams

  3. servletContextInitParams

  4. jndiProperties

  5. systemProperties

  6. systemEnvironment

  7. random

  8. application.properties ...

  9. @PropertySource annotations on your @Configuration classes

  10. defaultProperties

Этот метод не рекомендуется, рекомендуется подготовиться перед обновлением контекста,@PropertySourceЗагружено слишком поздно, чтобы повлиять на автоконфигурацию

2. Расширение внешних источников свойств конфигурации

1. На основеEnvironmentPostProcessorрасширять

public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor

2. На основеApplicationEnvironmentPreparedEventрасширять

public class ApplicationEnvironmentPreparedEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>

3. На основеSpringApplicationRunListenerрасширять

public class CustomSpringApplicationRunListener implements SpringApplicationRunListener, Ordered

Может переопределять методы environmentPrepared, contextPrepared, contextLoaded для расширения.

4. На основеApplicationContextInitializerрасширять

public class CustomApplicationContextInitializer implements ApplicationContextInitializer

Что касается интеграции с клиентом Spring Cloud Config, расширение внешней загрузки конфигурации (привязанной к серверу конфигурации, инициализированной с помощью удаленных источников свойств)Environment), обратитесь к исходному кодуPropertySourceBootstrapConfiguration(правдаApplicationContextInitializerрасширение),ConfigServicePropertySourceLocator#locate

Получить удаленные источники свойствRestTemplateПолучено путем отправки запроса GET по адресу http://{spring.cloud.config.uri}/{spring.application.name}/{spring.cloud.config.profile}/{spring.cloud.config.label}.

5. На основеApplicationPreparedEventрасширять

public class ApplicationPreparedEventListener implements ApplicationListener<ApplicationPreparedEvent>

6. Расширьте реальный бой

6.1 Расширенная конфигурация

Добавить файл конфигурации в путь к классамMETA-INF/spring.factories, содержание следующее

# Spring Application Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
springboot.propertysource.extend.listener.CustomSpringApplicationRunListener

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
springboot.propertysource.extend.initializer.CustomApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
springboot.propertysource.extend.event.listener.ApplicationEnvironmentPreparedEventListener,\
springboot.propertysource.extend.event.listener.ApplicationPreparedEventListener

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
springboot.propertysource.extend.processor.CustomEnvironmentPostProcessor

Для расширения можно выбрать одно из вышеперечисленных расширений, но время загрузки источника атрибута отличается.

6.2 Расширенный пример кода

GitHub.com/сотня тысяч 823/tickets…

PropertySourcesзаказ:

propertySourceName: [ApplicationPreparedEventListener], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [CustomSpringApplicationRunListener-contextLoaded], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [CustomSpringApplicationRunListener-contextPrepared], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [CustomApplicationContextInitializer], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [bootstrapProperties], propertySourceClassName: [CompositePropertySource]

propertySourceName: [configurationProperties], propertySourceClassName: [ConfigurationPropertySourcesPropertySource]

propertySourceName: [CustomSpringApplicationRunListener-environmentPrepared], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [CustomEnvironmentPostProcessor-dev-application], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [ApplicationEnvironmentPreparedEventListener], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [commandLineArgs], propertySourceClassName: [SimpleCommandLinePropertySource]

propertySourceName: [servletConfigInitParams], propertySourceClassName: [StubPropertySource]

propertySourceName: [servletContextInitParams], propertySourceClassName: [ServletContextPropertySource]

propertySourceName: [systemProperties], propertySourceClassName: [MapPropertySource]

propertySourceName: [systemEnvironment], propertySourceClassName: [OriginAwareSystemEnvironmentPropertySource]

propertySourceName: [random], propertySourceClassName: [RandomValuePropertySource]

propertySourceName: [applicationConfig: [classpath:/extend/config/springApplicationRunListener.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [applicationConfig: [classpath:/extend/config/applicationListener.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [applicationConfig: [classpath:/extend/config/applicationContextInitializer.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [applicationConfig: [classpath:/extend/config/environmentPostProcessor.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [applicationConfig: [classpath:/extend/config/application.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [applicationConfig: [classpath:/extend/config/config.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [applicationConfig: [classpath:/application.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [springCloudClientHostInfo], propertySourceClassName: [MapPropertySource]

propertySourceName: [applicationConfig: [classpath:/bootstrap.properties]], propertySourceClassName: [OriginTrackedMapPropertySource]

propertySourceName: [propertySourceConfig], propertySourceClassName: [ResourcePropertySource]

propertySourceName: [defaultProperties], propertySourceClassName: [MapPropertySource]

bootstrapProperties - получить источники свойств удаленного (конфигурационного сервера)

Порядок загрузки также может относиться к http://{host}:{port}/actuator/env.

PropertySourcesПорядок модульного тестирования:

@TestPropertySource#properties
@SpringBootTest#properties
@TestPropertySource#locations

3. Ссылки

docs.spring.IO/весенняя загрузка…