Sử dụng biến env trong application.properies của Spring Boot


198

Chúng tôi đang làm việc trên một ứng dụng web Spring Boot và cơ sở dữ liệu chúng tôi đang sử dụng là MySql ;

  • thiết lập mà chúng tôi có là trước tiên chúng tôi kiểm tra nó cục bộ (có nghĩa là chúng tôi cần cài đặt MySql trên PC của chúng tôi);

  • sau đó chúng tôi đẩy sang Bitbucket ;

  • Jenkins tự động phát hiện lần đẩy mới tới Bitbucket và thực hiện việc xây dựng nó (để Jenkins mvn xây dựng để vượt qua, chúng ta cũng cần cài đặt MySql trên các máy ảo đang chạy Jenkins).

  • nếu bản dựng Jenkins vượt qua, chúng tôi sẽ đẩy mã vào ứng dụng của mình trên OpenShift (sử dụng plugin triển khai Openshift trên Jenkins).

Vấn đề chúng tôi gặp phải như bạn có thể đã nhận ra đó là:

  • trong application.propertieschúng tôi không thể mã cứng thông tin MySql. Vì dự án của chúng tôi sẽ chạy ở 3 nơi khác nhau ( cục bộ , JenkinsOpenShift ), chúng tôi cần làm cho trường nguồn dữ liệu động application.properties(chúng tôi biết có nhiều cách khác nhau để thực hiện nhưng hiện tại chúng tôi đang nghiên cứu giải pháp này).

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

Giải pháp chúng tôi đưa ra là chúng tôi tạo các biến môi trường hệ thống cục bộ và trong vm Jenkins (đặt tên chúng theo cách giống như OpenShift đặt tên cho chúng) và gán cho chúng các giá trị đúng tương ứng:

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"

Chúng tôi đã làm điều này và nó hoạt động. Chúng tôi cũng đã kiểm tra Map<String, String> env = System.getenv();rằng các biến môi trường có thể được tạo thành các biến java như sau:

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

Bây giờ điều duy nhất còn lại là chúng ta cần sử dụng các biến java này trong application.properties đó và đó là điều chúng ta đang gặp rắc rối.

Trong đó thư mục, và làm thế nào, chúng ta cần gán password, userName, sqlURL, và sqlPortcác biến cho application.propertiesđể có thể nhìn thấy chúng và làm thế nào để chúng ta đưa vào application.properties?

Chúng tôi đã thử nhiều thứ mà một trong số chúng là:

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

Không có may mắn cho đến nay. Chúng tôi có thể không đặt các biến env này vào đúng lớp / thư mục hoặc đang sử dụng chúng không chính xác application.properties.

Giúp đỡ của bạn được đánh giá cao!!

Cảm ơn!


3
Đọc @ConfigurationProperies để tìm hiểu thêm. Tuy nhiên, đây là trường hợp sử dụng hoàn hảo cho các thuộc tính cấu hình cụ thể của Hồ sơ
Eddie B

Câu trả lời:


266

Bạn không cần sử dụng các biến java. Để bao gồm các biến env hệ thống, thêm các mục sau vào application.propertiestệp của bạn :

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}

Nhưng cách được đề xuất bởi @Stefan Isele thì tốt hơn, bởi vì trong trường hợp này, bạn phải khai báo chỉ một biến env : spring.profiles.active. Spring sẽ tự động đọc tệp thuộc tính thích hợp theo application-{profile-name}.propertiesmẫu.


12
Phương pháp này thuận tiện hơn cho liên kết docker. Ví dụ: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
Đây hoàn toàn là cách tốt nhất để đi. Sử dụng các biến môi trường có nghĩa là bạn không cần liệt kê các bí mật trong văn bản đơn giản dọc theo ứng dụng của mình. Điều này an toàn hơn đáng kể và giảm sự phụ thuộc vào các biện pháp bảo mật truy cập mã nguồn của bạn để bảo vệ toàn bộ tài sản của bạn. Một bài viết SO tình cờ có các thuộc tính bao gồm không dẫn đến thông tin bị rò rỉ.
kipper_t

51
Tôi muốn thêm vào điều này và đề cập rằng nếu bạn đang sử dụng spring boot (không kiểm tra nếu nó hoạt động mà không khởi động), thì bất kỳ thuộc tính nào cũng có thể được ghi đè thông qua một biến môi trường mà không cần sửa đổi ứng dụng của bạn. tức là, nếu bạn có một thuộc tính được gọi spring.activemq.broker-urlthì biến môi trường tương ứng sẽ là : SPRING_ACTIVEMQ_BROKER_URL. dấu chấm và dấu gạch ngang được tự động chuyển đổi thành dấu gạch dưới. Điều này cực kỳ thuận tiện khi làm việc với container / spring boot.
abe

15
Nếu bạn thiết kế cho đám mây, đó không phải là cách tốt hơn để sử dụng cấu hình Spring. Sử dụng biến môi trường được khuyến nghị theo tiêu chuẩn ứng dụng 12 yếu tố: 12factor.net/config
Mikhail Golubtsov

6
Tôi biết chủ đề này là một chút cũ. Nhưng bạn có thể kết hợp cả thiết lập biến môi trường và thiết lập hồ sơ mùa xuân. Hồ sơ dev của bạn nên có thông tin tĩnh trong khi hồ sơ sản xuất của bạn có thể sử dụng các biến môi trường. Theo cách này, dev không còn cần xác định các biến môi trường trên máy của họ nếu họ chỉ muốn triển khai cấu hình phát triển.
gạch

72

Cách dễ nhất để có cấu hình khác nhau cho các môi trường khác nhau là sử dụng cấu hình lò xo. Xem cấu hình bên ngoài .

Điều này cung cấp cho bạn rất nhiều tính linh hoạt. Tôi đang sử dụng nó trong các dự án của tôi và nó cực kỳ hữu ích. Trong trường hợp của bạn, bạn sẽ có 3 hồ sơ: 'cục bộ', 'jenkins' và 'openshift'

Sau đó bạn có các file tài sản cụ thể 3 hồ sơ: application-local.properties, application-jenkins.properties, vàapplication-openshift.properties

Ở đó bạn có thể thiết lập các thuộc tính cho môi trường liên quan. Khi bạn chạy ứng dụng, bạn phải chỉ định hồ sơ để kích hoạt như thế này: -Dspring.profiles.active=jenkins

Biên tập

Theo tài liệu mùa xuân, bạn có thể đặt biến môi trường hệ thống SPRING_PROFILES_ACTIVEđể kích hoạt các cấu hình và không cần truyền nó làm tham số.

Có cách nào để vượt qua tùy chọn hồ sơ hoạt động cho ứng dụng web trong thời gian chạy không?

Không. Spring xác định các cấu hình hoạt động là một trong những bước đầu tiên, khi xây dựng bối cảnh ứng dụng. Các cấu hình hoạt động sau đó được sử dụng để quyết định các tệp thuộc tính nào được đọc và các bean nào được khởi tạo. Khi ứng dụng được khởi động, điều này không thể thay đổi.


4
Tôi thích câu trả lời này, nhưng nếu bạn muốn tên hồ sơ đến từ môi trường thì sao? Tôi đã thử -Dspring.active.profiles = $ SPRING_ACTIVE_PROFILES và đặt hệ điều hành env var trong /etc/profile.d/myenvvars.sh, nhưng Spring Boot không nhận được điều đó
Tom Hartwell

1
SPRING_PROFILES_ACTIVE hoạt động nhờ tính năng ràng buộc thoải mái của docs
feed.me

5
cảm ơn vì câu trả lời này Stefan, nó đã làm việc cho tôi, nhưng với một thay đổi - tài sản thực sự là spring.profiles.active và không phải spring.active.profiles
Rudi

10
Mặc dù cấu hình Spring có thể rất hữu ích, liên quan đến OP mà chúng không phù hợp. Điều này là do cách mã nguồn được lưu trữ và độ nhạy cảm của thông tin thuộc tính được lưu trữ với điều đó. Bối cảnh OP là xung quanh cơ sở dữ liệu truy cập. Đối với tình huống đó, bạn không muốn chi tiết prod trong văn bản đơn giản trong nguồn. Điều này có nghĩa là nếu nguồn bị xâm phạm thì cơ sở dữ liệu cũng bị xâm phạm. Tốt hơn là sử dụng các biến env hoặc các công cụ bí mật cho việc này thay vì Vault. Tôi thích env. Tôi cũng sẽ làm cho tất cả các môi trường hoạt động theo cùng một cách liên quan đến tính nhất quán này. Nó tránh được tai nạn trong tương lai.
kipper_t

2
Bạn có thể sử dụng tệp thuộc tính cấu hình Spring Boot bên ngoài JAR của ứng dụng. Ví dụ, tệp dành riêng cho môi trường này application-production.propertiessẽ được triển khai cho máy sản xuất một cách an toàn và thường không có trong kho mã nguồn ứng dụng.
Colin D Bennett

13

Điều này là để đáp lại một số ý kiến ​​vì danh tiếng của tôi không đủ cao để bình luận trực tiếp.

Bạn có thể chỉ định hồ sơ trong thời gian chạy miễn là bối cảnh ứng dụng chưa được tải.

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

12

Flayway không nhận ra các biến môi trường trực tiếp vào application.properations (Spring-Boot V2.1). ví dụ

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

Để giải quyết vấn đề này, tôi đã thực hiện các biến môi trường này, thông thường tôi tạo tệp .env:

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

Và xuất các biến sang môi trường của tôi:

export $(cat .env | xargs)

Và cuối cùng chỉ cần chạy lệnh

mvn spring-boot:run

Hoặc chạy tệp jar của bạn

java -jar target/your-file.jar

Có một cách tiếp cận khác ở đây: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/maven-plugin/examples/run-env-variables.html


1
Env-vars là gì? Chúng được sử dụng như thế nào. Câu trả lời của bạn đề cập đến những điều không có mô tả đầy đủ và bạn không bao gồm bất kỳ liên kết. Tôi gần như đã đánh giá thấp điều này, nhưng tôi thấy đại diện của bạn là 21 vì vậy bạn là người mới và một người thấy câu trả lời của bạn hữu ích, vì vậy tôi đã bỏ qua, nhưng cố gắng cung cấp thêm thông tin trong các câu trả lời trong tương lai và chào mừng bạn đến với SO (Stack Overflow). Tôi hy vọng bạn thích nó nhiều như tôi làm.
PatS

2
Cảm ơn @PatS, tôi đã thêm chi tiết, hy vọng nó sẽ hữu ích.
Felipe Girotti

1
Thay đổi tuyệt vời. Cảm ơn cập nhật câu trả lời của bạn.
PatS

9

Đây là đoạn mã thông qua một chuỗi các tệp thuộc tính môi trường đang được tải cho các môi trường khác nhau.

Tệp thuộc tính trong tài nguyên ứng dụng của bạn ( src / main / resource ): -

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

Lý tưởng nhất là application.properies chứa tất cả các thuộc tính chung có thể truy cập được đối với tất cả các môi trường và thuộc tính liên quan đến môi trường chỉ hoạt động trên môi trường chỉ định. do đó thứ tự tải các tệp thuộc tính này sẽ theo cách như vậy -

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

Đoạn mã ở đây: -

    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 không xử lý tình huống này ra khỏi hộp sao? Xem tài liệu Cấu hình bên ngoài tại đây
ChickenFeet

4

Có thể tôi viết điều này quá muộn, nhưng tôi đã gặp vấn đề tương tự khi tôi đã cố gắng ghi đè các phương thức để đọc các thuộc tính.

Vấn đề của tôi là: 1) Đọc thuộc tính từ env nếu thuộc tính này đã được đặt trong env 2) Đọc thuộc tính từ thuộc tính hệ thống nếu thuộc tính này đã được xử lý trong thuộc tính hệ thống 3) Và cuối cùng, đọc từ thuộc tính ứng dụng.

Vì vậy, để giải quyết vấn đề này, tôi đi đến lớp cấu hình bean của tôi

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

Và ghi đè lên nhà máy trong @PropertySource. Và sau đó tôi đã tạo ra triển khai của riêng tôi để đọc thuộc tính.

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


    }

Và đã tạo 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);
    }
}

Vì vậy, điều này đã giúp tôi.


4

Sử dụng bối cảnh Spring 5.0 Tôi đã đạt được thành công tải tệp thuộc tính chính xác dựa trên môi trường hệ thống thông qua chú thích sau

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

Ở đây giá trị MYENV được đọc từ môi trường hệ thống và nếu không có môi trường hệ thống thì tệp thuộc tính môi trường kiểm tra mặc định sẽ được tải, nếu tôi đưa ra giá trị MYENV sai - nó sẽ không khởi động được ứng dụng.

Lưu ý: đối với mỗi cấu hình, bạn muốn duy trì - bạn sẽ cần tạo một ứng dụng- [hồ sơ] .property và mặc dù tôi đã sử dụng Spring bối cảnh 5.0 và không phải Spring boot - Tôi tin rằng điều này cũng sẽ hoạt động vào Spring 4.1


3

Tôi phải đối mặt với cùng một vấn đề như tác giả của câu hỏi. Đối với câu trả lời cho trường hợp của chúng tôi trong câu hỏi này là không đủ vì mỗi thành viên trong nhóm của tôi có môi trường cục bộ khác nhau và chúng tôi chắc chắn cần đến .gitignoretệp có chuỗi kết nối db và thông tin đăng nhập khác nhau, vì vậy mọi người không cam kết tệp chung do nhầm lẫn và phá vỡ các kết nối db của người khác.

Trên hết, khi chúng tôi thực hiện theo quy trình bên dưới, thật dễ dàng để triển khai trên các môi trường khác nhau và vì phần thưởng thêm, chúng tôi không cần phải có bất kỳ thông tin nhạy cảm nào trong kiểm soát phiên bản .

Lấy ý tưởng từ khung công tác PHP Symfony 3 có parameters.yml(.gitignored) và parameters.yml.dist(là mẫu tạo ra cái đầu tiên thông quacomposer install ),

Tôi đã thực hiện các kiến ​​thức sau đây từ các câu trả lời dưới đây: https://stackoverflow.com/a/35534970/986160https://stackoverflow.com/a/35535138/986160 .

Về cơ bản, điều này cho phép tự do sử dụng kế thừa cấu hình lò xo và chọn cấu hình hoạt động thông qua cấu hình ở trên cùng cộng với bất kỳ thông tin xác thực bổ sung nào như sau:

application.yml.dist (mẫu)

    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 trên máy chủ 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 trên máy cục bộ)

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 (thuộc tính môi trường cụ thể không nhạy cảm)

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

Điều tương tự có thể được thực hiện với .properations

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.