What configuration enables the evaluation of #Value annotations? - spring

I'm tying to do a very minimal programmatic/annotation based configuration of Spring, to do some command line stuff and I want to be able to inject value of some bean values from System properties.
I'm using the #Value like this:
#Value("${MigrateDb.task:default}")
private String task;
It's sort of working, but it's not evaluating the value definition, I'm just getting "${MigrateDb.task:default}" in the actual field, instead of Spring evaluating it and giving me the value of the Migrate.db.task system property (or default).
What do I need to add to my Configuration class to enable this behaviour?

try using it this way:
#Value("${MigrateDb.task:default}")
private String task;
XML Config:
<context:property-placeholder
location="your.filelocation.properties" />`
Java Config :
#Bean
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer();
propertyPlaceholderConfigurer.setLocation(new ClassPathResource("file.properties"));
return propertyPlaceholderConfigurer;
}

From ShadowRay's answer, the minimum code to enable the requested behaviour is:
#Bean
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer(){
return new PropertyPlaceholderConfigurer();
}
Method should be static as per: https://stackoverflow.com/a/14943106/924597

Related

how to load property file in to spring boot project with annotations?

I have written queries in property file. I want to read the property file in to one class with annotations in spring boot. How can i read it? And is there any better approach for writing queries in spring boot project?
If you add your properties in application.properties file, you can read them inside the spring boot classes like:
#Service
public class TwitterService {
private final String consumerKey;
private final String consumerKeySecret;
#Autowired
public TwitterService(#Value("${spring.social.twitter.appId}") String consumerKey, #Value("${spring.social.twitter.appSecret}") String consumerKeySecret) {
this.consumerKey = consumerKey;
this.consumerKeySecret = consumerKeySecret;
} ...
You can annotate fields in your components by #Value("${property.name}")
Else, you can use Properties Object in java.util package.
For example, i have a mode property, which values are dev or prod, i can use it in my beans as follow :
#Value("${mode:dev}")
private String mode;
The other approach is by using :
Properties pro = new Properties();
pro.load(this.getClass().getClassLoader().getResourceAsStream());
You can use #PropertySource to read the properties from a file and then pass them to a bean. If you have a file called "queries.properties" that has a property like:
query1: select 1 from foo
Then your config might look like:
#PropertySource("classpath:queries.properties")
#Configuration
public class MyConfig {
#Bean
public DbBean dbBean(#Value("${queries.query1}") String query) {
return new DbBean(query);
}
}

Prefix properties of multiple PropertyPlaceholderConfigurers

I want to use Spring's PropertyPlaceholderConfigurer to read two property files. I can load each of them by using one of the following tags:
<context:property-placeholder location="class path:com/myapp/internal.properties"/>
<context:property-placeholder location="file://${settings.location}/external.properties"/>
I am not allowed to change the keys in those two files. Both files may contain entries with the same key.
I need to inject the value of a specific file.
//Pseudocode of injecting a property of a specific file
#Value("${internal.properties:my.key}")
String internalValue;
#Value("${external.properties:my.key}")
String externalValue;
So how to specify the file, and not only the key?
you will have to translate it to xml if needed:
public class InternalPropertyPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer{
public UploaderPropertyPlaceholderConfigurer() {
setLocations(new ClassPathResource[]{
new ClassPathResource("com/myapp/internal.properties"),
});
setPlaceholderPrefix("$internal{");
setPlaceholderSuffix("}");
}
and register it in spring ( or use #Component in above class ):
#Bean
public InternalPropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new InternalPropertyPlaceholderConfigurer();
}
this way you should be able to inject properties with this rather ugly syntax:
#Value("$internal{your.key}")
private String value;
If it works, then just add 2nd bean for external :)

How to access a Spring Boot Application's name programmatically?

I've defined an application name using the bootstrap.yml file in my spring boot application.
spring:
application:
name: abc
How can i get this application name during runtime/programmatically ?
You should be able to use the #Value annotation to access any property you set in a properties/YAML file:
#Value("${spring.application.name}")
private String appName;
#Autowired
private ApplicationContext applicationContext;
...
this.applicationContext.getId();
Please, find this:
# IDENTITY (ContextIdApplicationContextInitializer)
spring.application.name=
spring.application.index=
In Spring Boot Reference Manual.
And follow with source code for that ContextIdApplicationContextInitializer class:
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.setId(getApplicationId(applicationContext.getEnvironment()));
}
Where the default behavior is with this:
/**
* Placeholder pattern to resolve for application name
*/
private static final String NAME_PATTERN = "${vcap.application.name:${spring.application.name:${spring.config.name:application}}}";
Since the #Value annotation is discouraged in Spring Boot when referencing configuration properties, and because applicationContext.getId(); doesn't always return the value of spring.application.name another way is to get the value from the Environment directly
private final Environment environment;
...
public MyBean(final Environment environment) {
this.environment = environment;
}
...
private getApplicationName() {
return this.environment.get("spring.application.name");
}
Another possible way would be to create your own ConfigurationProperties class to get access to the value.
I'm not saying these are the best ways, and I hope/wish that there is a better way, but it is a way.
Note! If your using a SpringBootTest, you need to suplly the properties/yml.
Otherwise, the environment/appcontext does not load the config files.
The, your app name is not set.
Like so:
#PropertySource("classpath:application.properties")
#RunWith(SpringRunner.class)
#SpringBootTest
....
This post is aged but I hate unanswered questions.
So use the following snippet:
#Value("${spring.application.name [: defaultValue]}")
private String appName;
What is between [] is optional.
So I found a really ugly way to do this, but it works so I'm not searching further. Maybe this will help someone.
The basic premise is that spring Environment stores the value inside a propertySource.. It appears that bootstrap config is stored in the ResourcePropertySource and so you can get it from that. For me it is currently throwing an exception, but then I can get the value out of the exception, so I haven't looked any further:
try {
this.environment.getProperty("name", ResourcePropertySource.class);
} catch (ConversionFailedException e) {
String res = (String)e.getValue();
}
And then you can just do this for every property you are interested in.
Like I said ugly, but it works.

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).

creating custom PropertySourcesPlaceholderConfigurer using property loaded dynamically

I am facing some challenge creating PropertySourcesPlaceholderConfigurer based on some value that is available in another property file.
I have a property file, custom-{environment}.property, which contains a value, that is needed to set location of PropertySourcesPlaceholderConfigurer.
My CustomConfiguration looks something like:
#Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setLocation(customLocation);
//Custom url location based on a String available in the properties file. This is the problem area
return propertySourcesPlaceholderConfigurer;
}
I want to populate this customLocation from the properties file. Tried autowiring Environment, but it's failing as environment is null when placeholderConfigurer() is getting called. Tried using #PropertySource("custom-${environment}.property") and then #Value("**customLocation**"), but that's also not working.
Please let me know how this can be done. Thanks in advance.
I would suggest adding an ApplicationContextInitializer to load your property files instead of a plain #PropertySource. First load your custom-{environment}.properties next your configurable properties file.
public class PropertySourceInitializer implements ApplicationContextInitializer {
private static final String DEFAULT_CONFIG_FILE = "classpath:custom-${environment}.properties";
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
final ConfigurableEnvironment env = applicationContext.getEnvironment();
final MutablePropertySources mps = env.getPropertySources();
//
Resource resource = applicationContext.getResource(env.resolvePlaceholders(DEFAULT_CONFIG_FILE));
mps.addLast(new ResourcePropertySource(resource.getDescription(), resource));
String additional = env.getProperty("name.of.property");
if (StringUtils.hasText(additional) {
Resource additionalResource = applicationContext.getResource(env.resolvePlaceholders(additional));
if (additionalResource.isReadable() ) {
mps.addLast(new ResourcePropertySource(resource.getDescription(), resource));
}
}
}
}
Trying to get it to work with a #PropertySource will be much harder as the phases in which the PropertySourcesPlaceHolderConfigurer is created is different then the one in which the #PropertySource annotations are scanned. Staged loading of #PropertySource (which is basically what you want) is quite difficult. Spring Boot also has its own loading mechanism (which actually is also a ApplicationContextInitializer.
Can you try setting your location as a system property?
#Value("#{ systemProperties['myapp.location'] }")
private String location;
You need to set "myapp.location" as system property.

Resources