Spring #ConfigurationProperties resolver ignores externalized properties - java

I use #ImportResource({"classpath:property-sources.xml"}) annotation to externalize path to some configuration files.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<util:properties id="externalProperties"
ignore-resource-not-found="true"
location="file:${dir.data}/foo.properties"
/>
<context:property-placeholder ignore-unresolvable="true" properties-ref="externalProperties"/>
</beans>
So, in application I can inject any property from foo.properties using #Value. Pretty common, yes.
However, when I'm trying to use #ConfigurationProperties to do do the same thing, Spring Boot completely ignores any property winthin that file. At first I've thought that something wrong with configuration, but if I put exactly the same property into application.yml it works. Compare:
foo.properties (ignored by #ConfigurationProperties resolver)
descriptions.foo.bar.baz = test
application.yml (successfully processed by #ConfigurationProperties resolver)
descriptions:
foo:
bar:
baz: test
There should be no difference. Also, there is nothing special in the bean itself:
#ConfigurationProperties(prefix = "descriptions", ignoreUnknownFields = false)
public class Descriptions {
private Map<String, String> foo = new HashMap<>();
public Map<String, String> getFoo() {
return foo;
}
public void setFoo(Map<String, String> foo) {
this.foo = foo;
}
}
Why #Value does always work and #ConfigurationProperties only works for application.yml ?

In your XML file you are loading a properties file into a Properties object, which then is passed to an additionally configured PropertySourcePlaceholderConfigurer. That is what all that namespace magic does.
The PropertySourcePlaceholderConfigurer will use the properties to resolve value expressions in #Value or xml. It will NOT add the loaded properties to the Environment. The Environment is what is being used by the ConfigurationPropertiesBindingPostProcessor in Spring Boot. This also runs very early in the process.
Instead what you should do is either put an #PropertySource on your #SpringBootApplication annotation class:
#PropertySource(value="file:${dir.data}/foo.properties", ignoreResourceNotFound=true)
Or specify which additional configuration files to load using the spring.config.additional-location property.
--spring.config.additional-location=file:${dir.data}/foo.properties
With the latter you don't need to change anything, just specify which files to load at startup.

public class someclassname{
#Value("${file.name}")
private String fileName;
FileInputStream fip = new FileInputStream(fileName);
Properties properties = new Properties();
properties.load(fip);
//read your file contents
}
The value of the file.name can be set in a properties file:
file.name=yourpath

PropertySource annotation can be used to achieve the same.
#Component
#PropertySource("classpath:foo.properties")
public class TestProperties {
#Value("${descriptions.foo.bar.baz}")
private String descriptions;
//getters and setters
}
ConfigurationProperties equivalent
import org.springframework.boot.context.properties.ConfigurationProperties;
#Configuration
#PropertySource("classpath:foo.properties")
#ConfigurationProperties
public class TestProperties {
private String descriptions;
//getters and setters
}

File foo.properties gives a Properties object which holds string=>string mapping "descriptions.foo.bar.baz" => "test". The string "descriptions.foo.bar.baz" in general can not be converted to structure like nested Map<String,Object>s.
On the other side, application.yml gives exactly the same structure as the application expects, nested Map<String,Object>s. So it works because of this, and because of type-erasure: at runtime jvm only knows that void setFoo(Map foo) expects a Map. So the application is going to throw a ClassCastException as soon as foo.get("bar") is used as String, because actual value would be a Map.

Related

How to use message.properties in Spring Boot in code instead of hardcoded string [duplicate]

How to access properties from messages.properties file in spring controller using annotation.
Please provide an example.
I used MessageSource :
#Autowired
private MessageSource messageSource;
...
private EventByDate createDefaultEventByDate(String date, Long barId, String locale) {
Event defaultEvent = new Event();
Locale localeValue = new Locale(locale);
defaultEvent.setTitle(messageSource.getMessage("default.event.title", null, "DefaultTitle", localeValue));
defaultEvent.setText(messageSource.getMessage("default.event.text", null, "DefaultText", localeValue));
...
}
First of all you need to define property place holder in your dispatcher-servlet.xml file like below.
<util:properties id="messageProperties" location="/messages.properties"/>
You need to change the path of your messages.properties file.
And then you can just access the property file value with help of #Value annotation.
private #Value("#{messageProperties['your.message.code']}") String message;
Hope this helps you. Cheers.
Following is dispatcher-servlet.xml
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<util:properties id="messageProperties" location="/messages.properties"/></beans></pre>
And as you mentioned for controller i am using same code with my properties. and its unable to resolve #Value annotation.

Spring XML cannot find my Spring Java Configuration Class

So I have been using the 3.2.0.RELEASE Spring XML configurations for most of my beans but now I am faced with a unique situation where the Getters and Setters can't be used (bad legacy code - can't get around it).
As such, I want to use Spring #Configuration class and the XML to workaround this problem.
However, I am getting "Class Not Found" exception when it tries to read my #Configuration Class.
Caused by: java.lang.ClassNotFoundException: v1.inventory.item.myJavaConfig
My XML file which is failing looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="v1.inventory.item.myJavaConfig"/>
</beans>
My #Configuration class looks like this:
package v1.inventory.item;
#Configuration
#ImportResource("classpath:v1/inventory/item/baseItemConfigs.xml")
public class myJavaConfig {
#Autowired
#Qualifier("parentItem")
Item baseItem;
#Bean
public Item realItem(){
Item modifiedBean = baseItem;
modifiedBean.setManufacturer("Fake Setter for Manufacturer");
modifiedBean.setDesigner("Fake Setter for Designer");
return modifiedBean;
}
}
I need this to be read by the ApplicationContext so I need to make sure these beans can be found. Is this a bug with Spring 3.2.0.RELEASE? Or my code?
For the record, I am pulling in the #Configuration last (parentItem is scanned first in XML).
I figured out the issue here.
It seems that maven/spring (not sure which) wasn't looking in my "resource" directory for the file. Only my "java" directory. When I moved my file into the "java" directory, Spring found the file just fine.
The tests which were passing in JUnit now pass using the Maven compiler (which was throwing the above error during the test phase)

Spring Could not Resolve placeholder

I'm fairly new to spring so excuse me if this is a dumb question. When I try to launch a program I get the following error: java.lang.IllegalArgumentException: Could not resolve placeholder 'appclient' in string value [${appclient}]. The error is thrown when the following code is executed:
package ca.virology.lib2.common.config.spring.properties;
import ca.virology.lib2.config.spring.PropertiesConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
#Configuration
#Import({PropertiesConfig.class})
#PropertySource("${appclient}")
public class AppClientProperties {
private static final Logger log = LoggerFactory.getLogger(AppClientProperties.class);
{
//this initializer block will execute when an instance of this class is created by Spring
log.info("Loading AppClientProperties");
}
#Value("${appclient.port:}")
private int appClientPort;
#Value("${appclient.host:}")
private String appClientHost;
public int getAppClientPort() {
return appClientPort;
}
public String getAppClientHost() {
return appClientHost;
}
}
A property file called appclient.properties exists in the resources folder with the information for host and port. I'm not sure where the "${appclient}" is defined, if it is at all. Maybe it is not even defined and that is causing the problem. Do I need to change the "${appclient}" to something like "{classpath:/appclient.properties}" or am I missing something else?
You are not reading the properties file correctly. The propertySource should pass the parameter as: file:appclient.properties or classpath:appclient.properties. Change the annotation to:
#PropertySource(value={"classpath:appclient.properties"})
However I don't know what your PropertiesConfig file contains, as you're importing that also. Ideally the #PropertySource annotation should have been kept there.
If you are using Spring 3.1 and above, you can use something like...
#Configuration
#PropertySource("classpath:foo.properties")
public class PropertiesWithJavaConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
You can also go by the xml configuration like...
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<context:property-placeholder location="classpath:foo.properties" />
</beans>
In earlier versions.
Hopefully it will be still helpful, the application.properties (or application.yml) file must be in both the paths:
src/main/resource/config
src/test/resource/config
containing the same property you are referring
If your config file is in a different path than classpath, you can add the configuration file path as a system property:
java -Dapp.config.path=path_to_config_file -jar your.jar
I know It is an old message , but i want to add my case.
If you use more than one profile(dev,test,prod...), check your execute profile.
In my case , I forgot to add the new property in my application.properties under test folder.Adding it resolved the issue
For properties that need to be managed outside of the WAR:
<context:property-placeholder location="file:///C:/application.yml"/>
For example if inside application.yml are name and id
Then you can create bean in runtime inside xml spring
<bean id="id1" class="my.class.Item">
<property name="name" value="${name}"/>
<property name="id" value="${id}"/>
</bean>
in my case, the war file generated didn't pick up the properties file so had to clean install again in IntelliJ editor.
If the issue occurs in IntelliJ, all you have to do is to install the plugin https://plugins.jetbrains.com/plugin/7861-envfile and follow the steps given.
My solution was to add a space between the $ and the {.
For example:
#Value("${appclient.port:}")
becomes
#Value("$ {appclient.port:}")

#Value annotation doesn't return a value

I have a class FichierCommunRetriever that use the #Value annotation of Spring. But I am struggling to make it work.
So in my application.properties I have :
application.donneeCommuneDossier=C\:\\test
application.destinationDonneeCommuneDossier=C\:\\dev\\repertoireDonneeCommune\\Cobol
My class FichierCommunRetriever is using those entries with the following code :
public class FichierCommunRetriever implements Runnable {
#Value("${application.donneeCommuneDossier}")
private String fichierCommunDossierPath;
#Value("${application.destinationDonneeCommuneDossier}")
private String destinationFichierCommunDossierPath;
}
We are loading application.properties with the following code in the class ApplicationConfig:
#ImportResource("classpath:/com/folder/folder/folder/folder/folder/applicationContext.xml")
In ApplicationConfig, I am defining a bean that use FichierCommunRetriever in a new thread like that :
Thread threadToExecuteTask = new Thread(new FichierCommunRetriever());
threadToExecuteTask.start();
I suppose that my problem is, since FichierCommunRetriever is running in a separate thread, the class can't reach the applicationContext and is not able to give a value.
I'm wondering if the annotation would work or I must change the way I'm getting those values?
In your applicationConfig you should define your bean this way:
#Configuration
public class AppConfig {
#Bean
public FichierCommunRetriever fichierCommunRetriever() {
return new FichierCommunRetriever();
}
}
Then, after Spring loads, you can access you bean via the application context
FichierCommunRetriever f = applicationContext.getBean(FichierCommunRetriever.class);
Thread threadToExecuteTask = new Thread(f);
threadToExecuteTask.start();
Now you are sure that your bean lives inside Spring context and that it is initialized.
In addition, in your Spring XML, you have to load the properties (this example uses the context namespace):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
...
<context:property-placeholder location="classpath:application.properties" />
...
</beans>
You create an instance of FichierCommunRetriever using new, instead of asking Spring to return a bean instance. So Spring isn't controlling the creation and injection of this instance.
You should have the following method in your config class, and call it to get the bean instance:
#Bean
public FichierCommunRetriever fichierCommunRetriever() {
return new FichierCommunRetriever();
}
...
Thread threadToExecuteTask = new Thread(fichierCommunRetriever());

Loading an application context by annotating a class

I am new at Spring and am wondering if one can load an application just by annotating the class whose variables must be injected (instead of using ApplicationContext ctx = new ApplicationContext("myAppContext")).
Let me give the following example:
I have this class TestSpring.java in which a string should be autowired
package mytest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
//Is it possible to put an annotation here that loads the application context "TestSpringContext.xm"??
public class TestSpring {
#Autowired
#Qualifier("myStringBean")
private String myString;
/**
* Should show the value of the injected string
*/
public void showString() {
System.out.println(myString);
}
}
The spring bean configuration file (TestSpringContext.xml) looks like this
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"
>
<context:annotation-config />
<bean id="myStringBean" class="java.lang.String">
<constructor-arg value="I am an injected String."/>
</bean>
</beans>
Now I would like to display the value of the autowired string myString (declared in TestSpring.java) using following code in RunTestSpring.java:
package mytest;
public class RunTestSpring {
public static void main(String[] args) {
TestSpring testInstance = new TestSpring();
testInstance.showString();
}
}
Now my question, is it possible to run "RunTestSpring.java" successfully while loading the application context by just annotating RunTestSpring.java. If yes, with which annotation?
#Configurable is probably what you are looking for, it will ensure that objects which are not instantiated by Spring can have their dependencies autowired by Spring. However the catch is that it requires AspectJ compile time/load time weaving for it to work(not Spring AOP).
Here is one reference:
http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/aop.html#aop-atconfigurable
I would suggest writing a JUnit class that would use spring injection for environment initialization. Something like this -
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations="/spring/spring-wireup.xml", inheritLocations = true)
public class MyTestCase extends TestCase {
// your test methods ...
}

Categories