How to access a file property from Spring Condition? - java

Let's say we have a simple Spring Condition which has to match against a file property from the properties file:
public class TestCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
context.getEnvironment().getProperty("my.property");
context.getBeanFactory().resolveEmbeddedValue("${my.property}");
context.getEnvironment().resolvePlaceholders("${my.property}");
// ... more code
}
}
Unfortunately, none of the mentioned above method calls return a real property which is defined in the property file. Instead, I get null when I call the getProperty method and "${my.property}" string for the other two (apparently, the property hasn't been resolved).

How about PropertiesLoaderUtils? Just put it into your method, instead of what you have there.
// path to your .properties file
Resource resource = new ClassPathResource("/my.properties");
Properties props = PropertiesLoaderUtils.loadProperties(resource);
....
String someValue = props.getProperty("someKey", "DEFAULT_VALUE");
Maybe try this if your stuff does not work.

you have various ways to access the properties in spring, the main and most simple are as follow
using injected values
#PropertySource("classpath:foo.properties")
public class foo {
#Value("${db.driver}")
private String dbDriver;
}
or you can use the Environment
#PropertySource("classpath:config.properties")
public class foo {
#Autowired
private Environment env;
...
dataSource.setUrl(env.getProperty("jdbc.url"));
}
more info can be found here

I had the similar problem.
Property was loaded by XML
<context:property-placeholder location="classpath:conf/coreConf.properties"/>
So I had to add empty java #Configuration to import properties.
#Configuration
#PropertySource("classpath:conf/coreConf.properties")
public class Configuration {
}
After that I was able to get property
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String property = context.getEnvironment().getProperty("main.settings.db.active");
return property != null && Boolean.parseBoolean(property.trim());
}

Related

how to get application property from ConditionContext

I am working on spring but not spring boot. I have a class marked as #Component (but not as #Configuration [it's not config class]). I would like to create it based on the serviceA.create property in app.properties. It the file it looks like this: serviceA.create=true. I would think, that in the code it narrows down to:
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
return Boolean.valueOf(context.getEnvironment().getProperty("serviceA.create"));
}
But based on different questions/answers (f.e. this) on the same topic, it sounds like adding #PropertySource(value="classpath:config.properties") is mandatory for this to work (to actually read the property value).
Question is, if I am able to get property from ConditionContext without defining #PropertySource ? because currently I am getting null without that annotation.
It should be done in the following way:
#Component
#Conditional(ServiceACondition.class)
#PropertySource(value="classpath:app.properties")
public class ServiceA {
(...)
}
and the cond. class
public class ServiceACondition implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
return Boolean.valueOf(context.getEnvironment().getProperty("serviceA.start"));
}
}

Spring Boot condition based on whether a collection in configuration properties is empty or not

I have the following classes:
#Component
#ConifgurationProperties("redis")
public class RedisProperties {
private List<String> hosts;
// getters, setters
}
#Component
public class StaticRedisHostsProvider implements RedisHostsProvider {
private final RedisProperties redisProperties;
public StaticRedisHostsProvider(RedisProperties redisProperties) {
this.redisProperties = redisProperties;
}
#Override
public List<String> getAll() {
return redisProperties.getHosts();
}
}
#Component
public DiscoveryBasedRedisHostsProvider { ... }
I want StaticRedisHostsProvider to be used if redis.hosts property is specified, DiscoveryBasedRedisHostsProvider otherwise.
I could annotate StaticRedisHostsProvider with #ConditionalOnProperty(prefix = "redis", name = "hosts"), but there is no similar #ConditionalOnMissingProperty annotation for using with DiscoveryBasedRedisHostsProvider.
I tried to use #ConditionalOnExpression("#redisProperties.hosts.empty"), but it doesn't work for some reason:
Description:
A component required a bean named 'redisProperties' that could not be found.
Action:
Consider defining a bean named 'redisProperties' in your configuration.
Is there some way to fix that (maybe with #Order or similar annotations)?
Here's my take on this issue with the use of custom conditions in Spring autoconfiguration.
#Conditional annotations are executed very early in during the application startup. Properties sources are already loaded but ConfgurationProperties beans are not yet created. However we can work around that issue by binding properties to Java POJO ourselves.
First I introduce a functional interface which will enable us to define any custom logic checking if properties are in fact present or not. In your case this method will take care of checking if the property List is empty or null.
public interface OptionalProperties {
boolean isPresent();
}
Now let's create an annotation which will be metannotated with Spring #Conditional and allow us to define custom parameters. prefix represents the property namespace and targetClass represents the configuration properties model class to which properties should be mapped.
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(OnConfigurationPropertiesCondition.class)
public #interface ConditionalOnConfigurationProperties {
String prefix();
Class<? extends OptionalProperties> targetClass();
}
And now the main part. The custom condition implementation.
public class OnConfigurationPropertiesCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MergedAnnotation<ConditionalOnConfigurationProperties> mergedAnnotation = metadata.getAnnotations().get(ConditionalOnConfigurationProperties.class);
String prefix = mergedAnnotation.getString("prefix");
Class<?> targetClass = mergedAnnotation.getClass("targetClass");
// type precondition
if (!OptionalProperties.class.isAssignableFrom(targetClass)) {
return ConditionOutcome.noMatch("Target type does not implement the OptionalProperties interface.");
}
// the crux of this solution, binding properties to Java POJO
Object bean = Binder.get(context.getEnvironment()).bind(prefix, targetClass).orElse(null);
// if properties are not present at all return no match
if (bean == null) {
return ConditionOutcome.noMatch("Binding properties to target type resulted in null value.");
}
OptionalProperties props = (OptionalProperties) bean;
// execute method from OptionalProperties interface
// to check if condition should be matched or not
// can include any custom logic using property values in a type safe manner
if (props.isPresent()) {
return ConditionOutcome.match();
} else {
return ConditionOutcome.noMatch("Properties are not present.");
}
}
}
Now you should create your own configuration properties class implementing OptionalProperties interface.
#ConfigurationProperties("redis")
#ConstructorBinding
public class RedisProperties implements OptionalProperties {
private final List<String> hosts;
#Override
public boolean isPresent() {
return hosts != null && !hosts.isEmpty();
}
}
And then in Spring #Configuration class.
#Configuration
class YourConfiguration {
#ConditionalOnConfigurationProperty(prefix = "redis", targetClass = RedisProperties.class)
StaticRedisHostsProvider staticRedisHostsProvider() {
...
}
#ConditionalOnMissingBean(StaticRedisHostsProvider.class)
DiscoveryBasedRedisHostsProvider discoveryRedisHostsProvider() {
...
}
}
There are two downsides to this solution:
Property prefix must be specified in two locations: on #ConfigurationProperties annotation and on #ConditionalOnConfigurationProperties annotation. This can partially be alleviated by defining a public static final String PREFIX = "namespace" in your configuration properties POJO.
Property binding process is executed separately for each use of our custom conditional annotation and then once again to create the configuration properties bean itself. It happens only during app startup so it shouldn't be an issue but it still is an inefficiency.

Spring: Read property from file why executing Condition::match

I want to implement a conditional Bean depending on a flag in my application.properties. Example:
// application.properties
service=foobar
The idea is to make different service implementations configurable, let assume I got a central configuration class for this service in Spring:
#Configuration
#Import({ServiceA.class, ServiceB.class, ...})
public class ServiceConfiguration {
...
}
And possible service implementations would look like
#Configuration
public class ServiceA implements Condition {
#Bean
#Conditional(ServiceA.class)
public Service service() {
Service a = ...
return a;
}
#Override
public boolean matches(
ConditionContext conditionContext,
AnnotatedTypeMetadata annotatedTypeMetadata) {
// getProperty will alsways return null for some reason
return conditionContext
.getEnvironment()
.getProperty("service")
.equals("ServiceA");
}
// This will be null anyways
#Value("${service}")
private String confService;
}
Since the class implementing Condition (here just the same class ServiceA) will be initialized via default constructor #Value-injections won't work. How ever, by what I understand getProperty()should return the correct value. What am I doing wrong? How can I access application properties at this point?
I found at "dirty workarround", I really don't like that solution, how ever, it solves the problem. As mentioned here a #PropertySource fixes the problem (I haven't tried this before posting here since it wasn't an accpeted answer).
#Configuration
#PropertySource(value="file:config/application.properties")
public class ServiceA implements Condition {
#Bean
#Conditional(ServiceA.class)
public Service service() {
Service a = ...
return a;
}
#Override
public boolean matches(
ConditionContext conditionContext,
AnnotatedTypeMetadata annotatedTypeMetadata) {
// Will work now
return conditionContext
.getEnvironment()
.getProperty("service")
.equals("ServiceA");
}
}
Although this works I don't like it for several reason:
With every implementation I have code redundancy (giving a path to a config file)
It's highly unmaintainable when having multiple configuration files
Example: Behavior like load default.properties <-then load and overwrite with -> customer.properties won't work anymore (altough this should be solvable using #PropertySources which would, on the other hand, increase code redundancy)

How to access a value defined in the application.properties file in Spring Boot

I want to access values provided in application.properties, e.g.:
logging.level.org.springframework.web: DEBUG
logging.level.org.hibernate: ERROR
logging.file=${HOME}/application.log
userBucket.path=${HOME}/bucket
I want to access userBucket.path in my main program in a Spring Boot application.
You can use the #Value annotation and access the property in whichever Spring bean you're using
#Value("${userBucket.path}")
private String userBucketPath;
The Externalized Configuration section of the Spring Boot docs, explains all the details that you might need.
Another way is injecting org.springframework.core.env.Environment to your bean.
#Autowired
private Environment env;
....
public void method() {
.....
String path = env.getProperty("userBucket.path");
.....
}
#ConfigurationProperties can be used to map values from .properties( .yml also supported) to a POJO.
Consider the following Example file.
.properties
cust.data.employee.name=Sachin
cust.data.employee.dept=Cricket
Employee.java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
#ConfigurationProperties(prefix = "cust.data.employee")
#Configuration("employeeProperties")
public class Employee {
private String name;
private String dept;
//Getters and Setters go here
}
Now the properties value can be accessed by autowiring employeeProperties as follows.
#Autowired
private Employee employeeProperties;
public void method() {
String employeeName = employeeProperties.getName();
String employeeDept = employeeProperties.getDept();
}
Currently, I know about the following three ways:
1. The #Value annotation
#Value("${<property.name>}")
private static final <datatype> PROPERTY_NAME;
In my experience there are some situations when you are not
able to get the value or it is set to null.
For instance, when you try to set it in a preConstruct() method or an init() method. This happens because the value injection happens after the class is fully constructed. This is why it is better to use the third option.
2. The #PropertySource annotation
#PropertySource("classpath:application.properties")
// 'env' is an Environment variable
env.getProperty(configKey);
PropertySouce sets values from the property source file in an Environment variable (in your class) when the class is loaded.
So you able to fetch easily afterword.
Accessible through System Environment variable.
3. The #ConfigurationProperties annotation.
This is mostly used in Spring projects to load configuration properties.
It initializes an entity based on property data.
#ConfigurationProperties identifies the property file to load.
#Configuration creates a bean based on configuration file variables.
#ConfigurationProperties(prefix = "user")
#Configuration("UserData")
class user {
// Property & their getter / setter
}
#Autowired
private UserData userData;
userData.getPropertyName();
You can do it this way as well....
#Component
#PropertySource("classpath:application.properties")
public class ConfigProperties {
#Autowired
private Environment env;
public String getConfigValue(String configKey){
return env.getProperty(configKey);
}
}
Then wherever you want to read from application.properties, just pass the key to getConfigValue method.
#Autowired
ConfigProperties configProp;
// Read server.port from app.prop
String portNumber = configProp.getConfigValue("server.port");
Follow these steps.
Create your configuration class like below. You can see:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Value;
#Configuration
public class YourConfiguration {
// Passing the key which you set in application.properties
#Value("${userBucket.path}")
private String userBucket;
// Getting the value from that key which
// you set in application.properties
#Bean
public String getUserBucketPath() {
return userBucket;
}
}
When you have a configuration class then inject in the variable from a configuration where you need.
#Component
public class YourService {
#Autowired
private String getUserBucketPath;
// Now you have a value in the getUserBucketPath
// variable automatically.
}
You can use the #Value to load variables from the application.properties if you will use this value in one place, but if you need a more centralized way to load these variables #ConfigurationProperties is a better approach.
Additionally, you can load variables and cast them automatically if you need different data types to perform your validations and business logic.
application.properties
custom-app.enable-mocks = false
#Value("${custom-app.enable-mocks}")
private boolean enableMocks;
You can use #Value("${property-name}") from the
application.properties if your class is annotated with
#Configuration or #Component.
There's one more way I tried out was making a Utility class to read properties in the following way -
protected PropertiesUtility () throws IOException {
properties = new Properties();
InputStream inputStream =
getClass().getClassLoader().getResourceAsStream("application.properties");
properties.load(inputStream);
}
You can make use of static method to get the value of the key passed as the parameter.
You should inject #Autowired private Environment env; from import org.springframework.core.env.Environment;
And then use it this way:
env.getProperty("yourPropertyNameInApplication.properties")
#Value Spring annotation is used for injecting values into fields in Spring-manged beans, and it can be applied to the field or constructor/method parameter level.
Examples
String value from the annotation to the field
#Value("string value identifire in property file")
private String stringValue;
We can also use the #Value annotation to inject a Map property.
First, we'll need to define the property in the {key: ‘value' } form in our properties file:
valuesMap={key1: '1', key2: '2', key3: '3'}
Not that the values in the Map must be in single quotes.
Now inject this value from the property file as a Map:
#Value("#{${valuesMap}}")
private Map<String, Integer> valuesMap;
To get the value of a specific key
#Value("#{${valuesMap}.key1}")
private Integer valuesMapKey1;
We can also use the #Value annotation to inject a List property.
#Value("#{'${listOfValues}'.split(',')}")
private List<String> valuesList;
To pick the values from property file, we can have a Config reader class, something like ApplicationConfigReader.java.
Then define all the variables against properties. Refer to the below example,
application.properties
myapp.nationality: INDIAN
myapp.gender: Male
Below is the corresponding reader class.
#Component
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "myapp")
class AppConfigReader{
private String nationality;
private String gender
// Getter and setter
}
Now we can auto-wire the reader class wherever we want to access property values.
E.g.,
#Service
class ServiceImpl{
#Autowired
private AppConfigReader appConfigReader;
//...
// Fetching values from the configuration reader
String nationality = appConfigReader.getNationality() ;
String gender = appConfigReader.getGender();
}
An application can read three types of values from the application.properties file.
application.properties
my.name = kelly
my.dbConnection = {connection_srting:'http://localhost:...', username:'benz', password:'pwd'}
Class file
#Value("${my.name}")
private String name;
#Value("#{${my.dbConnection}}")
private Map<String,String> dbValues;
If you don't have a property in application.properties then you can use the default value:
#Value("${your_name: default value}")
private String msg;
You can use the #Value annotation for reading values from an application.properties/yml file.
#Value("${application.name}")
private String applicationName;
There are 3 ways to read the application.properties,
using #Value, EnvironmentInterface and #ConfigurationProperties..
#Value(${userBucket.path})
private String value;
2nd way:
#Autowired
private Environment environment;
String s = environment.getProperty("userBucket.path");
3rd way:
#ConfigurationProperties("userbucket")
public class config {
private String path;
//getters setters
}
Can be read with getters and setters..
Reference - here
Injecting a property with the #Value annotation is straightforward:
#Value("${jdbc.url}")
private String jdbcUrl;
We can obtain the value of a property using the Environment API
#Autowired
private Environment env;
...
dataSource.setUrl(env.getProperty("jdbc.url"));
Another way to find a key/value in the configuration.
...
import org.springframework.core.env.ConfigurableEnvironment;
...
#SpringBootApplication
public class MyApplication {
#Autowired
private ConfigurableEnvironment myEnv;
...
#EventListener(ApplicationReadyEvent.class)
public void doSomethingAfterStartup()
throws Exception {
LOG.info("myEnv (userBucket.path): " + myEnv.getProperty("userBucket.path"));
}
}
You can access the application.properties file values by using:
#Value("${key_of_declared_value}")
The best ways to get property values are using:
1. Using Value annotation
#Value("${property.key}")
private String propertyKeyVariable;
2. Using the Environment bean
#Autowired
private Environment env;
public String getValue() {
return env.getProperty("property.key");
}
public void display() {
System.out.println("# Value : " + getValue);
}
Spring Boot allows us several methods to provide externalized configurations. You can try using file application.yml or YAML files instead of the property file and provide different property files setup according to different environments.
We can separate out the properties for each environment into separate YAML files under separate Spring profiles. Then during deployment you can use:
java -jar -Drun.profiles=SpringProfileName
to specify which Spring profile to use. Note that the YAML files should be named like
application-{environmentName}.yml
for them to be automatically taken up by Spring Boot.
Reference: 2. Externalized Configuration
To read from the application.yml or property file:
The easiest way to read a value from the property file or YAML is to use the Spring #value annotation. Spring automatically loads all values from the YAML file to the Spring environment, so we can directly use those values from the environment like:
#Component
public class MySampleBean {
#Value("${name}")
private String sampleName;
// ...
}
Or another method that Spring provides to read strongly-typed beans is as follows:
YML
ymca:
remote-address: 192.168.1.1
security:
username: admin
Corresponding POJO to read the YAML content:
#ConfigurationProperties("ymca")
public class YmcaProperties {
private InetAddress remoteAddress;
private final Security security = new Security();
public boolean isEnabled() { ... }
public void setEnabled(boolean enabled) { ... }
public InetAddress getRemoteAddress() { ... }
public void setRemoteAddress(InetAddress remoteAddress) { ... }
public Security getSecurity() { ... }
public static class Security {
private String username;
private String password;
public String getUsername() { ... }
public void setUsername(String username) { ... }
public String getPassword() { ... }
public void setPassword(String password) { ... }
}
}
The above method works well with YAML files.
Reference: 2. Externalized Configuration
There are two ways to access the value from the application.properties file:
Using the #Value annotation
#Value("${property-name}")
private data_type var_name;
Using an instance of the Environment class
#Autowired
private Environment environment;
// Access this way in the method where it's required
data_type var_name = environment.getProperty("property-name");
You can also inject an instance of the environment using constructor injection or creating a bean yourself.
Try class PropertiesLoaderUtils. This approach doesn’t use any annotation of Spring Boot. It is a traditional class way.
Example:
Resource resource = new ClassPathResource("/application.properties");
Properties props = PropertiesLoaderUtils.loadProperties(resource);
String url_server=props.getProperty("server_url");
Use the getProperty() method to pass the key and access the value in the properties file.
There are actually three ways to read the application.properties file,
Using Environment,
#Autowired
Environment environment
environment.getProperty({propertyName})
Or we can use #Value,
#Value("${property}")
but the problem with #Value is it might throw an exception if the value is not in the properties file.
The suggested way is using #ConfigurationProperties
#ConfigurationProperties("userBucket")
public class test{
private String path;
//getters and setters
}
For a detailed example - Reading application.properties.
The best thing is to use the #Value annotation. It will automatically assign a value to your object private Environment en.
This will reduce your code, and it will be easy to filter your files.
There are two ways,
you can directly use #Value in your class
#Value("#{'${application yml field name}'}")
public String ymlField;
Or
To make it clean you can clean #Configuration class where you can add all your #value
#Configuration
public class AppConfig {
#Value("#{'${application yml field name}'}")
public String ymlField;
}
application.yml or application.properties
config.value1: 10
config.value2: 20
config.str: This is a simle str
MyConfig class
#Configuration
#ConfigurationProperties(prefix = "config")
public class MyConfig {
int value1;
int value2;
String str;
public int getValue1() {
return value1;
}
// Add the rest of getters here...
// Values are already mapped in this class. You can access them via getters.
}
Any class that wants to access config values
#Import(MyConfig.class)
class MyClass {
private MyConfig myConfig;
#Autowired
public MyClass(MyConfig myConfig) {
this.myConfig = myConfig;
System.out.println( myConfig.getValue1() );
}
}
The easiest way would be to use the #Value annotation provided by Spring Boot. You need to define a variable at class level. For example:
#Value("${userBucket.path}")
private String userBucketPath
There is another way you can do this via the Environment Class. For example:
Autowire the environment variable to your class where you need to access this property:
#Autowired
private Environment environment
Use the environment variable to get the property value in the line you need it using:
environment.getProperty("userBucket.path");
Hope this answers your question!
To read application.properties or application.yml attributes follow the following steps:
Add your attributes in application.properties or application.yaml
Create config class and add your attributes
application.jwt.secretKey=value
application.jwt.tokenPrefix=value
application.jwt.tokenExpiresAfterDays=value ## 14
application:
jwt:
secret-key: value
token-prefix: value
token-expires-after-days: value ## 14
#Configuration("jwtProperties") // you can leave it empty
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "application.jwt") // prefix is required
public class JwtConfig {
private String secretKey;
private String tokenPrefix;
private int tokenExpiresAfterDays;
// getters and setters
}
NOTE: in .yaml file you have to use kabab-case
Now to use the config class just instantiate it, you can do this manualy or with dependency injection.
public class Target {
private final JwtConfig jwtConfig;
#Autowired
public Target(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
// jwtConfig.getSecretKey()
}
For me, none of the above did directly work for me.
I did the following:
In addition to Rodrigo Villalba Zayas' answer, I added implements InitializingBean to the class and implemented the method
#Override
public void afterPropertiesSet() {
String path = env.getProperty("userBucket.path");
}
So that will look like
import org.springframework.core.env.Environment;
public class xyz implements InitializingBean {
#Autowired
private Environment env;
private String path;
....
#Override
public void afterPropertiesSet() {
path = env.getProperty("userBucket.path");
}
public void method() {
System.out.println("Path: " + path);
}
}
I had this problem too. But there is a very simple solution. Just declare your variable in the constructor.
My example:
application.propperties:
#Session
session.timeout=15
SessionServiceImpl class:
private final int SESSION_TIMEOUT;
private final SessionRepository sessionRepository;
#Autowired
public SessionServiceImpl(#Value("${session.timeout}") int sessionTimeout,
SessionRepository sessionRepository) {
this.SESSION_TIMEOUT = sessionTimeout;
this.sessionRepository = sessionRepository;
}
You can use #ConfigurationProperties. It's simple and easy to access a value defined in application.properties:
# Datasource
app.datasource.first.jdbc-url=jdbc:mysql://x.x.x.x:3306/ovtools?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
app.datasource.first.username=
app.datasource.first.password=
app.datasource.first.driver-class-name=com.mysql.cj.jdbc.Driver
server.port=8686
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.database=mysql
#Slf4j
#Configuration
public class DataSourceConfig {
#Bean(name = "tracenvDb")
#Primary
#ConfigurationProperties(prefix = "app.datasource.first")
public DataSource mysqlDataSourceanomalie() {
return DataSourceBuilder.create().build();
}
#Bean(name = "JdbcTemplateenv")
public JdbcTemplate jdbcTemplateanomalie(#Qualifier("tracenvDb") DataSource datasourcetracenv) {
return new JdbcTemplate(datasourcetracenv);
}

How to override PropertySourcesPropertyResolver and add an additional property source to Externalized configuration

Is there a way to override Spring Boot's PropertySourcesPropertyResolver and extend Externalize configuration by adding additional property source?
What I'm trying to do is to add another property source in the current list, and be able to override this property with current mechanism.
And to extend PropertySourcesPropertyResolver, so when Spring is mapping properties for classes annotated with #ConfigurationProperties
and requesting for the key, it can check for key with two different prefixes.
For example, if I have properties from two different locations:
Properties on location 1:
data.user=userName
Properties on location 2:
service.data.user=serviceUserName
I want to be able to override the value of data.user with a value of service.data.user (if that property exists).
Just to share with others, maybe someone finds this useful.
The Entire solution is related with bean which extends PropertySourcesPlaceholderConfigurer.
Part is to add another property source to the existing list of property sources.
In my case I needed application.property which is stored in some jar file.
For that purpose, we have JarPropertiesPropertySource:
public class JarPropertiesPropertySource extends MapPropertySource implements Logger {
#SuppressWarnings({"unchecked", "rawtypes"})
public JarPropertiesPropertySource(String name, Properties source) {
super(name, (Map) source);
}
protected JarPropertiesPropertySource(String name, Map<String, Object> source) {
super(name, source);
}
}
And the main logic is in CustomPropertySourcesPlaceholderConfigurer bean:
public class CustomPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer implements Logger {
#Override
public PropertySources getAppliedPropertySources() throws IllegalStateException {
final MutablePropertySources mps = new MutablePropertySources();
// Get existing PropertySources
PropertySources ps = super.getAppliedPropertySources();
ps.forEach(propSource -> mps.addLast(new PropertySourceProxy(propSource)));
// Add JAR property source
mps.addLast(new JarPropertiesPropertySource("jar", ....))
return mps;
}
}
We need to support property key overloading. For example, if we have property 'serviceA|data.user', and if that property doesn't exist then try to find property 'data/user'.
For that purpose, we need to proxy property sources, and each property from super.getAppliedPropertySources() wrap inside proxy PropertySourceProxy. So each spring call for getProperty will go through proxy method and check variations:
.
public class PropertySourceProxy extends PropertySource {
public PropertySourceProxy(PropertySource propertySource) {
super(propertySource.getName(), propertySource.getSource());
Object o = propertySource.getSource();
if (o instanceof StandardEnvironment) {
ConfigurableEnvironment ce = (ConfigurableEnvironment) o;
MutablePropertySources cemps = ce.getPropertySources();
cemps.forEach(propSource -> {
cemps.replace(propSource.getName(), new PropertySourceProxy(propSource));
});
}
}
#Override
public Object getProperty(String name) {
Object value = null;
if (name != null) {
int index = name.indexOf("|");
if (index != -1) {
String newName = name.substring(index + 1);
value = super.getProperty(newName);
}
}
if (value == null)
value = super.getProperty(name);
return value;
}
}
from: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.html#DEFAULT_VALUE_SEPARATOR
Example XML property with default value:
<property name="url" value="jdbc:${dbname:defaultdb}"/>
However, there appear to be a few people who have been having problems with multiple property placeholders, so you might want to watch out for:
Is there a way to specify a default property value in Spring XML?
property not found with multiple context:property-placeholder
The other way you could do it is to provide a default value for your data.user property and let people override it as necessary.

Categories

Resources