Примечание: эта статья основана на Springboot версии 2.1.11
Говоря о конфигурации, что вы можете придумать?
Можно сказать, что в ежедневном процессе разработки, эксплуатации и обслуживания конфигурация чрезвычайно важна, поскольку она может повлиять на нормальный запуск или нормальную работу приложения. Я полагаю, что в предыдущую эпоху Spring xml многих людей беспокоила куча конфигураций xml.Кроме того, есть конфигурация подключения к базе данных, конфигурация кеша, конфигурация реестра, конфигурация сообщений и т. д., с которыми, я думаю, все знакомы. .
Конфигурация может быть уподоблена «ключу» для разработчиков или обслуживающего персонала, который может использоваться для запуска нашей программы, и этот «ключ» может использоваться для включения или выключения определенной функции приложения. Итак, зачем вам нужна конфигурация и какое значение она имеет для приложения?
Что означает конфигурация для компонентов и приложений фреймворка
Что означает конфигурация для компонентов и приложений платформы? Насколько я понимаю, компоненты и приложения фреймворка можно сделать гибкими.Благодаря настройке компонент фреймворка или приложение может работать в разных средах и сценариях без необходимости изменения собственного кода. Например, в Dubbo пользователи могут настроить Dubbo для регистрации служб в разных реестрах, таких как nacos, zookeeper, SOFARegistry и т. д. В качестве другого примера у меня есть приложение, которому необходимо подключаться к разным базам данных в среде разработки и рабочей среде. , но я не хочу вносить изменения в код для адаптации к различным средам, поэтому я также могу использовать метод конфигурации для управления. Конфигурация может сделать компоненты и приложения платформы гибкими и не сильно связанными. В определенном сценарии или среде она может существовать во многих формах, например в файлах, центрах конфигурации и системных переменных среды. Для программ JAVA также могут быть параметры командной строки или -D параметры. Можно сказать, что любой отличный фреймворк или приложение неотделимо от конфигурации.
Итак, как лучшая среда в экологии языка Java, как Spring управляет конфигурацией и использует ее? В этой статье будет сделан подробный разбор в настройке конфигурации в SpringBoot.
Конфигурация в Spring Boot
В официальной статье Spring Boot для описания конфигурации отведена отдельная глава и много места, видно, что конфигурация очень важна для Spring Boot. Spring Boot позволяет пользователям внедрить конфигурацию, чтобы один и тот же код приложения можно было использовать в разных средах.Пользователи могут использовать файлы свойств, файлы YAML, переменные среды и аргументы командной строки для материализации конфигурации. Значения свойств могут быть внедрены непосредственно в bean-компоненты с помощью аннотации @Value, доступны через абстракцию среды Spring или привязаны к структурированным объектам через @ConfigurationProperties.
В повседневной разработке для конфигурации в SpringBoot вы можете напрямую думать о application.properties, На самом деле, как вы можете видеть из официальной документации SpringBoot, существует целых 17 способов для SpringBoot получить конфигурацию; в то же время, Spring Boot также предоставляет очень специальный PropertyOrder, позволяющий пользователям переопределять определенные значения свойств в соответствующих сценариях.Ниже приведен приоритетный порядок загрузки свойств, описанный в официальной документации:
- 1. Установите глобальные свойства в Devtools в вашем домашнем каталоге (~/.spring-boot-devtools.properties, когда devtools активирован).
- 2. Аннотация @TestPropertySource, используемая в тесте.
- 3. Атрибут свойств, используемый в тесте, может быть @SpringBootTest, а тестовая аннотация используется для тестирования части приложения.
- 4. Параметры командной строки.
- 5. Свойства из SPRING_APPLICATION_JSON (встроенный JSON, встроенный в переменные среды или свойства системы)
- 6. Параметры инициализации ServletConfig.
- 7. Параметры инициализации ServletContext.
- 8. Свойства JNDI из java:comp/env.
- 9. Системные свойства Java (System.getProperties()).
- 10. Переменные среды операционной системы.
- 11. RandomValuePropertySource только со свойствами random.*.
- 12. Файл свойств приложения для конкретного профиля (application-{profile}.properties и переменные YAML) вне упакованного фатара.
- 13. Файл свойств приложения (application-{profile}.properties и переменные YAML) указанного профиля внутри упакованного фатжара.
- 14. Файл свойств приложения (application.properties и переменные YAML) вне упакованного фатжара.
- 15. Файл свойств приложения (application.properties и переменные YAML) внутри упакованного фатжара.
- 16. Аннотации @PropertySource к классам @Configuration.
- 17. Свойства по умолчанию (указаны с помощью SpringApplication.setDefaultProperties).
Я считаю, что большинство из них — это вещи, которые вы никогда раньше не использовали. таунхаус в соответствующем сценарии!
Конфигурация в Spring в конечном итоге управляется объектом Environment, который мы часто называем средой Spring. Например, вы можете получить значения конфигурации из Среды следующими способами:
ConfigurableEnvironment environment = context.getEnvironment();
environment.getProperty("key");
Так как же устроена Окружающая среда? Какова связь между средой и конфигурацией?
Сборка среды
Создание среды происходит в prepareEnvironment.Чтобы узнать больше о процессе запуска SpringBoot, вы можете обратиться к этой статьеSpringBoot Series — Анализ процесса запуска.
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
// 标准的 web 应用
case SERVLET:
return new StandardServletEnvironment();
// webflux 应用
case REACTIVE:
return new StandardReactiveWebEnvironment();
// 非web应用
default:
return new StandardEnvironment();
}
}
Эта статья основана на анализе не веб-приложений, и весь анализ в основном вращается вокруг класса StandardEnvironment.
Структура наследования класса среды:
systemProperties & systemEnvironment
Во время создания объекта StandardEnvironment инициализируются два источника свойств: systemProperties и systemEnvironment. Его время срабатывания находится в конструкторе его родительского класса AbstractEnvironment. Метод customPropertySources не имеет конкретной реализации в AbstractEnvironment, он полагается на подклассы для завершения, как показано ниже:
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
// 子类 StandardEnvironment 中的实现逻辑
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
// 构建 systemProperties 配置
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
// // 构建 systemEnvironment 配置
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
Возьмем в качестве примера мою локальную машину, давайте посмотрим, что такое systemProperties и systemEnvironment в основном.
- systemProperties
- systemEnvironment
defaultProperties & commandLineArgs
После того, как среда по умолчанию построена, среда настраивается, которая в основном включает стандартные параметры defaultProperties и командной строки. defaultProperties можно установить следующими способами:
Map<String, Object> defaultProperties = new HashMap<>();
defaultProperties.put("defaultKey","defaultValue");
SpringApplication springApplication = new SpringApplication(BootStrap.class);
springApplication.setDefaultProperties(defaultProperties);
springApplication.run(args);
Код для настройки defaultProperties и процедуры аргументов командной строки выглядит следующим образом:
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
// 如果 springApplication 设置了则构建 defaultProperties,没有就算了
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
}
// 命令行参数
if (this.addCommandLineProperties && args.length > 0) {
// PropertySource 名为 commandLineArgs
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(
new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
}
else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
Параметры, передаваемые через командную строку после того, как SpringBoot упакован в пакет fatjar, включают следующие три метода реализации.
- java -jar xxx.jar a b c : получено через параметры основного метода, т.е. args
- java -jar xxx.jar -Dp1=a -Dp2=b -Dp3=c : режим параметра -D, будут установлены системные параметры
- java -jar xxx.jar --p1=a --p2=b --p3=c : канонический метод SpringBoot, который можно получить с помощью @Value("${p1}")
Настройка профилей
Настройте, какие профили активны (или активны по умолчанию) для среды приложения. Во время обработки профиля дополнительные профили можно активировать с помощью свойства конфигурации spring.profiles.active. В основном есть два типа:
- Настраивается через spring.profiles.active
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
// 获取 spring.profiles.active 配置值
// 如:spring.profiles.active=local ,profiles 为 local
// 如:spring.profiles.active=local,dev ,profiles 为 local,dev
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
// 按 ,分割成 String[] 数组
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
// 返回,这里还没有解析和 merge 配置
return this.activeProfiles;
}
}
- Настраивается через объект SpringApplication setAdditionalProfiles
SpringApplication springApplication = new SpringApplication(BootStrap.class);
// 设置 dev
springApplication.setAdditionalProfiles("dev");
springApplication.run(args);
Профили, установленные двумя указанными выше способами, будут использоваться в качестве последних активных профилей.
configurationProperties
Прикрепите поддержку ConfigurationPropertySource к указанной среде. Настройте каждый PropertySource, управляемый Environment, чтобы он имел тип ConfigurationPropertySource, и разрешите PropertySourcesPropertyResolver вызывать разрешение с ConfigurationPropertyName. Дополнительные распознаватели будут динамически отслеживать любые добавления или удаления из основного источника свойств среды (это также основной принцип, лежащий в основе Spring Cloud Config).
public static void attach(Environment environment) {
// 类型检查
Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
// 获取名为 configurationProperties 的 PropertySource
PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
// 如果存在先移除,保证每次都是最新的 PropertySource
if (attached != null && attached.getSource() != sources) {
sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
attached = null;
}
if (attached == null) {
// 重新将名为 configurationProperties 的 PropertySource 放到属性源中
sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
new SpringConfigurationPropertySources(sources)));
}
}
Привязать среду к SpringApplication
В Spring Boot 2.0 полностью переработан механизм привязки свойств среды @ConfigurationProperties, поэтому я полагаю, что многие наступят на эту яму при миграции SpringBoot с серии 1.x на серию 2.x.
Новый API позволяет использовать @ConfigurationProperties непосредственно вне вашего собственного кода. Обязательные правила могут относиться к:Relaxed-Binding-2.0. Вот простая демонстрация:
// 绑定 CustomProp
List<CustomProp> props = Binder.get(run.getEnvironment())
.bind("glmapper.property", Bindable.listOf(CustomProp.class))
.orElseThrow(IllegalStateException::new);
// 配置类
@ConfigurationProperties(prefix = "glmapper.property")
public class CustomProp {
private String name;
private int age;
// 省略 get&set
}
Конфигурация свойства:
glmapper:
property:
- name: glmapper
age: 26
- name: slg
age: 26
Из всего процесса построения, описанного выше, создание объекта Environment на самом деле представляет собой процесс заполнения объекта MutablePropertySources. Статические свойства и контейнеры хранения среды определяются в AbstractEnvironment.Метод getPropertySources(), предоставляемый интерфейсом ConfigurableWebEnvironment, может получить возвращенный экземпляр MutablePropertySources, а затем добавить дополнительные источники свойств. Фактически контейнер хранения Environment представляет собой набор подклассов PropertySource, а экземпляры, используемые в AbstractEnvironment, являются MutablePropertySources.
Итак, здесь связь между Environment и конфигурацией очень ясна.Одним предложением Environment является менеджером всех конфигураций и представляет собой унифицированный интерфейс Spring для предоставления конфигурации. Как упоминалось ранее, Environment управляет всеми конфигурациями среды Spring, которые в конечном итоге хранятся в Environment в виде объектов MutablePropertySources. На следующем рисунке показана система наследования класса MutablePropertySources:
Продолжим рассмотрение PropertySources.
PropertySource & PropertySources
Как можно интуитивно понять из названия, PropertySources — это классы, которые содержат один или несколько PropertySource. Источники свойств предоставляют базовый набор методов для управления источниками свойств.
PropertySource
Давайте посмотрим на исходный код PropertySource:
public abstract class PropertySource<T> {
protected final Log logger = LogFactory.getLog(getClass());
// 属性名
protected final String name;
// 属性源
protected final T source;
// 根据指定 name 和 source 构建
public PropertySource(String name, T source) {
Assert.hasText(name, "Property source name must contain at least one character");
Assert.notNull(source, "Property source must not be null");
this.name = name;
this.source = source;
}
// 根据指定 name 构建,source 默认为 Object 类型
@SuppressWarnings("unchecked")
public PropertySource(String name) {
this(name, (T) new Object());
}
// 返回当前 PropertySource 的 name
public String getName() {
return this.name;
}
// 返回当前 PropertySource 的 source
public T getSource() {
return this.source;
}
public boolean containsProperty(String name) {
return (getProperty(name) != null);
}
@Nullable
public abstract Object getProperty(String name);
// 返回用于集合比较目的的 PropertySource 实现 (ComparisonPropertySource)。
public static PropertySource<?> named(String name) {
return new ComparisonPropertySource(name);
}
// 省略其他两个内部类实现,无实际意义
}
Экземпляр PropertySource соответствует имени, например systemProperties, enviromentProperties и т. д. PropertySource включает несколько типов реализации, в основном в том числе:
- 1. AnsiPropertySource: Ansi.*, включая AnsiStyle, AnsiColor, AnsiBackground и т. д.
- 2. StubPropertySource: используется в качестве заполнителя в случаях, когда фактический источник свойства не может быть инициализирован сразу после создания контекста приложения. Например, источник атрибута на основе ServletContext должен ждать, пока объект ServletContext не станет доступным для его инкапсулированного ApplicationContext. В этом случае следует использовать заглушки для сохранения положения/порядка источников свойств по умолчанию, а затем заменять их во время обновления контекста.
- ComparisonPropertySource: унаследовано от StubPropertySource, все методы доступа к свойствам вынуждены создавать исключения, что является пустой реализацией недоступного свойства.
- 3. EnumerablePropertySource: перечисляемый PropertySource, который расширяет метод getPropertyNames на основе его родительского класса.
- CompositePropertySource: источник — это реализация PropertySource составного типа.
- CommandLinePropertySource: source — это реализация PropertySource типа параметра командной строки, включая два типа параметров командной строки и параметры java opts.
- MapPropertySource: источник — это реализация PropertySource типа Map.
- PropertiesPropertySource: экземпляр внутренней карты преобразуется из экземпляра Properties.
- JsonPropertySource: внутренний экземпляр карты преобразуется из экземпляра Json.
- SystemEnvironmentPropertySource: экземпляр внутренней карты получен системой env
Другие включают ServletConfigPropertySource, ServletContextPropertySource, AnnotationsPropertySource и т. д., все из которых могут знать свои исходные источники по имени.
PropertySources
Интерфейс PropertySources относительно прост:
public interface PropertySources extends Iterable<PropertySource<?>> {
// 从 5.1 版本才提供的
default Stream<PropertySource<?>> stream() {
return StreamSupport.stream(spliterator(), false);
}
// check name 为 「name」 的数据源是否存在
boolean contains(String name);
// 根据 name」 获取数据源
@Nullable
PropertySource<?> get(String name);
}
Ранее при анализе конструкции Environment вы могли видеть, что весь процесс основан на заполнении MutablePropertySources в качестве основной строки. MutablePropertySources — это реализация PropertySources по умолчанию, которая позволяет выполнять операции с содержащимися источниками свойств и предоставляет конструктор для копирования существующего экземпляра PropertySources. Кроме того, он внутренне упоминает приоритет в таких методах, как addFirst и addLast, что влияет на порядок, в котором PropertyResolver ищет источники свойств при разрешении данного свойства.
Внутри MutablePropertySources находится ряд операций управления над propertySourceList (добавление, удаление, изменение и т. д.), propertySourceList фактически является нижним контейнером хранения всей системы конфигурации, поэтому несложно понять, почему разбор конфигурации заполняет объект MutablePropertySources.
// 配置最终都被塞到这里了
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
Наконец, давайте посмотрим, как в Spring осуществляется доступ к атрибуту Environment.
Доступ к свойствам среды
С точки зрения кода среды он не предоставляет внутренних методов доступа к свойствам.Эти методы доступа к свойствам предоставляются его родительским интерфейсом PropertyResolver.
public interface PropertyResolver {
// 判断属性是否存在
boolean containsProperty(String key);
// 获取属性
@Nullable
String getProperty(String key);
// 获取属性,如果没有则提供默认值
String getProperty(String key, String defaultValue);
@Nullable
<T> T getProperty(String key, Class<T> targetType);
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
// 获取 Required 属性
String getRequiredProperty(String key) throws IllegalStateException;
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
// 解析占位符
String resolvePlaceholders(String text);
// 解析 Required占位符
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
Реализация объекта в среде, которая предоставляет свойства доступа по умолчанию, — это PropertySourcesPropertyResolver, который определен в абстрактном классе AbstractEnvironment:
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
В конце статьи давайте посмотрим, как PropertySourcesPropertyResolver получает доступ к свойствам конфигурации.
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
// 遍历所有的 PropertySource
for (PropertySource<?> propertySource : this.propertySources) {
// 省略日志
// 从 propertySource 中根据指定的 key 获取值
Object value = propertySource.getProperty(key);
// 如果值不为空->选用第一个不为 null 的匹配 key 的属性值
if (value != null) {
// 解析占位符替换, 如${server.port},底层委托到 PropertyPlaceholderHelper 完成
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
// 进行一次类型转换,具体由 DefaultConversionService 处理
return convertValueIfNecessary(value, targetValueType);
}
}
}
// 省略日志 ...
// 没有的话就返回 null
return null;
}
Следует отметить, что если в нескольких источниках свойств есть ключи с одинаковыми именами, будет возвращено только значение свойства ключа, соответствующего первому источнику свойств. В реальном бизнес-разработке, если вам нужно настроить некоторые свойства среды, лучше всего иметь достаточно знаний о порядке каждого PropertySource.
резюме
В целом, управление конфигурацией в Spring относительно простое, весь процесс от Environment до PropertySource не так сложен, он просто подключает конфигурацию из разных мест в MutablePropertySources и предоставляет интерфейсный доступ к внешнему миру через интерфейс Environment.
Наконец, спасибо за ваше внимание и поддержку в течение прошлого года.Попрощайтесь с 2019 годом и поздоровайтесь с 2020 годом!С Новым годом всех.