How can I get property value inside an annotation. For example I have an annotation
#GetMyValue(value1="Val1",intVal=10)
Now I want "Val1" and 10 to be coming from a property file. I tried
#GetMyValue(value1="${test.value}",intVal="${test.int.value}")
Which doesn't work.
I understand I can use
#Value("${test.value}")
String value;
#Value("${test.int.value}")
int intValue;
I don't want that, it need to be inside an annotation. Any suggestions?
In the Spring #Value the replacement of the placeholder is not done inside the annotation but by the framework when inspecting the bean.
See
DefaultListableBeanFactory#doResolveDependency
DefaultListableBeanFactory#resolveEmbeddedValue
org.springframework.util.StringValueResolver
So, you have to "manually" get the annotation value1 and intVal (which should be a string in your annotation) and resolve them against your properties file.
This need involve more code works I think, but maybe you can have a workaround, for example, not hardcode the value for #GetMyValue annonation, just introduce two parameters in a config bean.
private String stringVal;
private int intVal;
then you can use this two params in you annonation by spEL.
Here is how:
GetMyValue(value1="#{'${test.value}'}",intVal="#{'${test.int.value}'}")
Related
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;
#Column(name="DateOfBirth")
private Date dateOfBirth;
I specifically need the above code to create a column named "DateOfBirth," instead Hibernate gives me a column named date_of_birth. How can I change this? Is there a web.xml property? I came across DefaultNamingStrategy and ImprovedNamingStrategy, but not sure how to specify one or the other.
Try putting this in
application.properties
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
FYI: The reason for the insertion of underscores is probably because you're using an ImprovedNamingStrategy. It's set on your Configuration object. See here for an example...
If you don't want the underscores you can just not set the naming strategy, or set it to the DefaultNamingStrategy you discovered earlier.
Here is a possible workaround: if you name it dateofbirth the column in the DB would be named like that, but the attribute name should be the same.
Hibernate takes the camel case format to create/read database columns.
I've had this problem before. I worked with a legacy columns where there was no space in the column names "employeename", "employeerole", "departmentlocation". I hate it because all my beans properties had to be without camel case.
Database columns separated by "_" will be used to properly camelCase as you have just seen.
add below property in the case of spring boot.
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
Put the #Column annotation on the getter:
#Column(name="DateOfBirth")
public Date getDateOfBirth() {
...
}
The workaround proposed was to use #Column(name="dateofbirth"), which worked for my purposes.
ImprovedNamingStrategy has method addUnderscores() which is called from tableName() and columnName()
you can implement your own naming strategy class and override these as per your choice
public class MyOwnNamingStrategy extends ImprovedNamingStrategy {
#Override
public String tableName(String tableName) {
//return addUnderscores(columnName); // skip this
return columnName; // if you want column name variable name same
//return changeAsYouWant(columnName); // as name sames
}
}
You can annotate either fields or getter methods, it doesn't make a difference. Can you post your full hibernate.cfg.xml or persistence.xml file?
I'm not 100% sure, but don't you need to annotate the get method and not the private variable?
I had a similar problem and adding the following two properties to my application.properties solved my issue: spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl .
I'm using the fasterxml #JsonFormat annotation to control how a date is serialized to json in a web service using Spring MVC:
#JsonFormat(pattern = Util.DATE_FORMAT, timezone = Util.LOCAL_TIME_ZONE)
public Date getTransDate() {
return transDate;
}
Another developer on our team suggested that I make the constants in my code properties so that they can be changed in different environments. In other places, I'm using a #Value annotation, like so:
#Value("${myapp.localTimeZone}")
private String TRANSACTIONS_TIME_ZONE;
But, I'm not sure how to change my Util.LOCAL_TIME_ZONE in my first snippet above to access a property. It looks like I can only do static (final?) values as arguments to an annotation. I can use #PostConstruct or something similar to set the value of a static field on my Util class, but I'm not sure that that is guaranteed to run before the #JsonFormat accesses it. Is there another way to do this?
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.
Just as we can read the property using #Value annotation, is there any way to change the value of the property through the code?
#Value("#{envProps['" + Keys.Env.updatedDate + "']}")
private String date;
value in environment.properties
updatedDate =2013-10-01
I want to change the value to 2013-10-16. How to do it?
Thanks
Sorry you cant do that
#Annotations and its values in java are constants.
the #Annotations are stored in class file (at RuntimeVisibleAnnotations) while compiling.
the values of them can be String, Class, or other value and can't be change at Runtime.
see jvm class file spec
Alternative solution
use a place holder like
#Value("${updatedDate.envProps}")
private String date;
and write your custom PropertyPlaceholderConfigurer to parse updatedDate.envProps to the value you want