rayman
rayman

Reputation: 21616

How can I override Spring Boot application.properties programmatically?

I have jdbc property files which I take from external configuration web-service In spring boot in order to set mysql props it's easy as adding those to application.properties:

spring.datasource.url=jdbc:mysql://localhost/mydb
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

How could I override those programticlly in my app?

same goes for Spring-batch props:

database.driver=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost/mydv
database.username=root
database.password=root

Upvotes: 109

Views: 148171

Answers (12)

Lukas Hinsch
Lukas Hinsch

Reputation: 1860

You can add additional property sources in a lifecycle listener reacting to ApplicationEnvironmentPrepared event.

Something along the lines of:

import java.util.Properties;
import org.springframework.core.env.PropertiesPropertySource;


public class DatabasePropertiesListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
  public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    ConfigurableEnvironment environment = event.getEnvironment();
    Properties props = new Properties();
    props.put("spring.datasource.url", "<my value>");
    environment.getPropertySources().addFirst(new PropertiesPropertySource("myProps", props));
  }
}

Then register the class in src/main/resources/META-INF/spring.factories:

org.springframework.context.ApplicationListener=my.package.DatabasePropertiesListener

This worked for me, however, you are sort of limited as to what you can do at this point as it's fairly early in the application startup phase, you'd have to find a way to get the values you need without relying on other spring beans etc.

Upvotes: 91

Raphael Amoedo
Raphael Amoedo

Reputation: 4465

To add another option, there's a BeanPostProcessor class that you can implement. It provides two methods:

postProcessAfterInitialization and postProcessBeforeInitialization

Factory hook that allows for custom modification of new bean instances — for example, checking for marker interfaces or wrapping beans with proxies. Typically, post-processors that populate beans via marker interfaces or the like will implement postProcessBeforeInitialization(java.lang.Object, java.lang.String), while post-processors that wrap beans with proxies will normally implement postProcessAfterInitialization(java.lang.Object, java.lang.String).

In my code example, I was using it to override Kafka Location properties, because for SSL it doesn't read from classpath.

So this was the code:

import io.confluent.kafka.schemaregistry.client.SchemaRegistryClientConfig;
import java.io.IOException;
import java.util.Arrays;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.kafka.common.config.SslConfigs;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

@Configuration
@RequiredArgsConstructor
public class KafkaConfiguration implements BeanPostProcessor {

  @Value("${spring.kafka.ssl.key-store-location:}")
  private Resource keyStoreResource;
  @Value("${spring.kafka.properties.schema.registry.ssl.truststore.location:}")
  private Resource trustStoreResource;
  private final Environment environment;

  @SneakyThrows
  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof KafkaProperties) {
      KafkaProperties kafkaProperties = (KafkaProperties) bean;
      if(isLocalProfileActive()) {
        configureStoreLocation(kafkaProperties);
      }
    }
    return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
  }

  private boolean isLocalProfileActive() {
    return Arrays.stream(environment.getActiveProfiles()).anyMatch(profile -> "local".equals(profile));
  }

  private void configureStoreLocation(KafkaProperties kafkaProperties) throws IOException {
    kafkaProperties.getSsl().setKeyStoreLocation(new FileSystemResource(keyStoreResource.getFile().getAbsolutePath()));
    kafkaProperties.getProperties().put(SchemaRegistryClientConfig.CLIENT_NAMESPACE + SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG, keyStoreResource.getFile().getAbsolutePath());
    kafkaProperties.getSsl().setTrustStoreLocation(new FileSystemResource(trustStoreResource.getFile().getAbsolutePath()));
    kafkaProperties.getProperties().put(SchemaRegistryClientConfig.CLIENT_NAMESPACE + SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, trustStoreResource.getFile().getAbsolutePath());
  }

}

This way I could have on my properties file:

spring.kafka.ssl.key-store-location=classpath:mykeystore.jks

And the code would get the absolute path from that and set it. It makes also possible to filter based on profiles.

In your scenario, you would check probably for DataSourceProperties. It's important to mention that BeanPostProcessor runs for EVERY bean, so make sure you filter what you want.

Upvotes: 1

A. Shipulin
A. Shipulin

Reputation: 1

Add string to your Application class

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        System.setProperty("spring.config.location",
                "file:///D:/SpringProjects/SpringBootApp/application.properties");

        SpringApplication.run(Application.class, args);
    }

}

Upvotes: 0

Yonas
Yonas

Reputation: 449

If you need to do this for testing purposes: since spring-test 5.2.5 you can use @DynamicPropertySource:

    @DynamicPropertySource
    static void setDynamicProperties(DynamicPropertyRegistry registry) {
        registry.add("some.property", () -> some.way().of(supplying).a(value) );
    }

Takes precedence over pretty much all of the other ways of supplying properties. The method must be static though.

Upvotes: 19

idmitriev
idmitriev

Reputation: 5064

It could be very simple:

@SpringBootApplication
public class SampleApplication {

  public static void main(String[] args) {
    new SpringApplicationBuilder(SampleApplication.class)
        .properties(props())
        .build()
        .run(args);
  }

  private static Properties props() {
    Properties properties = new Properties();
    properties.setProperty("MY_VAR", "IT WORKS");
    return properties;
  }
}

application.yml

test:
  prop: ${MY_VAR:default_value}

Upvotes: 15

Vadim Kirilchuk
Vadim Kirilchuk

Reputation: 3542

This is how you can set properties during startup if you are running spring boot application.

The easiest way is to set the properties before you even started an app.

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(Application.class);
        ConfigurableEnvironment env = new ConfigurableEnvironment();
        env.setActiveProfiles("whatever");

        Properties properties = new Properties();
        properties.put("server.port", 9999);
        env.getPropertySources()
            .addFirst(new PropertiesPropertySource("initProps", properties));

        application.setEnvironment(env);
        application.run(args);
    }
}

Upvotes: 9

Jason DeMorrow
Jason DeMorrow

Reputation: 579

As of Spring Boot 2.0.X, you can dynamically override individual properties (for example, in a unit test) using a combination of a custom ApplicationContextInitializer and the ContextConfiguration annotation.

import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.PortTest.RandomPortInitailizer;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.util.SocketUtils;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(initializers = RandomPortInitializer.class)
public class PortTest {
    @Autowired
    private SomeService service;

    @Test
    public void testName() throws Exception {
        System.out.println(this.service);
        assertThat(this.service.toString()).containsOnlyDigits();
    }

    @Configuration
    static class MyConfig {

        @Bean
        public SomeService someService(@Value("${my.random.port}") int port) {
            return new SomeService(port);
        }
    }

    static class SomeService {
        private final int port;

        public SomeService(int port) {
            this.port = port;
        }

        @Override
        public String toString() {
            return String.valueOf(this.port);
        }
    }

    public static class RandomPortInitializer
            implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            int randomPort = SocketUtils.findAvailableTcpPort();
            TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext,
                    "my.random.port=" + randomPort);
        }
    }
}

Upvotes: 14

spx01
spx01

Reputation: 359

With this Method in your configuration you can set default properties.

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class)
              .properties("propertyKey=propertyValue");
}

Upvotes: 5

Kishore Vanapalli
Kishore Vanapalli

Reputation: 134

This is how you can override the application.properties programatically if you have to.

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(Restdemo1Application.class);
    app.setAdditionalProfiles("dev"); 
    // overrides "application.properties" with  "application-dev.properties"
    app.run(args);

}

Upvotes: 1

Peter Szanto
Peter Szanto

Reputation: 7722

Since spring boot 1.3 EnvironmentPostProcessor is available for this purpose. Create a subclass of it and register in META-INF/spring.factories A good example is here :

https://github.com/spring-cloud/spring-cloud-sleuth/blob/48f3f9783f277a795d0210399f0ea09b7f1a4e71/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java

Upvotes: 28

Roger Thomas
Roger Thomas

Reputation: 855

Just to provide another option to this thread for reference as when I started to look for an answer for my requirement this came high on the search list, but did not cover my use case.

I was looking to programmatically set spring boot property at start up, but without the need to work with the different XML/Config files that spring supports.

The easiest way is to set the properties at the time the SpringApplication is defined. The basic example below sets the tomcat port to 9999.

@SpringBootApplication
public class Demo40Application{

    public static void main(String[] args){
        SpringApplication application = new SpringApplication(Demo40Application.class);

        Properties properties = new Properties();
        properties.put("server.port", 9999);
        application.setDefaultProperties(properties);

        application.run(args);
    }
}

Upvotes: 46

Shilan
Shilan

Reputation: 833

Under META-INF folder create exactly this folders and file: spring>batch>override>data-source-context.xml and in your xml file make sure to override the paramters you want like this:

<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${loader.jdbc.driver}" />
    <property name="url" value="${loader.jdbc.url}" />
    <property name="username" value="${loader.jdbc.username}" />
    <property name="password" value="${loader.jdbc.password}" />
</bean>

<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

or use a jndi like this in the xml file to access your external configuration file like catalina.properties

<jee:jndi-lookup id="dataSource"
    jndi-name="java:comp/env/jdbc/loader-batch-dataSource" lookup-on-startup="true"
    resource-ref="true" cache="true" />

Upvotes: -1

Related Questions