Can anyone offer me some guidance on the best way for me to achieve this goal.
I'd like to extend the Spring Boot Externalized Configuration so that I have a single method which can be called from anywhere in my application. This method will retrieve a property value using a key. This method will first interrogate a database table and if it does not find the specified key it will then fall back on the PropertySource order described in 1.
So I'd have a service similar to:
@Service
public class ConfigurationService {
private final ConfigurationRepository configurationRepository;
@Autowired
public ConfigurationService(ConfigurationRepository configurationRepository) {
this.configurationRepository = configurationRepository;
}
public String getValue(String key) {
Configuration configuration = configurationRepository.findOne(key);
// Add something here to get the property from application.properties if the key does not exist in the db
return configuration == null ? null : configuration.getValue();
}
}
Which I can use as follows:
foo = configuration.getValue("my.property");
Is there a better way of going about this? Am I missing a Spring Boot feature that I could utilise?
EDIT: I'd like to be able to change the values of the properties while the application is running and have these new values picked up.
I have used the EnvironmentPostProcessor spring feature to do this.
You need to create a class like this:
public class ReadDbPropertiesPostProcessor implements EnvironmentPostProcessor {
/**
* Name of the custom property source added by this post processor class
*/
private static final String PROPERTY_SOURCE_NAME = "databaseProperties";
/**
* Adds Spring Environment custom logic. This custom logic fetch properties from database and setting highest precedence
*/
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Map<String, Object> propertySource = new HashMap<>();
try {
// Build manually datasource to ServiceConfig
DataSource ds = DataSourceBuilder
.create()
.username(USERNAME) // replace with your config
.password(PASSWORD) // replace with your config
.url(DATASOURCE-URL)// replace with your config
.driverClassName(DRIVER) // replace with your config
.build();
// Fetch all properties
PreparedStatement preparedStatement = ds.getConnection().prepareStatement("SELECT name, value FROM propertyConfig WHERE service = ?");
preparedStatement.setString(1, APP_NAME);
ResultSet rs = preparedStatement.executeQuery();
// Populate all properties into the property source
while (rs.next()) {
String propName = rs.getString("name");
propertySource.put(propName, rs.getString("value"));
}
// Create a custom property source with the highest precedence and add it to Spring Environment
environment.getPropertySources().addFirst(new MapPropertySource(PROPERTY_SOURCE_NAME, propertySource));
} catch (Exception e) {
throw new RuntimeException("Error fetching properties from db");
}
}
}
Since you need to run this class at a very early stage of spring, you need to create the file spring.factories
and register your environment post processor. This file needs to be located here:
src/main/resources/META-INF/spring.factories
In the content you need to set your class to the spring property:
# Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=com.your.package.ReadDbPropertiesPostProcessor
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With