Статьи

«Конфигурирование всего» или «12-факторная конфигурация в стиле приложения с помощью Spring»


Эта статья была первоначально опубликована 
Джошом Лонгом   в весеннем блоге

Давайте установим некоторый словарный запас, прежде чем мы начнем. Когда мы говорим о  конфигурации  в Spring, мы  обычно  говорим о входных данных в различные ApplicationContext реализации среды Spring,  которые помогают контейнеру понять, что вы хотите сделать. Это может быть файл XML для подачи в  ClassPathXmlApplicationContextклассы Java или аннотированный определенный способ подачи в класс AnnotationConfigApplicationContext.

Другой тип  конфигурации , который  хорошо описан в манифесте 12-факторного приложения , — это любое приложение, которое может различаться в зависимости от развертывания (подготовка, производственная среда, среда разработки и т. Д.), Например учетные данные службы и имена хостов.

Этот второй тип конфигурации, который должен существовать вне развернутого приложения, хорошо поддерживается в Spring с момента появления  PropertyPlaceholderConfigurer класса. С тех пор поддержка Spring для этого типа конфигурации прошла долгий путь, и в этом блоге мы рассмотрим этот прогресс.

The PropertyPlaceholderConfigurer

Spring предлагает  PropertyPlaceholderConfigurer с 2003 года. Spring 2.5 представил поддержку пространства имен XML и вместе с ним поддержку пространства имен XML для разрешения заполнителей свойств. Например <context:property-placeholder location = "simple.properties"/> , давайте заменим литеральные значения определения компонента в конфигурации XML на значения, назначенные ключам в (внешнем) файле свойств (в данном случае  simple.properties это может быть путь к классу или внешний по отношению к приложению). Этот файл свойств может выглядеть так:

# Database Credentials
configuration.projectName =SpringFramework

Environment Абстракция

Это решение предшествует введению конфигурации Java в собственно Spring Framework в версии 3.0. Spring 3 упростил внедрение значений конфигурации в конфигурацию компонента Java с помощью  @Value-аннотаций, например:

@Value("${configuration.projectName}")privateString projectName;

Весной 3.1 введена   абстракция. Он обеспечивает некоторую косвенность времени выполнения между запущенным приложением и средой, в которой оно выполняется.  Действует как отображение ключей и значений. Вы можете настроить, откуда эти значения считываются, добавив. Введите объект типа   где угодно и задайте ему вопросы. По умолчанию Spring загружает системные ключи и значения, как . Вы можете указать Spring загрузить конфигурационные ключи из файла, в частности, используя   аннотацию.EnvironmentEnvironmentEnvironmentline.separator@PropertySource

package env;




import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.context.support.*;
import org.springframework.core.env.Environment;




@Configuration
@ComponentScan
@PropertySource("file:/path/to/simple.properties")
public class Application {




@Bean
static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}




@Value("${configuration.projectName}")
void setProjectName(String projectName) {
System.out.println("setting project name: " + projectName);
}




@Autowired
void setEnvironment(Environment env) {
System.out.println("setting environment: " + 
env.getProperty("configuration.projectName"));
}




public static void main(String args[]) throws Throwable {
new AnnotationConfigApplicationContext(Application.class);
}
}

Этот пример загружает значения из файла,  simple.propertiesа затем имеет одно значение configuration.projectName, вводится с использованием  @Value аннотации и затем снова считывается из Environment абстракции Spring  . Чтобы иметь возможность вводить значения с  @Valueаннотацией, нам нужно зарегистрировать  PropertySourcesPlaceholderConfigurer. В этом случае на выходе есть  Spring Framework.

Environment Также приносит идею  профилей . Позволяет приписывать ярлыки (профили) группировкам бинов. Используйте профили для описания бинов и графиков бинов, которые меняются от одной среды к другой. Вы можете активировать один или несколько профилей одновременно. Бины, которым не присвоен профиль, всегда активированы. Бины, имеющие профиль  default , активируются только тогда, когда другие активные профили отсутствуют.

Профили позволяют вам описывать наборы bean-компонентов, которые нужно создавать по-разному в одной среде по сравнению с другой. Например, вы можете использовать встроенный H2  javax.sql.DataSource в своем локальном  dev профиле, но затем переключиться  javax.sql.DataSource на PostgreSQL для, который разрешается с помощью поиска JNDI или путем чтения свойств из переменной среды в Cloud Foundry,  когда  prod профиль активен. В обоих случаях ваш код работает: вы получаете javax.sql.DataSource, но решение о том,  какой  специализированный экземпляр используется, принимается активным профилем или профилями.

Вы должны использовать эту функцию экономно. В идеале граф объектов между одним окружением и другим должен оставаться достаточно фиксированным.

 Конфигурация Bootiful

Spring Boot  улучшает вещи значительно. Spring Boot будет читать свойства src/main/resources/application.properties по умолчанию.  Если профиль активен , он также автоматически считывает файлы конфигурации на основе имени профиля, например, src/main/resources/application-foo.properties где  foo находится текущий профиль. Если библиотека Snake YML  находится на пути к классам, она также автоматически загрузит файлы YML. Да, прочитайте эту часть еще раз. YML так хорош и стоит того! Вот пример файла YML:

configuration:
projectName : Spring Boot
someOtherKey : Some Other Value

Spring Boot также значительно упростил получение правильного результата в обычных случаях. Это делает  -Dаргументы для  java переменных процесса и среды доступными в качестве свойств. Он даже нормализует их, поэтому переменная окружения  $CONFIGURATION_PROJECTNAME или  -Dаргумент формы  -Dconfiguration.projectname становятся доступными с помощью ключа configuration.projectName.

Значения конфигурации — это строки, и если у вас достаточно значений конфигурации, может быть неудобно пытаться убедиться, что эти ключи сами по себе не станут волшебными строками в коде. Spring Boot представляет  @ConfigurationProperties тип компонента. Аннотируйте POJO с помощью  @ConfigurationProperties и укажите префикс, и Spring попытается сопоставить все свойства, которые начинаются с этого префикса, со свойствами POJO. В приведенном ниже примере значение for  configuration.projectName будет сопоставлено с экземпляром POJO, который затем может ввести весь код и разыменовать для чтения (безопасных для типов) значений. Таким образом, у вас есть только отображение ключа в одном месте.

В приведенном ниже примере свойства будут автоматически разрешены из src/main/resources/application.yml.

package boot;




import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;




// reads a value from src/main/resources/application.properties first
// but would also read:
// java -Dconfiguration.projectName=..
// export CONFIGURATION_PROJECTNAME=..




@SpringBootApplication
public class Application {




@Autowired
void setConfigurationProjectProperties(ConfigurationProjectProperties cp) {
System.out.println("configurationProjectProperties.projectName = " + cp.getProjectName());
}




public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}




@Component
@ConfigurationProperties("configuration")
class ConfigurationProjectProperties {




private String projectName;




public String getProjectName() {
return projectName;
}




public void setProjectName(String projectName) {
this.projectName = projectName;
}
}

Spring Boot @ConfigurationProps активно использует этот  механизм, чтобы позволить пользователям переопределять биты системы. Вы можете увидеть, какие ключи свойств можно использовать, чтобы изменить ситуацию, например, добавив  org.springframework.boot:spring-boot-starter-actuator зависимость в веб-приложение на основе Spring Boot и затем посетив  http://127.0.0.1:8080/configprops. Это даст вам список поддерживаемых свойств конфигурации на основе типов, присутствующих в пути к классам во время выполнения. По мере добавления новых типов Spring Boot вы увидите больше свойств.

Централизованная журнальная конфигурация с поддержкой конфигурации Spring Cloud

Пока все хорошо, но в подходе пока есть пробелы:

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

Spring Cloud , основанный на Spring Boot и объединяющий различные инструменты и библиотеки для работы с микросервисами, включая  стек Netflix OSS , предлагает  сервер конфигурации  и клиент для этого сервера конфигурации. Эта поддержка, взятая вместе, решает эти последние три проблемы.

Давайте посмотрим на простой пример. Сначала мы настроим сервер конфигурации. Конфигурационный сервер — это то, что должно быть общим для набора приложений или микросервисов на основе Spring Cloud. Вы должны запустить его где-то, один раз. Тогда всем остальным сервисам нужно только знать, где найти сервис конфигурации. Служба конфигурации действует как своего рода прокси для ключей конфигурации и значений, которые она читает из онлайн-хранилища Git или на диске.

package cloud.server;




import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;




@SpringBootApplication
@EnableConfigServer
public class Application {




public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Если вы управляете всем правильно, то единственной конфигурацией, которая работает с любыми вашими сервисами, должна быть конфигурация, которая сообщает службе конфигурации, где найти репозиторий Git, и конфигурация, которая сообщает другим клиентским службам, где найти службу конфигурации.

Вот конфигурация для службы конфигурации src/main/resources/application.yml:

server:
port: 8888




spring:
cloud:
config:
server:
git :
uri: https://github.com/joshlong/microservices-lab-configuration

Это говорит службе конфигурации Spring Cloud искать файлы конфигурации для отдельных клиентских служб в репозитории Git моей учетной записи GitHub. Разумеется, URI с такой же легкостью мог бы быть репозиторием Git в моей локальной файловой системе. Значение, используемое для URI, также могло быть ссылкой на свойство в форме  ${SOME_URI}, которая ссылается — возможно, на переменную среды, которая называется  SOME_URI.

Запустите приложение, и вы сможете убедиться, что ваша служба конфигурации работает, указав в браузере,  http://localhost:8888/SERVICE/master где  SERVICE находится идентификатор, взятый из службы вашего клиента  boostrap.yml. Сервисы Spring Cloud ищут файл,  src/main/resources/bootstrap.(properties,yml) который он ожидает найти — вы уже догадались! — загрузите сервис. Одна из вещей, которую он ожидает найти в  bootstrap.ymlфайле, — это идентификатор службы, указанный в качестве свойства  spring.application.name. Вот наш клиент конфигурации  bootstrap.yml:

spring:
application:
name: config-client
cloud:
config:
uri: http://localhost:8888

Когда микросервис Spring Cloud запустится, он увидит, что  spring.application.name это так config-client. Он свяжется со службой конфигурации (о которой мы говорили, что Spring Cloud работает  http://localhosst:8080, хотя это тоже могло быть переменной среды) и запросит любую конфигурацию. Служба конфигурации возвращает обратно JSON, который содержит все значения конфигурации в  application.(properties,yml) файле, а также любую специфическую для службы конфигурацию в  config-client.(yml,properties). Он  также  загрузит любую конфигурацию для данной службы  и  определенного профиля, например  config-client-dev.properties.

Это все просто происходит автоматически. В следующем примере значение конфигурации считывается из службы конфигурации.

package cloud.client;




import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;




@SpringBootApplication
public class Application {




public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}




@Autowired
void setEnvironment(Environment e) {
System.out.println(e.getProperty("configuration.projectName"));
}
}




@RestController
@RefreshScope
class ProjectNameRestController {




@Value("${configuration.projectName}")
private String projectName;




@RequestMapping("/project-name")
String projectName() {
return this.projectName;
}
}

The ProjectNameRestController is annotated with @RefreshScope, a custom Spring Cloudscope that lets any bean recreate itself (and re-read configuration values from the configuration service) in-place. There are various ways to trigger the refresh: send a POSTrequest to http://127.0.0.1:8080/refresh (e.g.:curl -d{} http://127.0.0.1:8080/refresh), use the auto-exposed JMX refresh endpoint, or use the Spring Cloud Bus.

The Spring Cloud Bus links all services through a RabbitMQ powered-bus. This is particularly powerful. You can tell one (or thousands!) of microservices to refresh themselves by sending a single message to a message bus. This prevents downtime and is much more friendly than having to systematically restart individual services or nodes.

To see all this in action, get the config client and config server running, being sure to point the config server to a Git repository that you can control and make changes to. Hit the REST endpoint and confirm that you see Spring Cloud. Then make changes to the configuration file in Git, and at the very least git commit them. Then trigger a refresh against the config client and revisit the REST endpoint again. You should see the updated value reflected!

The Spring Cloud configuration support also includes first-class support for security and encryption. I’ll leave you to explore that last mile on your own, but it’s fairly trivial and amounts to configuring a valid key.

Next Steps

We’ve covered a lot here! Armed with all of this, it should be easy to package one artifact and then move that artifact from one environment to another without changes to the artifact itself. If you’re going to start an application today, I’d recommend starting on Spring Boot and Spring Cloud, especially now that we’ve looked at all the good stuff it brings you by default. Don’t forget to check out the code behind all of these examples