Onki
Onki

Reputation: 2095

spring boot : How to set spring properties dynamically

There are so many properties which can be defined in application.properties of spring boot application.

But I want to pass properties to configure ssl to spring from inside the code.

server.ssl.enabled=true
# The format used for the keystore 
server.ssl.key-store-type=PKCS12
# The path to the keystore containing the certificate
server.ssl.key-store=keys/keystore.jks
# The password used to generate the certificate
server.ssl.key-store-password=changeit
# The alias mapped to the certificate
server.ssl.key-alias=tomcat

So as these will be spring defined properties to be used from application.properties. I just want to set them from the code based on some logic.

For non spring application, I still have some idea that they can be passed as application, session or context properties but I am not aware on how this works in spring.

Any help would be appreciated.

Upvotes: 9

Views: 37724

Answers (4)

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

I was using Spring Boot with Spring Kafka and I only wanted a change for local profile.

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.

It's important to mention that BeanPostProcessor runs for EVERY bean, so make sure you filter what you want.

Upvotes: 2

Tien Do Nam
Tien Do Nam

Reputation: 4460

In Spring Boot, you can read and edit propeties using System.getProperty(String key) and System.setProperty(String key, String value)

public static void main(String[] args) {

    // set properties
    System.setProperty("server.ssl.enabled", "true");
    System.setProperty("server.ssl.key-store-type", "PKCS12");
    System.setProperty("server.ssl.key-store", "keys/keystore.jks");
    System.setProperty("server.ssl.key-store-password", "changeit");
    System.setProperty("server.ssl.key-alias", "tomcat");

    // run
    SpringApplication.run(DemoApplication.class, args);
}

Note:

This will overwrite the static properties (not all but the overwritten ones).

Upvotes: 12

Ajit Soman
Ajit Soman

Reputation: 4084

Since you know the properties to enable SSL in the spring boot app. You can pass these properties programmatically in your spring boot application like this:

@SpringBootApplication
public class SpringBootTestApplication {
    public static void main(String[] args) {

//      SpringApplication.run(SpringBootTestApplication.class, args);

        Properties props = new Properties();
        props.put("server.ssl.key-store", "/home/ajit-soman/Desktop/test/keystore.p12");
        props.put("server.ssl.key-store-password", "123456");
        props.put("server.ssl.key-store-type", "PKCS12");
        props.put("server.ssl.key-alias", "tomcat");

        new SpringApplicationBuilder(SpringBootTestApplication.class)
            .properties(props).run(args);
    }
}

As you can see I have commented out this: SpringApplication.run(SpringBootTestApplication.class, args); and used SpringApplicationBuilder class to add properties to the app.

Now, these properties are defined in the program, You can apply condition and change property values based on your requirements.

Upvotes: 14

user10871691
user10871691

Reputation:

You need to define and register an ApplicationListener like this:

public class DynamicPropertiesListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
  public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    ConfigurableEnvironment environment = event.getEnvironment();
    // modify the properties here, see the ConfigurableEnvironment javadoc
  }
}

Now register the listener. If you run the application with the main method:

SpringApplication app = new SpringApplication(primarySources);
app.addListeners(new DynamicPropertiesListener());
app.run(args);

or, even better, create src\main\resources\META-INF\spring.factories file with this content:

org.springframework.context.ApplicationListener=x.y.DynamicPropertiesListener

Where x.y is the package of your listener.

Upvotes: 8

Related Questions