Używanie zmiennej env w pliku application.properties Spring Boot

199

Pracujemy nad aplikacją sieciową Spring Boot, a używaną przez nas bazą danych jest MySql ;

  • konfiguracja, którą mamy, to najpierw testujemy ją lokalnie (oznacza to, że musimy zainstalować MySql na naszym komputerze);

  • następnie naciskamy na Bitbucket ;

  • Jenkins automatycznie wykrywa nowy push do Bitbucket i wykonuje na nim kompilację (aby kompilacja Jenkins mvn przebiegła pomyślnie, musimy również zainstalować MySql na maszynach wirtualnych, na których działa Jenkins).

  • jeśli kompilacja Jenkins przejdzie pomyślnie, wypychamy kod do naszej aplikacji na OpenShift (używając wtyczki wdrażania Openshift na Jenkins).

Problem, który, jak zapewne już wymyśliliście, polega na tym, że:

  • w application.propertiesnie możemy na stałe zakodować informacji MySql. Ponieważ nasz projekt będzie działał w 3 różnych miejscach ( lokalny , Jenkins i OpenShift ), musimy dynamizować pole źródła danych application.properties(wiemy, że istnieją różne sposoby, ale na razie pracujemy nad tym rozwiązaniem).

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

Opracowane przez nas rozwiązanie polega na tym, że tworzymy systemowe zmienne środowiskowe lokalnie oraz w vm Jenkinsa (nazywając je w ten sam sposób, w jaki OpenShift je nazywa) i przypisując im odpowiednio wartości:

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"

Zrobiliśmy to i działa. Sprawdziliśmy również, czy Map<String, String> env = System.getenv();zmienne środowiskowe można przekształcić w zmienne Java jako takie:

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");

Teraz pozostaje nam tylko użyć tych zmiennych java application.propertiesi właśnie z tym mamy problem.

W którym folderze i jak, musimy przypisać password, userName, sqlURLoraz sqlPortzmienne dla application.propertiesaby móc je zobaczyć i jak możemy włączyć je application.properties?

Próbowaliśmy wielu rzeczy, jedną z nich jest:

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

Jak dotąd brak szczęścia. Prawdopodobnie nie umieszczamy tych zmiennych env we właściwej klasie / folderze lub używamy ich niepoprawnie application.properties.

Twoja pomoc jest bardzo ceniona !!

Dzięki!

SM
źródło
3
Przeczytaj @ConfigurationProperties, aby dowiedzieć się więcej. Jest to jednak idealny przypadek użycia dla właściwości konfiguracyjnych specyficznych
Eddie B,

Odpowiedzi:

268

Nie musisz używać zmiennych java. Aby uwzględnić zmienne systemowe env, dodaj do application.propertiespliku:

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}

Ale sposób sugerowane przez @Stefan Isele jest bardziej korzystne, ponieważ w tym przypadku trzeba zadeklarować tylko jedną zmienną env: spring.profiles.active. Wiosna automatycznie odczyta odpowiedni plik właściwości według application-{profile-name}.propertiesszablonu.

Ken Bekov
źródło
12
Ta metoda jest wygodniejsza w przypadku łączenia dokerów. Na przykład: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
To absolutnie najlepsza droga. Używanie zmiennych środowiskowych oznacza, że ​​nie musisz wymieniać sekretów w postaci zwykłego tekstu obok aplikacji. Jest to znacznie bezpieczniejsze i zmniejsza zależność od środków bezpieczeństwa dostępu do kodu źródłowego w celu ochrony całej nieruchomości. Przypadkowy wpis SO z zawartymi właściwościami nie powoduje wycieku informacji.
kipper_t
51
Chciałem dodać do tego i wspomnieć, że jeśli używasz rozruchu wiosennego (nie sprawdziłem, czy działa bez rozruchu), wówczas każdą właściwość można zastąpić automatycznie zmienną środowiskową bez modyfikowania pliku application.properties. to znaczy, jeśli mają właściwość o nazwie spring.activemq.broker-urlwówczas odpowiadająca zmienna będzie: SPRING_ACTIVEMQ_BROKER_URL. kropki i myślniki są automatycznie konwertowane na podkreślenia. Jest to niezwykle wygodne podczas pracy z pojemnikami / butami sprężynowymi.
abe
15
Jeśli projektujesz w chmurze, nie jest preferowanym sposobem korzystania z profili Spring. Używanie zmiennych środowiskowych jest zalecane przez 12-czynnikowy standard aplikacji: 12factor.net/config
Michaił Golubcow
6
Wiem, że ten temat jest trochę stary. Możesz jednak połączyć zarówno konfigurację zmiennych środowiskowych, jak i konfigurację profilu sprężynowego. Twój programista powinien mieć statyczne informacje, podczas gdy twój profil produkcyjny może korzystać ze zmiennych środowiskowych. W ten sposób programista nie musi już definiować zmiennych środowiskowych na swoim komputerze, jeśli chce wdrożyć profil programistyczny.
underscore_05
72

Najłatwiejszym sposobem uzyskania różnych konfiguracji dla różnych środowisk jest użycie profili sprężynowych. Zobacz konfigurację zewnętrzną .

Daje to dużą elastyczność. Używam go w swoich projektach i jest to niezwykle pomocne. W twoim przypadku masz 3 profile: „lokalny”, „jenkins” i „openshift”

Trzeba wówczas 3 znajomych pliki specyficzne własności: application-local.properties, application-jenkins.properties, iapplication-openshift.properties

Tam możesz ustawić właściwości odpowiedniego środowiska. Po uruchomieniu aplikacji musisz określić profil, który chcesz aktywować w następujący sposób: -Dspring.profiles.active=jenkins

Edytować

Według dokumentu wiosennego można ustawić systemową zmienną środowiskową, SPRING_PROFILES_ACTIVEaby aktywowała profile i nie trzeba jej przekazywać jako parametru.

czy jest jakiś sposób na przekazanie opcji aktywnego profilu dla aplikacji internetowej w czasie wykonywania?

Nie. Wiosna określa aktywne profile jako jeden z pierwszych kroków podczas budowania kontekstu aplikacji. Aktywne profile są następnie wykorzystywane do decydowania, które pliki właściwości zostaną odczytane, a które komponenty zostaną utworzone. Po uruchomieniu aplikacji nie można tego zmienić.

Stefan Isele - prefabware.com
źródło
4
Podoba mi się ta odpowiedź, ale co, jeśli chcesz, aby nazwa profilu pochodziła ze środowiska? Próbowałem -Dspring.active.profiles = $ SPRING_ACTIVE_PROFILES i ustawiłem zmienną env OS w /etc/profile.d/myenvvars.sh, ale Spring Boot tego nie odbiera
Tom Hartwell
1
SPRING_PROFILES_ACTIVE działa ze względu na swobodną funkcję wiązania wiosennego rozruchu docs.spring.io/spring-boot/docs/1.3.0.BUILD-SNAPSHOT/reference/…
feed.me
5
dziękuję za tę odpowiedź, Stefan, zadziałało to dla mnie, ale z jedną zmianą - właściwość to właściwie spring.profiles.active i nie spring.active.profiles
Rudi
10
Chociaż profile sprężynowe mogą być bardzo przydatne, w odniesieniu do PO nie są odpowiednie. Wynika to z tego, w jaki sposób przechowywany jest kod źródłowy i wrażliwość przechowywanych informacji o właściwościach. Kontekst PO dotyczy dostępu do bazy danych. W tej sytuacji nie chcesz szczegółów produktu w postaci zwykłego tekstu w źródle. Oznacza to, że jeśli źródło zostanie naruszone, baza danych również zostanie naruszona. Lepiej jest do tego używać zmiennych env lub tajnych narzędzi, takich jak Vault. Wolę env. Chciałbym również, aby wszystkie środowiska działały w ten sam sposób pod względem spójności. Pozwala uniknąć wypadków w przyszłości.
kipper_t
2
Możesz użyć pliku właściwości profilu Spring Boot zewnętrznego w stosunku do pliku JAR aplikacji. Na przykład ten plik specyficzny dla środowiska application-production.propertieszostałby wdrożony na maszynie produkcyjnej w bezpieczny sposób i zwykle nie znajdowałby się w repozytorium kodu źródłowego aplikacji.
Colin D Bennett
13

Jest to odpowiedź na szereg komentarzy, ponieważ moja reputacja nie jest wystarczająco wysoka, aby komentować bezpośrednio.

Możesz określić profil w czasie wykonywania, o ile kontekst aplikacji nie został jeszcze załadowany.

// 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
źródło
12

Flayway nie rozpoznaje bezpośrednich zmiennych środowiskowych w pliku application.properties (Spring-Boot V2.1). na przykład

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

Aby rozwiązać ten problem, zrobiłem te zmienne środowiskowe, zwykle tworzę plik .env:

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

I wyeksportuj zmienne do mojego środowiska:

export $(cat .env | xargs)

I w końcu po prostu uruchom polecenie

mvn spring-boot:run

Lub uruchom plik jar

java -jar target/your-file.jar

Istnieje inne podejście tutaj: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/maven-plugin/examples/run-env-variables.html

Felipe Girotti
źródło
1
Co to są env-vars? Jak są wykorzystywane? Twoja odpowiedź odnosi się do rzeczy bez pełnego opisu i nie zawiera żadnych linków. Prawie zlekceważyłem to, ale widzę, że twój przedstawiciel ma 21 lat, więc jesteś nowy i jedna osoba uznała twoją odpowiedź za przydatną, więc puszczam ją, ale postaram się podać więcej informacji w przyszłych odpowiedziach i witam w SO (przepełnienie stosu). Mam nadzieję, że spodoba ci się tak samo jak ja.
PatS
2
Dzięki @PatS, dodałem więcej szczegółów, mam nadzieję, że się przyda.
Felipe Girotti
1
Doskonałe zmiany. Dziękujemy za aktualizację odpowiedzi.
PatS
9

Oto fragment kodu w łańcuchu właściwości środowiska, które są ładowane pliki dla różnych środowisk.

Plik właściwości w zasobach aplikacji ( src / main / resources ): -

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

Idealnie, application.properties zawiera wszystkie wspólne właściwości, które są dostępne dla wszystkich środowisk, a właściwości związane ze środowiskiem działają tylko w określonym środowisku. dlatego kolejność ładowania tych plików właściwości będzie następująca -

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

Fragment kodu tutaj: -

    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);

        }
    }
Ajay Kumar
źródło
2
Czy Spring Boot nie obsługuje tego scenariusza po wyjęciu z pudełka? Zobacz dokumentację
konfiguracji
4

Być może piszę to zbyt późno, ale mam podobny problem, gdy próbowałem zastąpić metody odczytu właściwości.

Mój problem polegał na: 1) Odczytanie właściwości z env, jeśli ta właściwość została ustawiona w env 2) Odczytanie właściwości z właściwości systemowej, jeśli ta właściwość została ustawiona we właściwości systemowej 3) I na koniec, odczytanie z właściwości aplikacji.

Aby rozwiązać ten problem, przechodzę do mojej klasy konfiguracji komponentu bean

@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);
    }
}

I nadpisuj fabrykę w @PropertySource. A potem stworzyłem własną implementację do odczytu właściwości.

    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);
        }


    }

I utworzył 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);
    }
}

To mi pomogło.

Maksym Galich
źródło
4

Korzystając z kontekstu Spring 5.0 Udało mi się załadować poprawny plik właściwości w oparciu o środowisko systemowe za pomocą następującej adnotacji

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

Tutaj wartość MYENV jest odczytywana ze środowiska systemowego, a jeśli środowisko systemowe nie jest obecne, zostanie załadowany domyślny plik właściwości środowiska testowego, jeśli podam nieprawidłową wartość MYENV - uruchomienie aplikacji nie powiedzie się.

Uwaga: dla każdego profilu chcesz zachować - musisz utworzyć plik application- [profile] .property i chociaż użyłem Spring kontekstu 5.0 i nie Spring boot - wierzę, że to zadziała również na Spring 4.1

Abdeali Chandanwala
źródło
3

Napotkałem ten sam problem, co autor pytania. W naszym przypadku odpowiedzi w tym pytaniu były niewystarczające, ponieważ każdy z członków mojego zespołu miał inne środowisko lokalne i zdecydowanie potrzebowaliśmy .gitignorepliku, który miał inny ciąg połączenia db i poświadczenia, więc ludzie nie zatwierdzają wspólnego pliku przez pomyłkę i zrywaj połączenia db innych użytkowników.

Ponadto, gdy wykonaliśmy poniższą procedurę, było łatwo wdrożyć w różnych środowiskach i jako dodatkowy bonus nie musieliśmy mieć żadnych poufnych informacji w kontroli wersji .

Zdobycie pomysłu z frameworka Symfony 3 PHP, który ma parameters.yml(.gitignored) i parameters.yml.dist(który jest próbką, która tworzy pierwszą poprzez composer install),

Zrobiłem następujące, łącząc wiedzę z poniższych odpowiedzi: https://stackoverflow.com/a/35534970/986160 i https://stackoverflow.com/a/35535138/986160 .

Zasadniczo daje to swobodę korzystania z dziedziczenia konfiguracji sprężynowych i wyboru aktywnych profili poprzez konfigurację u góry oraz dodatkowe wrażliwe dane uwierzytelniające w następujący sposób:

application.yml.dist (próbka)

    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 na serwerze deweloperskim)

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 na komputerze lokalnym)

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 (dodatkowe właściwości specyficzne dla środowiska nie są wrażliwe)

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

To samo można zrobić z .properties

Michail Michailidis
źródło
0

Jeśli pliki właściwości są eksternalizowane jako zmienne środowiskowe, do konfiguracji IDE można dodać następującą konfigurację uruchamiania:

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

Mahesh K.
źródło