Usando a variável env no application.properties do Spring Boot

199

Estamos trabalhando em um aplicativo Web Spring Boot e o banco de dados que estamos usando é MySql ;

  • a configuração que temos é primeiro testá-lo localmente (significa que precisamos instalar o MySql no nosso PC);

  • então nós empurramos para Bitbucket ;

  • Jenkins detecta automaticamente o novo envio para o Bitbucket e faz uma compilação (para o Jenkins mvn build passar, também precisamos instalar o MySql nas máquinas virtuais que estão executando o Jenkins).

  • se a construção do Jenkins for aprovada, enviaremos o código para o nosso aplicativo no OpenShift (usando o plug-in de implantação Openshift no Jenkins).

O problema que temos como você já deve ter percebido é que:

  • em application.propertiesque não podemos duro código a informação MySql. Como nosso projeto será executado em 3 locais diferentes ( local , Jenkins e OpenShift ), precisamos dinamizar o campo da fonte de dados application.properties(sabemos que existem maneiras diferentes de fazê-lo, mas estamos trabalhando nessa solução por enquanto).

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

A solução que criamos é criar variáveis ​​de ambiente do sistema localmente e no Jenkins vm (nomeando-as da mesma maneira que o OpenShift os nomeia) e atribuindo-lhes os valores corretos, respectivamente:

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"

Fizemos isso e funciona. Também verificamos se Map<String, String> env = System.getenv();as variáveis ​​de ambiente podem ser transformadas em variáveis ​​java da seguinte forma:

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

Agora, a única coisa que resta é que precisamos usar essas variáveis ​​java em nossa application.properties e é com isso que estamos tendo problemas.

Em que pasta, e como, não é necessário atribuir os password, userName, sqlURL, e sqlPortvariáveis paraapplication.properties ser capaz de vê-los e como é que vamos incluí-los em application.properties?

Tentamos muitas coisas, sendo uma delas:

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

Sem sorte até agora. Provavelmente, não estamos colocando essas variáveis ​​env na classe / pasta correta ou as estamos usando incorretamenteapplication.properties .

Sua ajuda é muito apreciada!!

Obrigado!

SM
fonte
3
Leia @ConfigurationProperties para saber mais. No entanto, este é um caso de uso perfeito para propriedades de configuração perfil específico
Eddie B

Respostas:

267

Você não precisa usar variáveis ​​java. Para incluir variáveis ​​de ambiente do sistema, adicione o seguinte ao seu application.propertiesarquivo:

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}

Mas o caminho sugerido por @Stefan Isele é mais preferível, porque neste caso você tem que declarar apenas uma variável env: spring.profiles.active. O Spring lerá o arquivo de propriedades apropriado automaticamente por application-{profile-name}.propertiesmodelo.

Ken Bekov
fonte
12
Este método é mais conveniente para a vinculação de janela de encaixe. Por exemplo: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
Este é absolutamente o melhor caminho a percorrer. O uso de variáveis ​​de ambiente significa que você não precisa listar segredos em texto sem formatação ao lado do aplicativo. Isso é significativamente mais seguro e reduz a dependência das medidas de segurança de acesso ao código fonte para proteger toda a propriedade. Uma postagem SO acidental com propriedades incluídas não resulta em vazamento de informações.
22417 kipper_t
51
Eu queria acrescentar isso e mencionar que, se você estiver usando a inicialização por mola (não verificou se funciona sem inicialização), qualquer propriedade poderá ser substituída por uma variável de ambiente automaticamente sem modificar seu application.properties. ou seja, se você tem uma propriedade chamada spring.activemq.broker-url, em seguida, a variável de ambiente correspondente seria: SPRING_ACTIVEMQ_BROKER_URL. períodos e traços são convertidos automaticamente para sublinhados. Isso é extremamente conveniente ao trabalhar com contêineres / bota com mola.
abe
15
Se você projeta para nuvem, não é uma maneira preferível de usar perfis do Spring. Usando variáveis de ambiente é recomendado por 12 fator de aplicativo padrão: 12factor.net/config
Mikhail Golubtsov
6
Eu sei que este tópico é um pouco antigo. Mas você pode combinar a configuração da variável de ambiente e a configuração do perfil da mola. Seu perfil de desenvolvedor deve ter informações estáticas enquanto seu perfil de produção pode fazer uso das variáveis ​​de ambiente. Dessa maneira, os desenvolvedores não precisam mais definir variáveis ​​de ambiente em suas máquinas, se quiserem apenas implantar o perfil de desenvolvimento.
Underscore_05
72

A maneira mais fácil de ter configurações diferentes para ambientes diferentes é usar perfis de mola. Veja configuração externalizada .

Isso oferece muita flexibilidade. Estou usando-o em meus projetos e é extremamente útil. No seu caso, você teria três perfis: 'local', 'jenkins' e 'openshift'

Então você tem arquivos de propriedade específica 3 Perfil: application-local.properties, application-jenkins.propertieseapplication-openshift.properties

Lá, você pode definir as propriedades para o ambiente em questão. Ao executar o aplicativo, você deve especificar o perfil para ativar da seguinte maneira: -Dspring.profiles.active=jenkins

Editar

De acordo com o documento da primavera, você pode definir a variável de ambiente do sistema SPRING_PROFILES_ACTIVEpara ativar perfis e não precisa passá-la como parâmetro.

existe alguma maneira de passar a opção de perfil ativo para aplicativo da web em tempo de execução?

Não. O Spring determina os perfis ativos como uma das primeiras etapas ao criar o contexto do aplicativo. Os perfis ativos são usados ​​para decidir quais arquivos de propriedades são lidos e quais beans são instanciados. Depois que o aplicativo é iniciado, isso não pode ser alterado.

Stefan Isele - prefabware.com
fonte
4
Gosto dessa resposta, mas e se você quiser que o nome do perfil seja proveniente do ambiente? Eu tentei -Dspring.active.profiles = $ SPRING_ACTIVE_PROFILES, e definindo o var OS env em /etc/profile.d/myenvvars.sh, mas não Primavera Bota não pegá-lo
Tom Hartwell
1
SPRING_PROFILES_ACTIVE funciona por causa do recurso de encadernação descontraído do boot de primavera docs.spring.io/spring-boot/docs/1.3.0.BUILD-SNAPSHOT/reference/…
feed.me
5
obrigado por esta resposta Stefan, ele trabalhou para mim, mas com uma mudança - a propriedade é realmente spring.profiles.active e não spring.active.profiles
Rudi
10
Embora os perfis do Spring possam ser muito úteis, em relação ao OP, eles não são adequados. Isso ocorre devido à maneira como o código-fonte é armazenado e à sensibilidade das informações de propriedades armazenadas com isso. O contexto do OP está relacionado ao acesso ao banco de dados. Para essa situação, você não deseja detalhes do produto em texto sem formatação na fonte. Isso significa que se a fonte estiver comprometida, o banco de dados também estará comprometido. É melhor usar variáveis ​​env ou ferramentas secretas para isso, como o Vault. Eu prefiro env. Eu também faria todos os ambientes operarem da mesma maneira nesse aspecto para obter consistência. Evita acidentes no futuro.
22417 kipper_t
2
Você pode usar um arquivo de propriedades de perfil do Spring Boot externo ao JAR do aplicativo. Esse arquivo específico do ambiente, por exemplo, application-production.propertiesseria implementado na máquina de produção de maneira segura e normalmente não estaria no repositório de código-fonte do aplicativo.
Colin D Bennett
13

Isso é uma resposta a vários comentários, pois minha reputação não é alta o suficiente para comentar diretamente.

Você pode especificar o perfil no tempo de execução, desde que o contexto do aplicativo ainda não tenha sido carregado.

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

O Flayway não reconhece as variáveis ​​de ambiente diretas no application.properties (Spring-Boot V2.1). por exemplo

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

Para resolver esse problema, fiz essas variáveis ​​de ambiente, geralmente crio o arquivo .env:

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

E exporte as variáveis ​​para o meu ambiente:

export $(cat .env | xargs)

E, finalmente, basta executar o comando

mvn spring-boot:run

Ou execute seu arquivo jar

java -jar target/your-file.jar

Existe outra abordagem aqui: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/maven-plugin/examples/run-env-variables.html

Felipe Girotti
fonte
1
O que é env-vars? Como eles são usados. Sua resposta se refere a coisas sem uma descrição completa e você não inclui nenhum link. Eu quase derrotei isso, mas vejo que seu representante tem 21 anos, então você é novo e uma pessoa achou sua resposta útil, então deixei para lá, mas tente fornecer mais informações em respostas futuras e seja bem-vindo ao SO (Stack Overflow). Espero que você goste tanto quanto eu.
PatS
2
Obrigado @PatS, adicionei mais detalhes, espero que seja útil.
Felipe Girotti
1
Excelentes mudanças. Obrigado atualizando sua resposta.
PatS
9

Aqui está um código de trecho através de uma cadeia de arquivos de propriedades dos ambientes que estão sendo carregados para diferentes ambientes.

Arquivo de propriedades nos recursos de seu aplicativo ( src / main / resources ): -

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

Idealmente, application.properties contém todas as propriedades comuns acessíveis a todos os ambientes, e as propriedades relacionadas ao ambiente só funcionam no ambiente especificado. portanto, a ordem de carregamento desses arquivos de propriedades será da seguinte maneira -

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

Fragmento de código aqui: -

    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
fonte
2
O Spring Boot não lida com esse cenário imediatamente? Veja a documentação de configuração externa aqui
ChickenFeet 5/19/19
4

Talvez eu escreva isso tarde demais, mas tive o mesmo problema ao tentar substituir os métodos de leitura de propriedades.

Meu problema foi: 1) Leia a propriedade do env se essa propriedade tiver sido configurada no env 2) Leia a propriedade da propriedade do sistema se essa propriedade tiver sido configurada na propriedade do sistema 3) E por último, leia as propriedades do aplicativo.

Então, para resolver esse problema, vou para minha classe de configuração de 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);
    }
}

E substitua a fábrica no @PropertySource. E então eu criei minha própria implementação para propriedades de leitura.

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


    }

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

Então, isso me ajudou.

Maksym Galich
fonte
4

Usando o contexto 5.0 do Spring, consegui carregar o arquivo de propriedades correto com base no ambiente do sistema através da seguinte anotação

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

Aqui, o valor MYENV é lido no ambiente do sistema e, se o ambiente do sistema não estiver presente, o arquivo de propriedades do ambiente de teste padrão será carregado, se eu fornecer um valor MYENV errado - ele falhará ao iniciar o aplicativo.

Nota: para cada perfil, você deseja manter - você precisará criar um arquivo application- [profile] .property e, embora eu tenha usado o contexto Spring 5.0 e não a inicialização Spring - acredito que isso também funcionará no Spring 4.1

Abdeali Chandanwala
fonte
3

Eu enfrentei o mesmo problema que o autor da pergunta. Para o nosso caso, as respostas nesta pergunta não foram suficientes, pois cada um dos membros da minha equipe tinha um ambiente local diferente e, definitivamente, precisávamos .gitignoredo arquivo com as diferentes cadeias de conexão e credenciais de conexão db, para que as pessoas não comprometessem o arquivo comum por engano e interrompa as conexões db de outras pessoas.

Além disso, quando seguimos o procedimento abaixo, foi fácil implantar em diferentes ambientes e, como bônus extra , não precisamos ter nenhuma informação confidencial no controle de versão .

Obtendo a idéia da estrutura do PHP Symfony 3 que possui um parameters.yml(.gitignored) e um parameters.yml.dist(que é uma amostra que cria a primeira atravéscomposer install ),

Fiz o seguinte combinando o conhecimento das respostas abaixo: https://stackoverflow.com/a/35534970/986160 e https://stackoverflow.com/a/35535138/986160 .

Essencialmente, isso oferece a liberdade de usar a herança das configurações de mola e escolher perfis ativos por meio da configuração na parte superior, além de quaisquer credenciais extras sensíveis, como a seguir:

application.yml.dist (amostra)

    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 no servidor de desenvolvimento)

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 máquina local)

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 (propriedades adicionais específicas do ambiente não sensíveis)

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

O mesmo pode ser feito com .properties

Michail Michailidis
fonte