Использование переменной env в application.properties Spring Boot

201

Мы работаем над веб-приложением Spring Boot , и база данных, которую мы используем, - MySql ;

  • у нас есть настройки, которые мы сначала тестируем локально (значит, нам нужно установить MySql на наш ПК);

  • затем мы нажимаем на Bitbucket ;

  • Jenkins автоматически обнаруживает новый push-запрос к Bitbucket и выполняет сборку на нем (для того, чтобы передать Jenkins mvn build, нам также нужно установить MySql на виртуальные машины, на которых работает Jenkins).

  • если сборка Jenkins проходит успешно, мы отправляем код в наше приложение в OpenShift (используя плагин развертывания Openshift в Jenkins).

Проблема, которую мы имеем, как вы, возможно, уже поняли, заключается в том, что:

  • в application.propertiesмы можем не трудно кодировать информацию MySql. Поскольку наш проект будет выполняться в 3 разных местах ( локальном , Jenkins и OpenShift ), нам нужно сделать поле источника данных динамичным application.properties(мы знаем, что есть разные способы сделать это, но мы сейчас работаем над этим решением).

    spring.datasource.url = 
    spring.datasource.username = 
    spring.datasource.password = 

Решение, которое мы придумали, заключается в том, что мы создаем системные переменные среды локально и в Jenkins vm (называя их так же, как их называет OpenShift) и присваивая им правильные значения соответственно:

export OPENSHIFT_MYSQL_DB_HOST="jdbc:mysql://localhost"
export OPENSHIFT_MYSQL_DB_PORT="3306"
export OPENSHIFT_MYSQL_DB_USERNAME="root"
export OPENSHIFT_MYSQL_DB_PASSWORD="123asd"

Мы сделали это, и это работает. Мы также проверили, Map<String, String> env = System.getenv();что переменные окружения могут быть преобразованы в переменные Java как таковые:

String password = env.get("OPENSHIFT_MYSQL_DB_PASSWORD");   
String userName = env.get("OPENSHIFT_MYSQL_DB_USERNAME");   
String sqlURL = env.get("OPENSHIFT_MYSQL_DB_HOST"); 
String sqlPort = env.get("OPENSHIFT_MYSQL_DB_PORT");

Теперь осталось только использовать эти переменные java в нашем, application.propertiesи это то, с чем у нас проблемы.

В какой папке, и как нам нужно назначить password, userName, sqlURLи sqlPortпеременные , application.propertiesчтобы иметь возможность видеть их и как мы включаем их в application.properties?

Мы перепробовали много вещей, одна из которых:

spring.datasource.url = ${sqlURL}:${sqlPort}/"nameofDB"
spring.datasource.username = ${userName}
spring.datasource.password = ${password}

Пока не повезло. Вероятно, мы не помещаем эти переменные env в нужный класс / папку или используем их неправильно application.properties.

Ваша помощь высоко ценится!

Спасибо!

SM
источник
3
Прочитайте @ConfigurationProperties, чтобы узнать больше. Тем не менее, это идеальный вариант использования специфичных
Eddie B

Ответы:

271

Вам не нужно использовать переменные Java. Чтобы включить системные переменные env, добавьте в свой application.propertiesфайл следующее:

spring.datasource.url = ${OPENSHIFT_MYSQL_DB_HOST}:${OPENSHIFT_MYSQL_DB_PORT}/"nameofDB"
spring.datasource.username = ${OPENSHIFT_MYSQL_DB_USERNAME}
spring.datasource.password = ${OPENSHIFT_MYSQL_DB_PASSWORD}

Но путь предложенный @Stefan Isele является более предпочтительным, поскольку в этом случае вы должны объявить только одна переменная ENV: spring.profiles.active. Spring автоматически прочитает соответствующий файл свойств по application-{profile-name}.propertiesшаблону.

Кен Беков
источник
12
Этот метод более удобен для связи с докером. Например:docker run --name my-tomcat -p 127.0.0.1:8080:8080 -e APP_DB_DB=mydb -e APP_DB_USER=dbuser -e APP_DB_PASS=dbpass --link mongo-myapp:mongo -v /path-to/tomcat/webapps:/usr/local/tomcat/webapps -d tomcat:8-jre8-alpine
Fırat KÜÇÜK
17
Это абсолютно лучший путь. Использование переменных среды означает, что вам не нужно перечислять секреты в виде простого текста рядом с вашим приложением. Это значительно более безопасно и снижает зависимость от ваших мер безопасности доступа к исходному коду для защиты всего вашего имущества. Случайный пост SO с включенными свойствами не приводит к утечке информации.
kipper_t
51
Я хотел бы добавить к этому и упомянуть, что если вы используете весеннюю загрузку (не проверял, работает ли она без загрузки), то любое свойство может быть автоматически переопределено через переменную окружения без изменения вашего application.properties. то есть, если у вас есть свойство , spring.activemq.broker-urlто соответствующая переменная окружения будет: SPRING_ACTIVEMQ_BROKER_URL. точки и тире автоматически преобразуются в подчеркивания. Это чрезвычайно удобно при работе с контейнерами / пружинной загрузкой.
Эйб
15
Если вы разрабатываете для облака, это не лучший способ использовать профили Spring. Использование переменных среды рекомендуется стандартом 12-факторного приложения: 12factor.net/config
Михаил Голубцов
6
Я знаю, что эта тема немного старая. Но вы можете комбинировать как настройку переменных среды, так и настройку профиля пружины. Ваш профиль разработчика должен иметь статическую информацию, в то время как ваш производственный профиль может использовать переменные среды. Таким образом, разработчикам больше не нужно определять переменные среды на своем компьютере, если они просто хотят развернуть профиль разработки.
underscore_05
72

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

Это дает вам большую гибкость. Я использую это в своих проектах, и это чрезвычайно полезно. В вашем случае у вас будет 3 профиля: 'local', 'jenkins' и 'openshift'

После этого у вас 3 профиля файлы конкретных свойств: application-local.properties, application-jenkins.propertiesиapplication-openshift.properties

Там вы можете установить свойства для соответствующей среды. Когда вы запускаете приложение, вы должны указать профиль для активации следующим образом: -Dspring.profiles.active=jenkins

редактировать

Согласно весеннему документу вы можете установить системную переменную среды SPRING_PROFILES_ACTIVEдля активации профилей и не нужно передавать ее в качестве параметра.

Есть ли способ передать параметр активного профиля для веб-приложения во время выполнения?

Нет. Spring определяет активные профили как один из первых шагов при построении контекста приложения. Активные профили затем используются, чтобы решить, какие файлы свойств будут прочитаны и какие компоненты будут созданы. После запуска приложения это нельзя изменить.

Стефан Изеле - prefabware.com
источник
4
Мне нравится этот ответ, но что, если вы хотите, чтобы имя профиля пришло из среды? Я попробовал -Dspring.active.profiles = $ SPRING_ACTIVE_PROFILES и установил переменную env var в /etc/profile.d/myenvvars.sh, но Spring Boot этого не понимает
Том Хартвелл,
1
SPRING_PROFILES_ACTIVE работает из-за непринужденной привязки весенней загрузки docs.spring.io/spring-boot/docs/1.3.0.BUILD-SNAPSHOT/reference/…
feed.me
5
спасибо за этот ответ, Стефан, он сработал для меня, но с одним изменением - это свойство на самом деле spring.profiles.active, а не spring.active.profiles
Руди
11
В то время как пружинные профили могут быть очень полезны, в отношении OP они не подходят. Это связано с тем, как хранится исходный код, и чувствительностью информации о свойствах, хранящейся в нем. Контекст OP связан с доступом к базе данных. Для этой ситуации вам не нужны подробности о продукции в виде простого текста в источнике. Это означает, что если источник скомпрометирован, то база данных также скомпрометирована. Для этого лучше использовать переменные env или секретные инструменты, такие как Vault. Я предпочитаю env. Я также заставил бы все среды работать одинаково в отношении согласованности. Это позволяет избежать несчастных случаев в будущем.
kipper_t
2
Вы можете использовать файл свойств профиля Spring Boot, внешний по отношению к JAR приложения. Этот специфичный для среды файл, например, application-production.propertiesбудет развернут на производственном компьютере безопасным способом и обычно не будет находиться в хранилище исходного кода приложения.
Колин Д. Беннетт
13

Это ответ на ряд комментариев, поскольку моя репутация недостаточно высока, чтобы комментировать напрямую.

Вы можете указать профиль во время выполнения, если контекст приложения еще не загружен.

// Previous answers incorrectly used "spring.active.profiles" instead of
// "spring.profiles.active" (as noted in the comments).
// Use AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME to avoid this mistake.

System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, environment);
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/META-INF/spring/applicationContext.xml");
gthazmatt
источник
12

Flayway не распознает прямые переменные среды в application.properties (Spring-Boot V2.1). например

spring.datasource.url=jdbc:mysql://${DB_HOSTNAME}:${DB_PORT}/${DB_DATABASE}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASS}

Чтобы решить эту проблему, я сделал переменные окружения, обычно я создаю файл .env:

SPRING_DATASOURCE_URL=jdbc:mysql://127.0.0.1:3306/place
SPRING_DATASOURCE_USERNAME=root
SPRING_DATASOURCE_PASSWORD=root

И экспортируйте переменные в мою среду:

export $(cat .env | xargs)

И, наконец, просто запустите команду

mvn spring-boot:run

Или запустите файл JAR

java -jar target/your-file.jar

Здесь есть другой подход: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/maven-plugin/examples/run-env-variables.html.

Фелипе Джиротти
источник
1
Что такое env-vars? Как они используются. Ваш ответ относится к вещам без полного описания, и вы не включаете никаких ссылок. Я чуть не проголосовал за это, но я вижу, что вашему представителю 21, так что вы новичок, и один человек нашел ваш ответ полезным, поэтому я его отпустил, но постараюсь предоставить больше информации в будущих ответах и ​​добро пожаловать в SO (переполнение стека). Я надеюсь, вам понравится так же, как и мне.
PatS
2
Спасибо @PatS, я добавил больше деталей, надеюсь, это будет полезно.
Фелипе Джиротти
1
Отличные изменения. Спасибо за обновление вашего ответа.
PatS
9

Вот фрагмент кода через цепочку файлов свойств среды, загружаемых для разных сред.

Файл свойств под ресурсами вашего приложения ( src / main / resources ): -

 1. application.properties
 2. application-dev.properties
 3. application-uat.properties
 4. application-prod.properties

В идеале application.properties содержит все общие свойства, которые доступны для всех сред, а свойства, связанные со средой, работают только в определенных средах. поэтому порядок загрузки этих файлов свойств будет таким -

 application.properties -> application.{spring.profiles.active}.properties.

Фрагмент кода здесь: -

    import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.core.io.Resource;

    public class PropertiesUtils {

        public static final String SPRING_PROFILES_ACTIVE = "spring.profiles.active";

        public static void initProperties() {
            String activeProfile = System.getProperty(SPRING_PROFILES_ACTIVE);
            if (activeProfile == null) {
                activeProfile = "dev";
            }
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer
                    = new PropertySourcesPlaceholderConfigurer();
            Resource[] resources = new ClassPathResource[]
                    {new ClassPathResource("application.properties"),
                            new ClassPathResource("application-" + activeProfile + ".properties")};
            propertySourcesPlaceholderConfigurer.setLocations(resources);

        }
    }
Аджай Кумар
источник
2
Разве Spring Boot не обрабатывает этот сценарий из коробки? Смотрите документацию по External Config здесь
ChickenFeet
4

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

Моя проблема была: 1) Чтение свойства из env, если это свойство было установлено в env 2) Чтение свойства из системного свойства, если это свойство было установлено в системном свойстве 3) И, наконец, чтение из свойств приложения.

Итак, для решения этой проблемы я иду в свой класс конфигурации бина

@Validated
@Configuration
@ConfigurationProperties(prefix = ApplicationConfiguration.PREFIX)
@PropertySource(value = "${application.properties.path}", factory = PropertySourceFactoryCustom.class)
@Data // lombok
public class ApplicationConfiguration {

    static final String PREFIX = "application";

    @NotBlank
    private String keysPath;

    @NotBlank
    private String publicKeyName;

    @NotNull
    private Long tokenTimeout;

    private Boolean devMode;

    public void setKeysPath(String keysPath) {
        this.keysPath = StringUtils.cleanPath(keysPath);
    }
}

И перезаписать фабрику в @PropertySource. И тогда я создал свою собственную реализацию для чтения свойств.

    public class PropertySourceFactoryCustom implements PropertySourceFactory {

        @Override
        public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
            return name != null ? new PropertySourceCustom(name, resource) : new PropertySourceCustom(resource);
        }


    }

И создал PropertySourceCustom

public class PropertySourceCustom extends ResourcePropertySource {


    public LifeSourcePropertySource(String name, EncodedResource resource) throws IOException {
        super(name, resource);
    }

    public LifeSourcePropertySource(EncodedResource resource) throws IOException {
        super(resource);
    }

    public LifeSourcePropertySource(String name, Resource resource) throws IOException {
        super(name, resource);
    }

    public LifeSourcePropertySource(Resource resource) throws IOException {
        super(resource);
    }

    public LifeSourcePropertySource(String name, String location, ClassLoader classLoader) throws IOException {
        super(name, location, classLoader);
    }

    public LifeSourcePropertySource(String location, ClassLoader classLoader) throws IOException {
        super(location, classLoader);
    }

    public LifeSourcePropertySource(String name, String location) throws IOException {
        super(name, location);
    }

    public LifeSourcePropertySource(String location) throws IOException {
        super(location);
    }

    @Override
    public Object getProperty(String name) {

        if (StringUtils.isNotBlank(System.getenv(name)))
            return System.getenv(name);

        if (StringUtils.isNotBlank(System.getProperty(name)))
            return System.getProperty(name);

        return super.getProperty(name);
    }
}

Итак, это помогло мне.

Максим Галич
источник
4

Используя Spring context 5.0, я успешно загрузил правильный файл свойств, основанный на системной среде, с помощью следующей аннотации

@PropertySources({
    @PropertySource("classpath:application.properties"),
    @PropertySource("classpath:application-${MYENV:test}.properties")})

Здесь значение MYENV считывается из системной среды, и если системная среда отсутствует, будет загружен файл свойств тестовой среды по умолчанию, если я укажу неправильное значение MYENV - приложение не запустится.

Примечание: для каждого профиля вы хотите сохранить - вам нужно будет создать файл application- [profile] .property, и хотя я использовал контекст Spring 5.0, а не загрузку Spring - я считаю, что это также будет работать в Spring 4.1

Абдеали Чанданвала
источник
3

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

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

Получение идеи из среды PHP Symfony 3, которая имеет parameters.yml(.gitignored) и parameters.yml.dist(который является примером, который создает первый черезcomposer install ),

Я сделал следующее, объединив знания из ответов ниже: https://stackoverflow.com/a/35534970/986160 и https://stackoverflow.com/a/35535138/986160 .

По сути, это дает свободу использовать наследование пружинных конфигураций и выбирать активные профили через верхнюю конфигурацию, а также любые дополнительные конфиденциальные учетные данные следующим образом:

application.yml.dist (образец)

    spring:
      profiles:
        active: local/dev/prod
      datasource:
        username:
        password:
        url: jdbc:mysql://localhost:3306/db?useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

application.yml (.gitignore-d на сервере разработки)

spring:
  profiles:
    active: dev
  datasource:
    username: root
    password: verysecretpassword
    url: jdbc:mysql://localhost:3306/real_db?useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

application.yml (.gitignore-d на локальной машине)

spring:
  profiles:
    active: dev
  datasource:
    username: root
    password: rootroot
    url: jdbc:mysql://localhost:3306/xampp_db?useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

application-dev.yml (дополнительные свойства, не зависящие от среды)

spring:
  datasource:
    testWhileIdle: true
    validationQuery: SELECT 1
  jpa:
    show-sql: true
    format-sql: true
    hibernate:
      ddl-auto: create-droop
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL57InnoDBDialect

То же самое можно сделать с .properties

Михаил Михайлидис
источник
0

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

--spring.config.additional-location={PATH_OF_EXTERNAL_PROP}

Махеш К
источник