Spring Boot: Make property non-configurable - java

In Spring Boot I have several options to externalize my configuration. However, how can I make such properties non-configurable, i.e. readonly.
Concretly, I want to set server.tomcat.max-threads to a fixed value and do not want somebody who is going to start the application to have the ability to change it. This could easily be done by passing it as a command line argument for instance.
It's probably not possible by default, maybe someone could suggest workarounds?

You have 2 options
Set System.setProperty("prop", "value") Property hard coded
Use properties that will override all other properties
Set system property hard coded
public static void main(String[] args) {
System.setProperty("server.tomcat.max-threads","200");
SpringApplication.run(DemoApplication.class, args);
}
Properties in secure.properties will override all others (see, Prevent overriding some property in application.properties - Spring Boot)
#Configuration
public class SecurePropertiesConfig {
#Autowired
private ConfigurableEnvironment env;
#Autowired
public void setConfigurableEnvironment(ConfigurableEnvironment env) {
try {
final Resource resource = new
ClassPathResource("secure.properties");
env.getPropertySources().addFirst(new
PropertiesPropertySource(resource.getFilename(),
PropertiesLoaderUtils.loadProperties(resource)));
} catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}

I ended up implementing an ApplicationContextInitializer (Docu), which in its initialize() method simply sets the static value programatically:
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String, Object> props = new HashMap<>();
props.put(MAX_THREADS, MAX_THREADS_VAL);
environment.getPropertySources().addFirst(new MapPropertySource("tomcatConfigProperties", props));
}
Another possible solution was found here: Prevent overriding some property in application.properties - Spring Boot

Related

Unable to resolve variable from properties file when tried to access as function parameter using #Value annotation

This may be silly question to ask but i'm unable to find any satisfactory solution to my problem. In java we don't have the concept of default variables so i am trying to give default value from properties file to my function parameters/arguments using #Value annotation, but i'm always getting null and i'm unable to figure why is this happening. Please help me to solve the issue or provide me some appropriate link/reference which may solve my issue.
MainApplication.java
#SpringBootApplication
public class Application
{
public static void main(String[] args)
{
ApplicationContext context = SpringApplication.run(NetappApplication.class, args);
Sample sample = context.getBean(Sample.class);
System.out.println(sample.check(null));
}
}
Sample.java
public interface Sample
{
public String check(String message);
}
SampleImpl.java
#Service
#PropertySource("classpath:app.properties")
public class SampleImpl implements Sample
{
#Value("${test}")
String message1;
#Override
public String check(#Value("${test}") String message)
{
return message;
}
}
app.properties
test=anand
But you are passing null to your method...
Perhaps what you want to do is to assign default value to test in case it's not defined in property file:
#Value("${test:default}");
Then, when properties are autowired by Spring if placeholder resolver doesn't get the value from props file, it will use what is after :.
The best use case for this (that I can think of) is when you create Spring configuration.
Let's say you have a configuration class: for DB access. Simply put:
#Configuration
public class DbConfig {
#Value("${url:localhost}")
String dbUrl;
// rest for driver, user, pass etc
public DataSource createDatasource() {
// here you use some DataSourceBuilder to configure connection
}
}
Now, when Spring application starts up, properties' values are resolved, and as I wrote above you can switch between value from property and a default value. But it is done once, when app starts and Spring creates your beans.
If you want to check incoming argument on runtime, simple null check will be enough.
#Value("${test}")
String message1;
#Override
public String check(String message) {
if (message == null) {
return message1;
}
}

Prevent overriding some property in application.properties - Spring Boot

I want to move some configuration in my property file, but don't want it to be overwritten at runtime, for security purpose. Is it possible to do it in spring boot?
Thanks,
Manish
Something like this should work, anything in secure.properties will not be able to be overridden as it will be added to the start of the env (from the properties file), regardless of what has been overridden.
#Configuration
public class SecurePropertiesConfig {
#Autowired
public void setConfigurableEnvironment(ConfigurableEnvironment env) {
try {
final Resource resource = new ClassPathResource("secure.properties");
env.getPropertySources().addFirst(new PropertiesPropertySource(resource.getFilename(), PropertiesLoaderUtils.loadProperties(resource)));
} catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
}
I tried the SecurePropertiesConfig method posted here and interestingly what is happening for me is the property value is different based on if I use a #Value annotation or just load the Environment. The #Value annotation is resolving before the setConfigurableEnvironment method resolves. So #Value will resolve to property file values set directly in the classpath while the Environment variable will have the secure (un-overridable) property value once the method is actually running. It works if I just use Environment, but unfortunately that makes it a bit brittle to a future code change accidentally using #Value instead, thinking it is "secure". I modified the SecurePropertiesConfig.java to be a ApplicationContextInitializer to force it to load the properties first.
SecureProperties.java
public class SecurePropertiesConfig implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
try {
final Resource resource = new ClassPathResource("secure.properties");
environment.getPropertySources().addFirst(new PropertiesPropertySource(resource.getFilename(), PropertiesLoaderUtils.loadProperties(resource)));
} catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
And then add the initializer in your application.properties:
context.initializer.classes=package.path.config.SecureProperties

Spring - How to get subset of properties file without enumerating all required properties

I am loading my properties file as following:
#Configuration
#PropertySource("classpath:app.properties")
class MyApp {
#Bean
public PropertySourcesPlaceholderConfigurer PropertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
In the properties file, I have several database-related properties:
database.dataSource.url=jdbc:postgresql://localhost:${db-port:5432}/mydb
database.dataSource.x=...
database.dataSource.y=...
database.dataSource.z=...
Note:
${db-port} should be replaced by either the value of property/environment variable db-port or 5432. In my case, I am defining the environment variable db-port when spawning the Tomcat container.
All database-related properties are grouped under database. root. This is intentional, see below.
I want to avoid that I have to enumerate/hardcode all possible database-related properties in my code. Luckily, the database layer in use (Hikari) has the nice feature that I can pass all properties via a java.util.Properties. So, I want retrieve all defined properties under database.* and simply forward it to Hikari.
For this, I wrote the following utility:
#Component
public class PropertyFetcher
{
#Autowired
private ConfigurableEnvironment environment;
public Properties get(final String key) {
final Properties p = new Properties();
for (final PropertySource<?> s : environment.getPropertySources()) {
if (s instanceof EnumerablePropertySource) {
for (final String k : ((EnumerablePropertySource) s).getPropertyNames()) {
if (k.startsWith(key) && k.length() > key.length()) {
p.put(k.substring(key.length()), s.getProperty(k));
}
}
}
}
return p;
}
}
Now, when calling get("database."), I have all database-related properties as defined in the properties file. Great! But, the value for property dataSource.url is now
jdbc:postgresql://localhost:${db-port:5432}/mydb
instead of
jdbc:postgresql://localhost:9876/mydb
So, for some reason, the ${db-port:5432} is not resolved (yet?) when going via this route (ConfigurableEnvironment).
How can this be fixed? Or is there a better way to get all properties under a certain root without having to enumerate/hardcode them into the code?
Please note that in the default scenario, the ${db-port:5432} in property database.dataSource.url=jdbc:postgresql://localhost:${db-port:5432}/mydb is correctly resolved. I tested this by defining the following member and logging it:
#Value("${database.dataSource.url}")
final String url; // holds jdbc:postgresql://localhost:9876/mydb
You should read the property values from real environment only. Then only you will get actual or effective value of a property.
This will require a little change in your code.
change this line:
p.put(k.substring(key.length()), s.getProperty(k));
to this:
p.put(k.substring(key.length()), environment.getProperty(k));

#Value at runtime

How do I access the #Value machinery dynamically at run-time?
I thought that Environment might be what I was looking for, but it
#Component
public class SpringConfiguration implements ConfigurationI {
#Autowired
private Provider<Environment> env;
#Override
public String get(String key) {
try {
return env.get().getRequiredProperty(key);
} catch (IllegalStateException e) {
return null;
}
}
}
Unfortunately, this does not access the values exposed by our PropertyPlaceholderConfigurer bean.
EDIT: To explain my use case: This is part of making a library with a lot of spring specific pieces (that a pile of older spring applications depend on) usable from newer Guice applications by switching Spring specific annotations for JSR 330 (javax.inject) ones. I was hoping to avoid rewriting all the PropertyPlaceholderConfigurer stuff across all our Spring applications, by providing a nice entrypoint like this. If there is another better way to do this (maybe with #Named?) then I am all ears.
EDIT2: This is a (cleaned up) example of what kind of PropertyPlaceholderConfigurer exists in the apps calling into this library.
#Bean
public PropertyPlaceholderConfigurer placeholderConfigurer() {
return new PropertyPlaceholderConfigurer() {
#Override
protected String resolvePlaceholder(String placeholder, Properties props) {
// Some code to parse and cleanup key here
String result = getPropertyFromLocalAppSpecificConfig(key);
if (result == null) {
result = super.resolvePlaceholder(placeholder, props);
}
// Some more random app specific logic for missing defaults
return result;
}
};
}
PropertyPlaceholder and friends do not put the properties in your Environment (mainly because of backward compatibility reasons). Instead they use Environment and its own internal Properties object gathered generally from property files from the classpath to resolve #Value properties. Thus the properties loaded from PropertyPlaceholder can not be fetched dynamically (ie no getProperty(String..)).
Some people create custom PropertyPlaceholder that store the properties publicly (through getter or whatever) but I think completely defeats Spring's new unified environment configuration handling.
What you really want is probably #PropertySource which still is pretty crappy since its not dynamic (since its an annotation you can't change where files get loaded from) but it will load properties into the Environment. I have been meaning to file issues with Spring Source about the confusion of this.
Anyway you can look at my solution here: Manually add a #PropertySource: Configuring Environment before context is refreshed
Basically you need to get hold of ConfigurableEnvironment and load your properties into it by creating PropertySources. The API for this is very powerful but not very intuitive. You can use ApplicationContextInitializers to get the Environment which has its own annoying issues (see link) or you can do what I do below.
public class ConfigResourcesEnvironment implements
ResourceLoaderAware, EnvironmentAware, BeanDefinitionRegistryPostProcessor, EnvironmentPropertiesMapSupplier {
private Environment environment;
private Map<String, String> environmentPropertiesMap;
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = ((ConfigurableEnvironment) this.environment);
List<PropertySource> propertySources;
try {
propertySources = loadPropertySources(); //Your custom method for propertysources
} catch (IOException e) {
throw new RuntimeException(e);
}
//Spring prefers primacy ordering so we reverse the order of the sources... You may not need to do this.
reverse(propertySources);
for (PropertySource rp : propertySources) {
env.getPropertySources().addLast(rp);
}
environmentPropertiesMap = ImmutableMap.copyOf(environmentPropertiesToMap(env));
}
else {
environmentPropertiesMap = ImmutableMap.of();
}
}
public static Map<String,String> environmentPropertiesToMap(ConfigurableEnvironment e) {
Map<String, String> properties = newLinkedHashMap();
for (String n : propertyNames(e.getPropertySources())) {
String v = e.getProperty(n);
if (v != null)
properties.put(n, v);
}
return properties;
}
public static Iterable<String> propertyNames(PropertySources propertySources) {
LinkedHashSet<String> propertyNames = new LinkedHashSet<String>();
for (PropertySource<?> p : propertySources) {
if (p instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> e = (EnumerablePropertySource<?>) p;
propertyNames.addAll(asList(e.getPropertyNames()));
}
}
return propertyNames;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//NOOP
}
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
public Map<String, String> getEnvironmentPropertiesMap() {
return environmentPropertiesMap;
}
}
Once you have ConfigurableEnvironment loaded you can use the EnvironmentAware interface for things that need the Environment or create your own interface.
Here is a custom interface you can use for things that need dynamic properties (the above class implements it):
public interface EnvironmentPropertiesMapSupplier {
public Map<String, String> getEnvironmentPropertiesMap();
}

Spring - Replacing the bean property values with new Property File values

I have a property file and using Spring property place holder, I set values to the Spring beans. Now, this property file may be modified during the run time. Is there a way to refresh the properties of the Spring beans with this newly modified property value? Especially, I have many singleton beans? How can I refresh them with the new values? Is there already a solution to this or should it be custom coded? If it doesn't already exist, can someone please give the best approach to achieve this? Thanks!
PS: My application is a batch application. I use Spring based Quartz configuration to schedule the batches.
I'll leave this in for reference, but the updated answer is below the divider:
Well the ConfigurableApplicationContext interface contains a refresh() method, which should be what you want, but the question is: how to access that method. Whichever way you do it, you'll start with a bean that has a dependency of type ConfigurableApplicationContext:
private ConfigurableApplicationContext context;
#Autowired
public void setContext(ConfigurableApplicationContext ctx){
this.context = ctx;
}
Now the two basic options I'd suggest would be to either
use the Task Execution Framework and let your bean watch the property resources regularly, refreshing the ApplicationContext when it finds changes or
expose the bean to JMX, allowing you to manually trigger the refresh.
Referring to comments: since it seems impossible to refresh the entire context, an alternative strategy would be to create a properties factory bean and inject that into all other beans.
public class PropertiesFactoryBean implements FactoryBean<Properties>{
public void setPropertiesResource(Resource propertiesResource){
this.propertiesResource = propertiesResource;
}
private Properties value=null;
long lastChange = -1L;
private Resource propertiesResource;
#Override
public Properties getObject() throws Exception{
synchronized(this){
long resourceModification = propertiesResource.lastModified();
if(resourceModification != lastChange){
Properties newProps = new Properties();
InputStream is = propertiesResource.getInputStream();
try{
newProps.load(is);
} catch(IOException e){
throw e;
} finally{
IOUtils.closeQuietly(is);
}
value=newProps;
lastChange= resourceModification;
}
}
// you might want to return a defensive copy here
return value;
}
#Override
public Class<?> getObjectType(){
return Properties.class;
}
#Override
public boolean isSingleton(){
return false;
}
}
You could inject this properties bean into all your other beans, however, you would have to be careful to always use prototype scope. This is particularly tricky inside singleton beans, a solution can be found here.
If you don't want to inject lookup methods all over the place, you could also inject a PropertyProvider bean like this:
public class PropertiesProvider implements ApplicationContextAware{
private String propertyBeanName;
private ApplicationContext applicationContext;
public void setPropertyBeanName(final String propertyBeanName){
this.propertyBeanName = propertyBeanName;
}
#Override
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException{
this.applicationContext = applicationContext;
}
public String getProperty(final String propertyName){
return ((Properties) applicationContext.getBean(propertyBeanName)).getProperty(propertyName);
}
}

Categories