Injecting property in Springboot using #Value annotation - java

I am using Spring and Java for an application, and I want to use the #Value annotation to inject the value of the property, my use case is that I want first to check if that property exists as system property (so it takes priority), and otherwise default to a configuration property (existing in the properties file)
In the commented code you can see what I am trying to achieve, is that possible to default to something else that a simple string? If it is not, how can I achieve this?
//#Value("#{configProperties['local.datasource.username']}") THIS IS THE ORIGINAL CODE
//#Value("#{systemProperties['some.key'] ?: 'my default system property value'}") THIS IS HOW SPRING PROPOSE TO SET A DEFAULT VALUE
//#Value("#{systemProperties['some.key'] ?: #{configProperties['local.datasource.username']}}") THIS IS WHAT I WANT TO ACHIEVE, HOWEVER NOT COMPILING,
private String username;

What you are looking for are simple Property Palceholders.
While the Spring Expression Language supports the #{} syntax for rather complex expressions (ternary, calls, expressions, math), injecting property values and defaults is in most cases better done using a simple property placeholder ${value:defaultValue} approach:
#Property("${some.key:local.datasource.username}")
private String username;
Where some.key is being resolved (independent of its origin), and if that is null, Spring defaults to the value of local.datasource.username.
Please keep in mind, that even if some.key is present, Spring will throw an exception when it can't resolve your default property.
See also:
Spring Expression Language (SpEL) with #Value: dollar vs. hash ($ vs. #) and
A Quick Guide to Spring #Value

Related

How to set #NotEmpty annotation messages in Spring?

In a Java (+ Spring Boot) project, there is a notation using javax.validation as shown below:
#NotEmpty(message = "validation.product.notEmpty")
private String product;
#NotEmpty(message = "validation.username.password")
private String password;
I have a look at the usage of them, but there are some points that I could not understand:
1. Is there a special usage e.g. conditional message displaying for validation.username.password? For example if username field is null, then display this message? Or is it completely the same manner as the product field?
2. I search the project, but could not find validation.product.notEmpty or validation.username.password. So, how do they work? I think there should be a definition for these messages, but as I did not find, is it come from default messages of javax.validation?
What is the difference between #EmailRegex and #Email? And is there
any need to also use #NotEmpty with these #EmailRegex or #Email
annotations?
#Email will not throw error for an empty String. So you need #NotEmpty to be sure that this String is not empty if you always require an email to be there.
#Email will consider valid everything that is in the form blabla#blabla.blabla. If you want to further constraint this you can use #EmailRegex so that you allow only blabla#blabla.eu by defining your own regular expression.
#EmailRegex does not seem to be included in hibernate annotations or spring annotations. So it is either a custom annotation imported from somewhere else or just a custom annotation of your application. Inspect the code to see how it actually behaves but from it's name I suppose it behaves as I have explained above.
I search the project, but could not find validation.product.notEmpty
or validation.username.password. So, how do they work? I think there
should be a definition for these messages, but as I did not find, is
it come from default messages of javax.validation?
It should be with {....} so like #NotEmpty(message = "{validation.username.password}") private String password;. In that case Spring will automatically read properties from the property files and apply the value for the property validation.username.password. If it does not exist then go to either application.properties or application.yaml and add that property.
Some more notes on this last one. I have seen some strange cases in backend-frontend applications which might be your case here.
#NotEmpty(message = "validation.username.password")
The actual message thrown here when the validation fails is validation.username.password. I have seen cases where the frontend then reads that message and binds a value to this one. I have seen this to be used when frontend supports multiple languages and binds another value for each language each time. This would explain why you don't have { } or such a property in your application.
#NotEmpty(message = "{validation.username.password}")
with an existing property validation.username.password= password can not be empty
will have as a result when the validation fails the message password can not be empty to be delivered.

Parse a yaml with placeholder in Java

I have a yaml file which consist of placeholders to be taken from environment variable. I want to parse it to a custom class. Currently I am using snakeyaml library to parse it and its populating the bean correctly, how can I resolve environment variables using snakeyaml or any other library in Java.
datasource:
url: {url_environment_variable}
foo: bar
username: {user_name_environment_variable}
password: {password_environment_variable}
#Getter
#Setter
public class DataSource {
private String url;
private String foo;
private String username;
private String password;
}
Parsing code below
Constructor c = new Constructor(MyDataSource.class);
Yaml yaml = new Yaml(c);
MyDataSource myData = yaml.loadAs(inputStream, MyDataSource.class);
The problem is I am yet to find a way to resolve placeholders. People were able to solve it using python and is available in question -
How to replace environment variable value in yaml file to be parsed using python script
How can I do the same in Java. I can add a new dependency if required.
PS - It's not a Spring Boot Project so standard Spring placeholder replacements can not be used.
The easiest way would be to do it in two passes. First deserialize into MyDataSource as you’re doing already. Then use reflection to iterate over all fields of the instance, and if the value starts with a curly brace and ends with one, extract the key, and get the value from System.getenv map. See this answer for code.
If you want to do it in one pass, then you need to use the snakeyaml event-driver parser. For every key, you resolve the value as described above, and then based on the key name, set the corresponding field in the MyDataSource class, either using reflection, or simple if-else. For an example of the event-driven parser, see this class; it’s not Java, it’s Kotlin, but it may be the only JVM language example you’ll find.

Spring Boot YAML: set null property value

I'm having trouble setting null as a property value.
This is how the value is defined in YAML file:
my-property: null
This is how I inject it in code:
#Value("${my-property}")
private String myProperty;
For some reason, Spring keeps injecting an empty string ("") instead of null. Am I missing something or is this an error in Spring?
You can't, but this is not because YAML. YAML supports null as per:
Empty field in yaml
This is because of a Spring processor class, which turns "null" values into empty strings, see here:
https://github.com/spring-projects/spring-framework/issues/19986
Try to use instead of $ use this #.
Or try this #Value("${<my-property>}")
I had a similar use case, and had to go through quite a bit of trial and error.
The following two-step solution worked for me
Don't define my-property in the YAML file. This way Spring cannot (annoyingly) inject a stringified value like "" or "~" or "null", and will be forced to look for a default value.
Use #{null} as the default value. This is an SpEL expression that evaluates to null.
In your example, it will be
#Value("${my-property:#{null}}")
private String myProperty;
Be careful not to mess up with the curly brackets.

Can multiple property names be specified in Spring's #Value annotation?

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;

Set annotation meta properties programmatically

Is it possible to set properties of annotation programmatically. So if I have :
#interface Author(
String name();
Date date ();
)
I want to be able to set the name property inside the code as if it is a regular class. Is this possible?
You can't use Date in an annotation
To access it at runtime, you need to add the #Retention(RetentionPolicy.RUNTIME) retention policy
The declaration for annotations uses {} for the annotation definition, not ()
No, you can't modify annotations during runtime using normal Java functionality
You may do this using a bytecode manipulator, like Javassist. But that is is not trivial.
Why do you want to do this during runtime? Possibly there are other better solutions to your problem.

Categories

Resources