Menggunakan variabel env di application.properties Spring Boot

199

Kami sedang mengerjakan aplikasi web Spring Boot dan database yang kami gunakan adalah MySql ;

  • setup yang kita miliki adalah pertama kita mengujinya secara lokal (berarti kita perlu menginstal MySql pada PC kita);

  • lalu kita dorong ke Bitbucket ;

  • Jenkins secara otomatis mendeteksi dorongan baru untuk Bitbucket dan melakukan build di atasnya (untuk membangun Jenkins, kita juga perlu menginstal MySql pada mesin virtual yang menjalankan Jenkins).

  • jika Jenkins build pass, kami mendorong kode ke aplikasi kami di OpenShift (menggunakan plugin penempatan openshift di Jenkins).

Masalah yang kami miliki karena Anda mungkin sudah mengetahuinya adalah:

  • di application.propertieskita tidak bisa hard kode info MySql. Karena proyek kami akan berjalan di 3 tempat yang berbeda ( lokal , Jenkins , dan OpenShift ), kami perlu membuat bidang sumber data dinamis application.properties(kami tahu ada cara berbeda untuk melakukannya tetapi kami sedang mengerjakan solusi ini untuk saat ini).

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

Solusi yang kami temukan adalah kami membuat variabel lingkungan sistem secara lokal dan dalam Jenkins vm (menamainya dengan cara yang sama dengan OpenShift menamainya) dan menetapkan nilai yang tepat untuk masing-masingnya:

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"

Kami telah melakukan ini dan berhasil. Kami juga telah memeriksa Map<String, String> env = System.getenv();bahwa variabel lingkungan dapat dibuat menjadi variabel java seperti:

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

Sekarang satu-satunya yang tersisa adalah kita perlu menggunakan variabel-variabel java di kita application.propertiesdan itulah yang kita mengalami masalah dengan.

Di mana folder, dan bagaimana, kita perlu menetapkan password, userName, sqlURL, dan sqlPortvariabel untuk application.propertiesdapat melihat mereka dan bagaimana kita memasukkan mereka dalam application.properties?

Kami telah mencoba banyak hal, salah satunya adalah:

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

Tidak beruntung sejauh ini. Kami mungkin tidak meletakkan variabel env ini di kelas / folder yang benar atau menggunakannya secara salah application.properties.

Bantuan Anda sangat dihargai !!

Terima kasih!

SM
sumber
3
Baca @ConfigurationProperties untuk mempelajari lebih lanjut. Namun, ini adalah kasus penggunaan yang sempurna untuk properti konfigurasi khusus Profil
Eddie B

Jawaban:

267

Anda tidak perlu menggunakan variabel java. Untuk memasukkan variabel sistem env, tambahkan yang berikut ke application.propertiesfile Anda :

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}

Tapi cara yang disarankan oleh @Stefan Isele lebih disukai, karena dalam hal ini Anda harus menyatakan hanya satu variabel env: spring.profiles.active. Spring akan membaca file properti yang sesuai secara otomatis dengan application-{profile-name}.propertiestemplat.

Ken Bekov
sumber
12
Metode ini lebih nyaman untuk menghubungkan buruh pelabuhan. Misalnya: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
Ini benar-benar cara terbaik untuk pergi. Menggunakan variabel lingkungan berarti Anda tidak perlu membuat daftar rahasia dalam teks biasa di samping aplikasi Anda. Ini secara signifikan lebih aman dan mengurangi ketergantungan pada langkah-langkah keamanan akses kode sumber Anda untuk melindungi seluruh real Anda. Pos SO yang tidak disengaja dengan properti yang disertakan tidak menghasilkan informasi yang bocor.
kipper_t
51
Saya ingin menambahkan ini dan menyebutkan bahwa jika Anda menggunakan spring boot (tidak memeriksa apakah ia berfungsi tanpa boot), maka properti apa pun dapat ditimpa melalui variabel lingkungan secara otomatis tanpa mengubah properti application.properties Anda. yaitu, jika Anda memiliki sifat yang disebut spring.activemq.broker-urlkemudian variabel lingkungan yang sesuai akan: SPRING_ACTIVEMQ_BROKER_URL. titik dan garis pisah secara otomatis dikonversi ke garis bawah. Ini sangat nyaman saat bekerja dengan container / spring boot.
abe
15
Jika Anda mendesain untuk cloud, ini bukan cara yang lebih baik untuk menggunakan profil Spring. Menggunakan variabel lingkungan direkomendasikan oleh standar aplikasi 12 faktor: 12factor.net/config
Mikhail Golubtsov
6
Saya tahu topik ini agak lama. Tetapi Anda dapat menggabungkan kedua pengaturan variabel lingkungan dan pengaturan profil pegas. Profil Anda harus memiliki informasi statis sementara profil produksi Anda dapat menggunakan variabel lingkungan. Dengan cara ini dev tidak perlu lagi mendefinisikan variabel lingkungan pada mesin mereka jika mereka hanya ingin menyebarkan profil pengembangan.
underscore_05
72

Cara termudah untuk memiliki konfigurasi yang berbeda untuk lingkungan yang berbeda adalah dengan menggunakan profil pegas. Lihat konfigurasi eksternal .

Ini memberi Anda banyak fleksibilitas. Saya menggunakannya dalam proyek saya dan ini sangat membantu. Dalam kasus Anda, Anda akan memiliki 3 profil: 'lokal', 'jenkins', dan 'openshift'

Anda kemudian memiliki 3 profil file properti spesifik: application-local.properties, application-jenkins.properties, danapplication-openshift.properties

Di sana Anda dapat mengatur properti untuk lingkungan yang bersangkutan. Ketika Anda menjalankan aplikasi, Anda harus menentukan profil yang akan diaktifkan seperti ini: -Dspring.profiles.active=jenkins

Edit

Menurut pegas dokumen Anda dapat mengatur variabel lingkungan sistem SPRING_PROFILES_ACTIVEuntuk mengaktifkan profil dan tidak perlu meneruskannya sebagai parameter.

apakah ada cara untuk melewati opsi profil aktif untuk aplikasi web pada saat run?

Tidak. Pegas menentukan profil aktif sebagai salah satu langkah pertama, saat membangun konteks aplikasi. Profil aktif kemudian digunakan untuk memutuskan file properti mana yang dibaca dan kacang mana yang dipakai. Setelah aplikasi dimulai ini tidak dapat diubah.

Stefan Isele - prefabware.com
sumber
4
Saya suka jawaban ini, tetapi bagaimana jika Anda ingin nama profil berasal dari lingkungan? Saya sudah mencoba -Dspring.active.profiles = $ SPRING_ACTIVE_PROFILES, dan mengatur OS env var di /etc/profile.d/myenvvars.sh, tetapi Spring Boot tidak mengambilnya
Tom Hartwell
1
SPRING_PROFILES_ACTIVE berfungsi karena fitur pengikatan santai dari boot spring docs.spring.io/spring-boot/docs/1.3.0.BUILD-SNAPSHOT/reference/…
feed.me
5
terima kasih atas jawaban ini Stefan, itu berhasil untuk saya, tetapi dengan satu perubahan - properti ini sebenarnya spring.profiles.active dan bukan spring.active.profiles
Rudi
10
Sementara profil Spring dapat sangat berguna, dalam kaitannya dengan OP, profil Spring tidak cocok. Ini disebabkan oleh bagaimana kode sumber disimpan dan sensitivitas informasi properti disimpan dengan itu. Konteks OP ada di sekitar akses Database. Untuk situasi itu Anda tidak ingin detail prod dalam teks biasa dalam sumber. Ini berarti jika sumber dikompromikan maka basis data juga dikompromikan. Lebih baik menggunakan variabel env atau alat rahasia untuk ini, bukan seperti Vault. Saya lebih suka env. Saya juga membuat semua lingkungan beroperasi dengan cara yang sama dalam hal konsistensi. Itu menghindari kecelakaan di masa depan.
kipper_t
2
Anda dapat menggunakan file properti profil Boot Spring di luar aplikasi JAR. File khusus lingkungan ini, misalnya, application-production.propertiesakan digunakan untuk mesin produksi dengan cara yang aman, dan biasanya tidak akan berada dalam repositori kode sumber aplikasi.
Colin D Bennett
13

Ini sebagai tanggapan terhadap sejumlah komentar karena reputasi saya tidak cukup tinggi untuk berkomentar secara langsung.

Anda dapat menentukan profil saat runtime selama konteks aplikasi belum dimuat.

// 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
sumber
12

Flayway tidak mengenali variabel lingkungan langsung ke dalam application.properties (Spring-Boot V2.1). misalnya

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

Untuk mengatasi masalah ini, saya melakukan variabel lingkungan ini, biasanya saya membuat file .env:

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

Dan ekspor variabel ke lingkungan saya:

export $(cat .env | xargs)

Dan akhirnya jalankan saja perintahnya

mvn spring-boot:run

Atau jalankan file jar Anda

java -jar target/your-file.jar

Ada pendekatan lain di sini: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/maven-plugin/examples/run-env-variables.html

Felipe Girotti
sumber
1
Apa itu env-vars? Bagaimana mereka digunakan. Jawaban Anda merujuk pada hal-hal tanpa deskripsi lengkap dan Anda tidak menyertakan tautan apa pun. Saya hampir menurunkan suara ini, tetapi saya melihat perwakilan Anda adalah 21 sehingga Anda baru dan satu orang menemukan jawaban Anda berguna, jadi saya membiarkannya pergi, tetapi cobalah untuk memberikan lebih banyak informasi dalam jawaban di masa depan, dan selamat datang di SO (Stack Overflow). Saya harap Anda menikmatinya sama seperti saya.
PatS
2
Terima kasih @PatS, saya menambahkan lebih banyak detail, semoga bermanfaat.
Felipe Girotti
1
Perubahan luar biasa. Terima kasih memperbarui jawaban Anda.
PatS
9

Berikut ini adalah potongan kode melalui serangkaian file properti lingkungan yang sedang dimuat untuk lingkungan yang berbeda.

File properti di bawah sumber daya aplikasi Anda ( src / main / resources ): -

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

Idealnya, application.properties berisi semua properti umum yang dapat diakses untuk semua lingkungan dan properti terkait lingkungan hanya berfungsi pada lingkungan yang ditentukan. oleh karena itu urutan memuat file properti ini akan sedemikian rupa -

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

Cuplikan kode di sini: -

    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
sumber
2
Tidak Spring Boot menangani skenario ini di luar kotak? Lihat dokumentasi Config Eksternal di sini
ChickenFeet
4

Mungkin saya menulis ini terlambat, tetapi saya mendapatkan masalah yang sama ketika saya mencoba menimpa metode untuk membaca properti.

Masalah saya adalah: 1) Baca properti dari env jika properti ini telah diatur dalam env 2) Baca properti dari properti sistem jika properti ini telah diselesaikan di properti sistem 3) Dan terakhir, baca dari properti aplikasi.

Jadi, untuk menyelesaikan masalah ini saya pergi ke kelas konfigurasi kacang saya

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

Dan menimpa pabrik di @PropertySource. Dan kemudian saya telah membuat implementasi saya sendiri untuk membaca properti.

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


    }

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

Jadi, ini telah membantu saya.

Maksym Galich
sumber
4

Menggunakan konteks Spring 5.0 Saya telah berhasil mencapai memuat file properti yang benar berdasarkan pada lingkungan sistem melalui anotasi berikut

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

Di sini nilai MYENV dibaca dari lingkungan sistem dan jika lingkungan sistem tidak ada maka file properti lingkungan pengujian standar akan dimuat, jika saya memberikan nilai MYENV yang salah - itu akan gagal untuk memulai aplikasi.

Catatan: untuk setiap profil, Anda ingin mempertahankan - Anda harus membuat aplikasi - [profil] file .property dan meskipun saya menggunakan konteks Spring 5.0 & bukan boot Spring - Saya percaya ini juga akan bekerja pada Spring 4.1

Abdeali Chandanwala
sumber
3

Saya menghadapi masalah yang sama dengan penulis pertanyaan. Untuk kasus kami, jawaban dalam pertanyaan ini tidak cukup karena masing-masing anggota tim saya memiliki lingkungan lokal yang berbeda dan kami pasti perlu .gitignorefile yang memiliki string koneksi db dan kredensial yang berbeda, sehingga orang tidak melakukan file umum karena kesalahan dan memutuskan koneksi db orang lain.

Selain itu, ketika kami mengikuti prosedur di bawah ini, mudah untuk digunakan di lingkungan yang berbeda dan sebagai bonus tambahan kami tidak perlu memiliki informasi sensitif dalam kontrol versi sama sekali .

Mendapatkan ide dari kerangka kerja PHP Symfony 3 yang memiliki parameters.yml(.gitignored) dan a parameters.yml.dist(yang merupakan sampel yang membuat yang pertama melalui composer install),

Saya melakukan yang berikut menggabungkan pengetahuan dari jawaban di bawah ini: https://stackoverflow.com/a/35534970/986160 dan https://stackoverflow.com/a/35535138/986160 .

Pada dasarnya ini memberikan kebebasan untuk menggunakan warisan konfigurasi pegas dan memilih profil aktif melalui konfigurasi di atas ditambah kredensial tambahan yang sensitif sebagai berikut:

application.yml.dist (contoh)

    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 di server dev)

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 pada mesin lokal)

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 (sifat khusus lingkungan tambahan tidak sensitif)

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

Hal yang sama dapat dilakukan dengan .properties

Michail Michailidis
sumber