How does Spring interpolate ${x} inside a string? - java

I have a java class in a Spring project that looks (edited) like:
#Component
public class X
{
private static final ApplicationContext CTX = new FileSystemXmlApplicationContext("file:${PATH}/ApplicationContext.xml");
...
I am looking for the reference explaining how the ${PATH} is interpolated in the string parameter. The PATH is passed as a system property ( java -DPATH=...) so I assume it takes it from there but I can't find an explanation describing the mechanism. Is it a Spring related feature similar to the syntax used in #Value?

configLocations (type String) passed to one of the FileSystemXmlApplicationContext constructors are processed by the resolvePath() method inherited from the AbstractRefreshableConfigApplicationContext class.
resolvePath() documentation says:
Resolve the given path, replacing placeholders with corresponding environment property values if necessary. Applied to config locations.
See Also:
PropertyResolver.resolveRequiredPlaceholders(String)
resolveRequiredPlaceholders() documentation says:
Resolve ${...} placeholders in the given text, replacing them with corresponding property values as resolved by getProperty(java.lang.String). Unresolvable placeholders with no default value are ignored and passed through unchanged.
The PropertyResolver declaring that getProperty() method is actually a StandardEnvironment.
StandardEnvironment documentation says:
Environment implementation suitable for use in 'standard' (i.e. non-web) applications.
In addition to the usual functions of a ConfigurableEnvironment such as property resolution and profile-related operations, this implementation configures two default property sources, to be searched in the following order:
system properties
system environment variables

Related

Class parameter name for default type in spring-kafka listener

I'm attempting to change the default type value for kafka listener with property "spring.json.value.default.type=" using my own annotation in spring-kafka. Currently, it's possible to overwrite it with following values:
properties="spring.json.value.default.type=com.package.class" which is canonical name of class.
I've made an annotation that sets the following value:
#MyAnnotation(topic = Topics.BUILD_CONFIG_CREATED, defaultType = ConstantsClass.TYPE_HEADER + "prz.student.finger.kafkaBSC.MyObjectDTO")
Is there any way to avoid hard typing the class name?
I would like to implement the option to use the following code(just giving the class that was imported):
#MyAnnotation(topic = Topics.BUILD_CONFIG_CREATED, defaultType = MyObjectDTO.class)
The closest to I've got is adding in my annotation:
#AliasFor(annotation = KafkaListener.class, attribute = "properties")
String defaultType() default headerType()+dtoType().getCanonicalName().toString();
String headerType() default "spring.json.value.default.type=";
Unfortunately, the constraints regarding the compilation time values for class in annotation blocks me from implementing it. Is there any way to inject the cannonical name without hard typing it, or any other way to implement this?
The properties property can contain SpEL (see its Javadocs).
Something like #{#someBean.type.name}; where someBean is a bean with a method public Class<?> getType().

Passing a variable to #Value annotation for reading specific property from properties file

In my Spring boot application, a specific value needs to be read from the properties file depending on a string value returned from another function. My properties file is as follows:
A=value_a
B=value_b
A function returns either A or B, and stores it in a String variable called stringValue . I am looking for doing something along the lines of the following:
#Value(stringValue)
String propertyValue
However, I get the following message on my IDE:
Attribute value must be constant
I have tried to convert stringValue to a static final variable, but to no avail.
I do know that I can pass a specific key to be read from the properties file, such as the following:
#Value("${A}")
String valueOfA
My question is whether I can pass a variable to the #Value annotation?
#Value annotations are resolved during startup, when Spring context is built. Since stringValue would not be available at this time, you can't use it for injection purposes.
An exception to this scenario would be if the bean with #Value annotation is prototype-scoped, in which case a new instance of it would be created any time it's requested. Still, stringValue would need to be available to the Spring context in order to be used at injection point.
Without seeing more code, it's not possible to give you any more detailed answer.
You can autowire Environment in your application and use it to read from the properties file as it is supposed to be at runtime.( This is assuming all files where your properties are have been added to environment).
I will be posting my answer with assumptions as you have not updated your question, with all information I requested.
So your code would be-
lets call your class where your making a call to a function called getValue to get value of stringValue- Example. Now lets assume you are making a call to the function getValue in a method in the class Example called doSomething().
class Example{
#Autowire
private Enviornment environment.
private String propertyValue
public void doSomething(){
String value=getValue()// this is where u know whether
its A or B.
propertyValue=environment.getProperty(value);
// do whatever u want know
}
Thanks for the help guys! I read the values from the properties file into a org.springframework.core.io.Resource object, and then used that to retrieve the specific value that I required. The following was how I structured my solution code:
#Value("classpath:config.properties")
private Resource propertiesfile;
I then declared a java.util.Properties object and read the values from the Resource object into it.
Properties properties = new Properties();
try{
properties.load(propertiesfile.getInputStream());
}catch(IOException e){
logger.error("Parsing error while reading properties file",e.toString());
}
Finally, based on the value in my stringValue variable, I read the corresponding value from the properties file
String propertyValue = properties.getProperty(stringValue);
if your variable is another environment variable you can try in this format.
#Value("${${stringvalue}}")
where stringvalue is the environment variable.

Spring 4 #Value where property default is a java system property

In Spring 4, using the #Value annotation, what is the right way to specify a system property as a default if a specified property does not exists?
Whereas this works for the no-default case:
#Value("${myapp.temp}")
private String tempDirectory;
This doesn't work when I need a default:
#Value("#{myapp.temp ?: systemProperties.java.io.tmpdir}")
private String tempDirectory;
Nor does this:
#Value("#{myapp.temp ?: systemProperties(java.io.tmpdir)}")
private String tempDirectory;
Both of these give me an exception at the time Spring is trying to create the bean:
org.springframework.beans.factory.BeanCreationException: Error creating bean
with name 'configurationService': Invocation of init method failed;
nested exception is java.lang.NullPointerException
Can this be done?
I tried the following and it worked for me:
#Value("${myapp.temp:#{systemProperties['java.io.tmpdir']}}")
private String tempDirectory;
The missing parts for you I believe was not using ?: and needing the #{}. According to this answer:
${...} is the property placeholder syntax. It can only be used to dereference properties.
#{...} is SpEL syntax, which is far more capable and complex. It can also handle property placeholders, and a lot more besides.
So basically what is happening is we are telling Spring to first interpret myapp.temp as property placeholder syntax by using the ${} syntax. We then use : instead of ?: (which is called the Elvis operator) since the elvis operator only applies to Spring Expression Language expressions, not property placeholder syntax. The third part of our statement is #{systemProperties['java.io.tmpdir']} which is telling Spring to interpret the next expression as a Spring Expression and allows us to get system properties.
Try systemProperties['java.io.tmpdir'].
It's a map, so if the key has a dot in the name, you should use [..]
For me it only works with different property-names (being property.name.a a key with a value in my application.properties and property.name.b a Environment-Variable) like:
#Value("${property.name.a:${property.name.b}}")
The same names didn´t work for me like expected (loading the default, when the first property isn´t present), e.g.:
#Value("${property.name.a:${property.name.a}}")

Java annotations: pass value of annotation attribute to another annotation

I have interface Resource and several classes implementing it, for example Audio, Video... Further, I have created custom annotation MyAnnotation with Class type param:
#MyAnnotation(type = Audio.class)
class Audio {
...
}
#MyAnnotation(type = Video.class)
class Video{
...
}
In some other place in code I have to use Interface Resource as a returned type:
public class Operations<T extends Resource> {
....
#OtherAnnotation(type = Audio.class (if audio), type = Video.class (if video) )
T getResource();
....
}
The question is how to appropriatelly annotate annotation #OtherAnnotation depending of what kind of Resource type will be returned ?
What you are asking is for dynamic values for annotation attributes.
However annotations can only be set at compile time which is the reason why their values can only be compile time constants. You may only read them at runtime.
There was a similar question in which someone tried to generate the annotation value , it's answer explains why there is no way to dynamically generate a value used in annotation in a bit more detail. In that question there was an attempt to use a final class variable generated with a static method.
There are annotation processors which offer a bit more flexibility by handling placeholders. However i don't think this fits your case, as you want the dynamic values at runtime.
This answer refers to spring's use of the expression language for the Value annotation in which the placeholder (#Value("#{systemProperties.dbName})") gets overrided with the data from one of the property sources defined ( example in spring boot )
In any case, you will have to rethink your architecture a bit.

Spring #Value from .properties file null in debug eclipse

The purpose of my code is to load some settings value from a *.properties file so that I later can use these values in some if-statements in my code. I want to load in some list-structure, but since that seems hard, an array will do. I have not really gotten that far, since I am stuck at the trivial matter of loading just a String from the properties file.
When I try to debug my code that is using some spring specific data. I get some interesting behaviour, pointing on the definition in the code right above the breakpoint gives me that the variable value is null.
#Value(value = "${ViewableReportFilter.allStates.verify}")
String verifyStringStates;
public ViewableReportFilter() {
viewStates = null;
log.debug("Read in properties for states: verify:" + verifyStringStates);
/*BREAKPOINT HERE*/
in my my.properties file:
ViewableReportFilter.allStates.verify=ONHOLD
And my config to use the properties-file:
<context:property-placeholder location="classpath:properties/my.properties" order="1" ignore-unresolvable="true" />
Spring can't set the fields of an object before that object is created. The first thing Spring does is use reflection to instantiate your class. It'll use either Class#newInstance() or use Constructor#newInstance() depending on the context. Only when the constructor has finished its work and returned can Spring, again using reflection, set the value of fields.
An alternative is to put a #Value annotated parameter in the constructor parameter list and set your field inside your constructor from the argument that's given to it by Spring.
public ViewableReportFilter(#Value String verify) {
this.verifyStringStates = verify;
...
Go through the Spring documentation for its IoC container. It explains all of this in much detail.
Updated the constructor, and added Autowire annotation. No changes in the properties file, no XML.
String arrayOfStrings;
#Autowired
public ViewableReportFilter(
#Value("${TMSViewableReportFilter.allStates.verify}") String[] verifyStringStates) {
arrayOfStrings = verifyStringStates;
public logViewableReportFilter() {
log.debug("Read in properties for states: verify:" + arrayOfString);
}
Try using this:
#Value(value = "${allStates.verify}")
And in your property my.properties:
allStates.verify=ONHOLD

Categories