Using #Value annotation with Spring and SPeL - spring

I am trying to find a way to do the following in my spring boot 1.5 application.
I have a variable who's value is dynamic meaning it comes in from an external system.
String name = "abc"; //gets set externally
I want to try and use the name's value to lookup my property file and see if there is a matching property defined. something like..
#Value("#{myClassName.name.concat('something')}")
String propertyValue;
Now my application.property file has the following property set
assume name has the value "abc"
property file contents:
abc.something:abcValue
Now, when i try to access the value of the variable propertyValue it gets set to the value abc.something and not abcValue.
I probably think I cannot use #Value with #{} to get to that, I was wondering if there was a way to to use #{} inside ${} so that I goes and fetches the property value after calculating the name of the property using #{}.
Let me know if you need more details please.

A bean life-cycle requires properties to be resolved at compile time. So, #Value requires constant parameter.
You can use Environment bean to access your properties programmatically.
import org.springframework.core.env.Environment;
#Service
public class Serivce {
#Autowired
private Environment environment;
public String getProperty(final String keyPart) {
String key = "build.your." + keyPart;
return environment.getProperty(key)
}
}
By the way you can use #('${spring.some.property}') in SpEL to access placeholder.
// This is valid access to property
#Value("#('${spring.some.property}')")
private String property;

Related

#ConfigurationProperties returns property placeholder instead of null

I have the following #ConfigurationProperties property holder:
#ConfigurationProperties(prefix = "custom.service")
public class CustomServicePropertyHolder {
private String name;
}
and my application.properties looks like this:
custom.service.name=${remote.service.name}
custom.service.....=...
custom.service.....=...
remove.service.name is an environment variable received in the runtime.
However, when remote.service.name was not provided, the value of the EtlConfigurationHolder.name is a string "${remote.service.name}".
How to make the property to return null instead of this placeholder string ?
I also encountered such a problem ... And it has been present for a long time
Unresolved Placeholder Validation for Spring Boot Configuration Properties
https://github.com/spring-projects/spring-boot/issues/4302
https://github.com/spring-projects/spring-boot/issues/1768
I found only one place where at least some solution is attached
https://davidagood.com/spring-boot-fail-on-missing-env-vars/

How do you make Spring fail fast when using placeholders with #ConfiguraitonProperties and an environment variable is not set?

When using placeholders to externalise configuration in an application.yaml file, and an associated properties class, how do you make sure Spring fails during startup when it can't resolve a placeholder, instead of just using the placeholder itself as the verbatim value?
For example, given this application.yaml file:
example.key: ${MY_ENV_VAR}
and this properties POJO:
#ConfigurationProperties(prefix="example")
public class AcmeProperties {
public String key;
// Getters, setters, constructors omitted...
}
if MY_ENV_VAR is not set on the system, how do you make Spring throw an exception at startup, instead of setting key to literally ${MY_ENV_VAR}?
Note, Spring doesn't return an empty String, which we could force by defining a default with ${MY_ENV_VAR:defaultValue}, or null (SpEL is not evaluated in #ConfigurationProperties, so this can not be defined as the default, and the behaviour of System.getenv("MY_ENV_VAR") returning null in the case of an undefined environment variable isn't mirrored), it literally just uses the placeholder as the value. We'd rather Spring stopped launching the app altogether instead, for all properties in AcmeProperties. Just using #Validated with Hibernate Validator on the classpath doesn't do it, neither does #ConfigurationProperties(prefix="example", ignoreInvalidFields=false) (which is the default anyway).
A manual check for the value containing ${...} could of course be added to all String properties in the POJO, but this is more error-prone, and would also not work if the actual environment variable was set to a string containing that character sequence. Is there a way to check if the placeholder can be resolved, instead of if the value after resolution is complete?
You can have a custom validator which could look like:
object UnresolvedPropertiesValidator : Validator {
override fun supports(clazz: Class<*>): Boolean {
return clazz == String::class.java
}
override fun validate(target: Any, errors: Errors) {
val stringVal = target as String
if (stringVal.startsWith(SystemPropertyUtils.PLACEHOLDER_PREFIX) && stringVal.endsWith(SystemPropertyUtils.PLACEHOLDER_SUFFIX)) {
errors.reject(
"prop.validation",
"Could not resolve placeholder $target"
)
}
}
}
And then create a bean with the name configurationPropertiesValidator:
class UnresolvedPropertiesValidatorAutoConfiguration {
#Bean
fun configurationPropertiesValidator(): UnresolvedPropertiesValidator {
return UnresolvedPropertiesValidator
}
}
As you can see from these https://github.com/spring-projects/spring-boot/blob/24a52aa66ddb92cd14acb2b41d9f55b957a44829/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationProperties.java#L47
https://github.com/spring-projects/spring-boot/blob/9630f853be3183f4872428e2e65b6ef4be7a9b7a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java#L105,
the validator with the name above will be applied after resolving the prop, and it simply ensures the final value doesn't start with ${ and end with }.
This works for us. But hopefully, the issue will be resolved soon, and we will remove this workaround.

Spring Custom Configuration not populating properties with underscore

I am trying to populate Custom class with properties. following is my Custom Properties class:
#Configuration
#ConfigurationProperties(prefix = "foo.bar")
public class CustomProperties{
private String PROPERTY_ONE;
private String propertyTwo;
//setters
//getters
}
and my properties in application.properties are:
foo.bar.PROPERTY_ONE=some text
foo.bar.PROPERTY_TWO=some other text
When I am trying to use value from CustomProperties this is what I gets:
customProperties.getPROPERTY_ONE() = null
customProperties.getPopertyTwo() = some other text
So I observed that if I have variable name with underscore(_) in it not populating the property value.
is there any way to get the value with variable having underscore?
Yes, it is 100% possible to get your configuration values.
It's all about the casing! Inside of CustomProperties simply name your first property propertyOne ... and refactor your getters/setters appropriately ... and you'll be good to go!
Spring's got the camel casing going on when translating the configuration fields to your Configuration classes/properties. So instead of matching the casing of your properties, follow the camel casing equivalent of the property name found in your configuration file.
Example: PROPERTY_ONE translates to propertyOne

Using #Value to inject property to constructor, SpelEvaluationException: EL1008E (4.1.6)

I read a post about authentication with facebook, http://www.jasha.eu/blogposts/2013/09/retrieve-facebook-profile-data-java-spring-social.html
he use autowired to inject 3 arguments to the constructor. which gives me SpelEvaluationException.
My project, I add a config.properties under /src/
facebook.clientId=237473571343
facebook.clientSecret=9iuwijig[sa[w90u2tjgjgj
application.host=http://localhost:8080
and the constructor
#Controller
public class FacebookSpringSocialAuthenticator {
public static final String STATE = "state";
private String applicationHost;
private FacebookConnectionFactory facebookConnectionFactory;
#Autowired
public FacebookSpringSocialAuthenticator(
#Value("#{properties['facebook.clientId']}")
String clientId,
#Value("#{properties['facebook.clientSecret']}")
String clientSecret,
#Value("#{properties['application.host']}")
String applicationHost) {
this.applicationHost = applicationHost;
facebookConnectionFactory =
new FacebookConnectionFactory(clientId, clientSecret);
}
}
after search and reading discussions and docs, I still don't figure out what to do.
the #{} is spring EL support, don't know how to use it, and don't know the difference to ${}
if I change to #Value("${facebook.clientId}"), there will be no exception, then I use debug mode to read the value of clientId, it does not show 237473571343, it shows ${facebook.clientId}, is that working correctly?
#Value("#{properties['facebook.clientId']}")
In your #Value annotation you are using a SpEL expression. In your case it is going to look for a Map or Properties object named properties and try to find a property with the key facebook.clientId.
To make it work you need to add the following
<util:properties id="properties" location="config.properties" />
Although this works I would strongly suggest to use a *PlaceHolderConfigurer instead of using SpEL.First add atag to your configuration, next change your#Value` to simply use properties.
<context:property-placeholder location="config.properties" />
Then your #Value can be like
#Value("${facebook.clientId}")
The added advantage of this is that you could also use system or environment properties to do some configuration (or override parts of your configuration).

Can I use a placeholder to dynamically add text to a string imported from a property file with Spring's #Value annotation

Does anyone know if I can use placeholders with Spring's #Value annotion?
For example:
#Value("${a.url.from.propertiesFile}")
private void setUrl(String myUrlFromProperties)
{
this.url = myUrlFromProperties;
}
where my properties file would have:
a.url.from.propertiesFile=/firstPartOfUrl{dynamicBitToAddTo}restOfUrl
Yes you can do that.
In this case if you have passed a null value in the parameter then the default value will be taken as defined with the annotation #Value.
But if you are passing a not null value in the parameter then it will not take the default value.
Hope this helps you.

Resources