I have some properties, where some initial part is same and remaining is different for all properties.
can have 1 dot (.) 2,3 or more.
I can't use #ConfigurationProperties to loca these with prefix ="com.some.props" because remaining properties are different
com:
some:
props:
prop1:
someProp: value
prop2:
anotherProp:
innerprop: value2
So I created a custom props class and used #Value but I have to write full property path for wach variable.
Is there a way I can apply a prefix for all #Value in a class
#Component (prefix="com.some.props") //Not a valid code, but want something of this sort.
class props {
#Values ("${prop1.someprop}")
String someprop;
#Values ("${prop2.anotherProp.innerProp}")
String somethingElse;
}
Related
I have a library that has a configuration class with #ConfigurationProperties(prefix ="b") and #ConditionalOnProperty(prefix = "b", name= "c"), in the project where I included this configuration class, the property "b" is nested under a property called "a" instead of being at the root level of the properties.yml, with this the property "b" name is "a.b" and the configuration class will get ignored, the property name needs to be kept "a.b" in the property file, is there a way to tell spring boot to ignore the "a" prefix for the property "b" or to rewrite the property name from "a.b" to "b" after application.yml gets loaded ?
You can (probably, depending on the exact context of the config class you're working with) use YAML anchor and alias to meet this need. YAML anchors are similar to anchors (<a> tags) in HTML; you mark some part of the yaml with an anchor, then refer to it with a reference somewhere else. The reference is an alias to the anchored value.
In your example, assuming the conditional config class is something like this:
#Configuration
#ConditionalOnProperty(prefix = "b", name = "foo")
#ConfigurationProperties(prefix ="b")
#Getter #Setter
public class SO_75435896 {
private String foo;
}
You can have a YAML config like this to populate it:
a.b: &b-alias
foo: bar
b: *b-alias
The anchor name (b-alias in my example) can be whatever you want, there's nothing special about that name I chose.
If you want to read more about YAML anchors, this is a decent explanation. It's part of Bitbucket documentation, but provides a pretty good general-purpose explanation with examples. There are, of course, plenty of other web pages about YAML anchors.
So your configuration class and properties files cannot be adapted. You can rename it at runtime in your application.yml :
BProperties
#ConfigurationProperties(prefix ="b")
public class BProperties {
private String c;
private String d;
}
And this properties file :
my-props-file.properties
a.b.c=value1
a.b.d=value2
You can rename the properties at runtime in your application.yml (define in classpath under src/main/ressources/) :
application.yml
b:
c: ${a.b.c}
d: ${a.b.d}
I want to inject some values from a YAML to the Spring context.
The structure of the YAML is similar so I did not want to duplicate code, but the Spring startup is failing because it is not being able to inject the value to the placeholder.
Please note my application.properties:
server.port=8084
activeProfile=dev
autoAgents.supplier.id=0
autoAgents.supplier.name=test
autoAgents.supplier.serviceType=REST
autoAgents.supplier.authType=1
autoAgents.supplier.adapter=test
autoAgents.supplier.username=test
autoAgents.supplier.secret=test
autoAgents.supplier.apiPassword=12345
autoAgents.client.id=1
autoAgents.client.name=test
autoAgents.client.serviceType=REST
autoAgents.client.authType=1
autoAgents.client.adapter=
autoAgents.client.username=test
autoAgents.client.secret=test
autoAgents.client.apiPassword=12345
Then I am injecting this values on the YAML, application.yml
activeProfile: ${activeProfile}
autoAgents:
supplier:
isSupplier: true
meta:
id: ${autoAgents.supplier.id}
name: ${autoAgents.supplier.name}
serviceType: ${autoAgents.supplier.serviceType}
authType: ${autoAgents.supplier.authType}
adapter: ${autoAgents.supplier.adapter}
credentials:
username: ${autoAgents.supplier.username}
secret: ${autoAgents.supplier.secret}
apiPassword: ${autoAgents.supplier.apiPassword}
client:
isSupplier: false
meta:
id: ${autoAgents.client.id}
name: ${autoAgents.client.name}
serviceType: ${autoAgents.client.serviceType}
authType: ${autoAgents.client.authType}
adapter: ${autoAgents.client.adapter}
credentials:
username: ${autoAgents.client.username}
secret: ${autoAgents.client.secret}
apiPassword: ${autoAgents.client.apiPassword}
And then I am importing this to a configuration property context:
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties
#Data
public class TwoConnectConfigurationProperties {
private String activeProfile;
#Value("${autoAgents.supplier}")
private AutoAgentDup supplier;
#Value("${autoAgents.client}")
private AutoAgentDup client;
}
But #Value("${autoAgents.supplier}") is not working.
Please advise.
As mentioned earlier it does not make sense to inject values to yaml, you can just create the "application.yaml" with the values directly. And just delete the ".properies" file.
You might want to take a look how to easily inject the properties with common suffix into a bean. Its nicely described here:
https://www.baeldung.com/configuration-properties-in-spring-boot
You will have a bean:
#Configuration
#ConfigurationProperties(prefix = "autoAgents.supplier")
public class AutoAgentSupplierProperties {
private long id;
private String name;
// ... rest of the properies properties
}
You might want the same for the "auto.agent" client.
If you want to avoid code duplication, you can have a bean with the common properties. Extend that class with 2 new classes. One for supplier and one for agent - and annotate those with
#ConfigurationProperties
annotation.
Why you need "nested properties"? If you only want to access them in application, just take values from .properties file and fill them as values to .yml file. E.g.: profile: dev, or
autoAgents:
client:
id: 1
Properties from .yml file can be accessed from code same way as from .properties file.
Your problem is in way how you access properties. When you use "#Configuration properties", you have to specific which one to use (e.g. #ConfigurationProperties("autoAgents.client").
I'm already familiar with the base behavior of Spring's #Value annotation to set a field to the value of a project property like so:
Project's Property File
foo.bar=value
Project's Configuration Class
#Configuration
public class MyConfig {
#Value("${foo.bar}")
private String myValue;
}
However I'm trying to make a SpringBoot starter project with conditional configuration and would like to standardize the property names to something useful such as "com.mycompany.propertygroup.propertyname", but to ease transition and encourage adoption, I want to support the old property names for a time as well, and was thus wondering if there was some way to allow multiple property names to set the same field? For instance:
My Theoretical Starter's Config
#Configuration
public class MyConfig {
#Value("${com.mycompany.propertygroup.propertyname}" || "${oldconvention.property}")
private String myValue;
}
Project A's Property
oldconvention.property=value
Project B's Property
com.mycompany.propertygroup.propertyname=value
I can't seem to find any documentation or SO answers on whether or not this is possible and how to achieve it if so... So I'm wondering if it is possible, or if it's not, is there an alternative to the #Value annotation that can be used to achieve the same effect?
Edit to Clarify:
I would not want to keep track of multiple values so I do not need instruction on how to get multiple values... the objective is to consolidate into a SINGLE VALUE that which may have multiple names. In practice, it would only ever have one name-value per project that uses the starter... only in rare cases when someone perhaps forgot to delete the old property would each property name be used (and it would probably have the same value anyway). In such cases, the NEW CONVENTION NAME-VALUE WOULD BE THE ONLY ONE USED.
Update
While the SpEL expression answers provided works when both properties are present, the application context cannot load when only one of the property names is present. Example:
Updated Configuration Class
#Value("#{'${com.mycompany.propertygroup.propertyname}' != null ? '${com.mycompany.propertygroup.propertyname}' : '${oldconvention.propertyname}'}"
private String myProperty;
Updated Property File
com.mycompany.propertygroup.propertyname=somevalue
Error
Caused by: java.lang.IllegalArgumentException:
Could not resolve placeholder 'oldconvention.propertyname' in value
"#{'${com.mycompany.propertygroup.propertyname}' != null ? '${com.mycompany.propertygroup.propertyname}' : '${oldconvention.propertyname}'}"
Requiring both property names to be present defeats the purpose, which is to allow an implementing project to configure this starter using EITHER the old convention OR the new convention...
Another Update...
I've been playing around with the SpEL expression a bit, and I've got the conditional check working when the property is present and when it's not, but I'm having trouble with property resolution after the fact. I think the problem is because property defaults and complex SpEL expressions don't play nice together.
#Value("#{${com.mycompany.propertygroup.propertyname:null} != null ? '${com.mycompany.propertygroup.propertyname}' : '${oldconvention.propertyname}'}")
private String myProperty;
When my SpEL is written like the above, I get a cannot resolve property placeholder exception, meaning that both properties have to be present in order for the SpEL expression to evaluate. So I got to thinking, I could use the default property syntax that I've seen for resolving optional properties: #Value("${myoptionalproperty:defaultValue}")
So below is my attempt to combine the default property resolution with the SpEL expression:
#Value("#{${com.mycompany.propertygroup.propertyname:null} != null ? '${com.mycompany.propertygroup.propertyname:}' : '${oldconvention.propertyname:}'}")
private String myProperty;
When using the default property notation, I keep getting this error:
org.springframework.expression.spel.SpelParseException:
EL1041E: After parsing a valid expression, there is still more data in the expression: 'colon(:)'
and when I Googled that error, the popular answer was that properties had to be wrapped in single quotes so that they evaluate to a string... but they're already wrapped (except the first one.. I had to unwrap that one since I wanted that to evaluate to a literal null for the null check). So I'm thinking that defaults can't be used with properties when they're wrapped in a spell expression. In truth, I've only ever seen the default property set when a #Value annotation is set with just a pure property holder, and all properties I've seen used in a SpEL expression never had a default set.
You can use the following #Value annotation:
#Configuration
public class MyConfig {
#Value("#{'${com.mycompany.propertygroup.propertyname:${oldconvention.propertyname:}}'}")
private String myValue;
}
This #Value annotation uses com.mycompany.propertygroup.propertyname if it is provided and defaults to oldconvention.property if com.mycompany.propertygroup.propertyname is not provided. If neither is provided, the property is set to null. You can set this default to another value by replacing null with another desired value.
For more information, see the following:
Spring Expression Language (SpEL)
Spring Expression Language Guide
As an alternative, you can capture both values and do a selection before returning the value:
#Configuration
public class MyConfig {
#Value("${com.mycompany.propertygroup.propertyname:}")
private String newValue;
#Value("${oldconvention.propertyname:}")
private String oldValue;
public String getValue() {
if (newValue != null && !newValue.isEmpty()) {
// New value is provided
System.out.println("New Value: " + newValue);
return newValue;
}
else {
// Default to the old value
return oldValue;
}
}
}
Using SPEL is the best way to solve this. This should work
#Value("#{'${com.mycompany.propertygroup.propertyname}' != null ? '${com.mycompany.propertygroup.propertyname}' : '${oldconvention.property}'}")
private String myValue;
No that's not possible I believe but yes you can define property as comma separated. For example
com.mycompany.propertygroup.propertyname=value1,value2,value3
And instead of receiving a String you can annotate #Value over String[] like this:
#Value("#{'${com.mycompany.propertygroup.propertyname}'.split(',')}")
private String[] propertyNames;
Another way you can also store key and value as a comma-separated string in the property file and use #Value annotation you can map into Map, For example, you want group name as key and value as group details so in the property file you can store string like this
group.details.property= {'group1':'group1.details','group2':'group2.details'}
And you can annotate #Value as
#Value("#{${group.details.property}}")
private Map<String, String> groupMap;
In my application, I have dozen of properties mapped to my bean attributes like that:
#Value("#{props['prop1']}")
private String prop1;
#Value("#{props['prop2']}")
private String prop2;
#Value("#{props['prop3']}")
private String prop3;
In my property file, I always need to define the values:
prop1=value1
prop2=value2
prop3=specific-value
It's heavy because most of the properties are often the same. I would prefer to define default values in my code and then override what I need in my property file. The code can looks like this:
#Value("#{props['prop1'] ?: value1}")
private String prop1;
#Value("#{props['prop2'] ?: value2}")
private String prop2;
#Value("#{props['prop3'] ?: value3}")
private String prop3;
And in my property file, I just change prop3:
prop3=specific-value
This is handy because we have a smaller property file that is easier to maintain. Spring boot can also help more by picking the right property file according to the enabled profile.
Now I have one concern: as a developer, how I can know all the configuration points inside my application? If we are forced to add all the properties to the property file, then we can have the list of supported properties easily. However with an approach where only a few properties are defined, we lose this benefit and it becomes tricky to know what property can be changed.
I'm curious to know how this issue has been already addressed before and how. I would expect to have a utility tool like a maven plugin that can scan my beans, detects inside them the attributes annotated with #Value and prints a table of all properties and their default value defined in the application.
Is there a better manner to implement properties file having key-value pairs as value using Spring/Spring Boot?
I want to create a property file where the key contains a couple of key-value pair as value.
I tried below implementation:-
Properties file:-
Fiat=model:pet,year:1996
Honda=model:dis,year:2000
And i have below class trying to read the properties file.
#Component
#PropertySources(#PropertySource("classpath:sample.properties"))
public class PropertiesExtractor {
#Autowired
private Environment env;
public String pullValue(String node) {
String value = env.getProperty(node);
System.out.println(value);//for Fiat, i get syso as **model:pet,year:1996**
}
}
I need to parse the values using java, to get the individual value. Is this the only way out to implement this.
Is there a better way to use nested property files in Java?
Create a Car object or something with a model and a year property. Then create something like this
#ConfigurationProperties("foo")
public class CarProperties {
private Map<String,Car> cars;
// Getters/Setters
}
Add add #EnableConfigurationProperties(CarProperties.class) in your main configuration class.
Then you can inject that config as follows:
foo.cars.Fiat.model=pet
foo.cars.Fiat.year=1996
foo.cars.Honda.model=dis
foo.cars.Honda.year=2000
There is more info in the doc.
You can use yaml files with spring as well:
http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-yaml
This way, you can work with
Fiat:
model: pet
year: 1996
Honda:
model: dis
year: 2000