Spring @Value annotation in @Controller class not evaluating to value inside properties file
SpringPropertiesSpring Problem Overview
I am new to Spring and trying to inject a string with a value using the @Value("${loginpage.message}")
annotation inside of a controller annotated with the @Controller
annotation and the value of my string is being evaluated as the string "${loginpage.message}"
and not what is inside my properties file.
Below is my controller with the string 'message' that I want to inject.
@Controller
public class LoginController extends BaseController {
@Value("${loginpage.message}")
private String message;
@RequestMapping("/")
public String goToLoginPage(Model model) {
model.addAttribute("message", message);
return "/login";
}
}
My application context looks like this:
<context:property-placeholder location="classpath:properties/application.properties" />
<context:annotation-config />
<context:component-scan base-package="com.me.application" />
My properties file has the line:
loginpage.message=this is a test message
Spring must be picking up the value at some point because whenever I change @Value("${loginpage.message}")
to a value not in the properties file like @Value("${notInPropertiesFile}")
, I get an exception.
Spring Solutions
Solution 1 - Spring
It seems that the question has been already asked https://stackoverflow.com/questions/5275724/spring-3-0-5-doesnt-evaluate-value-annotation-from-properties
The difference between web app root and servlet application contexts is one of the top sources of confusion in Spring, see https://stackoverflow.com/questions/3652090/difference-between-applicationcontext-and-spring-servlet-xml-in-spring
From @Value
javadoc :
> Note that actual processing of the @Value annotation is performed by a > BeanPostProcessor
> BeanPostProcessor interfaces are scoped per-container. This is only relevant if you are using container hierarchies. If you define a BeanPostProcessor in one container, it will only do its work on the beans in that container. Beans that are defined in one container are not post-processed by a BeanPostProcessor in another container, even if both containers are part of the same hierarchy.
Solution 2 - Spring
Yea I am having same issue with Spring 3. It doesn't seem to work inside Controllers. To fix the issue I created a another bean with @Service and injected that into the controller. It did work for me. Hope this would be helpful to someone as I spent all day to figure it out.
Solution 3 - Spring
You can @Autowire Environment
and then environment.getProperty("name")
.
See https://stackoverflow.com/a/15562319/632293
Solution 4 - Spring
You need to use PropertySourcePlaceHolder if you are using @Value annotation because it can extract the value from a properties file. If you are using java config base you need to create a bean like this
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
Or if you are using xml based then declare the bean accordingly.
Solution 5 - Spring
I had similar issue in my spring project, but specifically spring BATCH one. I built initially my config as below
@Configuration
public class BatchConfig
{
@Bean
public Job job(@Autowired Step stepMulti, @Autowired Step stepMultiDiff, @Autowired Step stepMultiPolling
){
Job job = jobBuilders.get("job")
.start(init0())
.on("POLLING").to(stepMultiPolling)
.from(init0()).on("*").to(stepMulti).next(stepMultiDiff).end()
.build();
return job;
}
@Bean
public Step init0(){
return stepBuilders.get("init0")
.tasklet(new MyDecider())
.build();
}
...
}
with MyDecider shortly as below
public class MyDecider implements StepExecutionListener , Tasklet{
@Autowired ThreadPoolTaskScheduler taskScheduler;
@Value("${read.chunk.size}") private Integer pagesize;
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
return RepeatStatus.FINISHED;
}
@Override
public ExitStatus afterStep(StepExecution exe) {
String type = exe.getJobParameters().getString("mode");
log.info("SPRING BATCH props:");
log.info(" READ chunk size: {}", pagesize);
if (StringUtils.equals(type, "send")) {
log.info("MODE batch SENDING...");
if (taskScheduler !=null) taskScheduler.shutdown();
else log.info(" Not able to stop scheduler (is null)");
return new ExitStatus("SEND");
} else {
log.info("MODE batch POLLING...");
return new ExitStatus("POLLING");
}
}
But in this way neither taskScheduler was wired nor pagesize was injected; both null. Thanks to Boris answer, after some try, I changed BatchConfig as below perfectly working
...
@Bean
public Step init0(){
return stepBuilders.get("init0")
.tasklet(decider())
.build();
}
@Bean
public Tasklet decider() {
return new MyDecider();
}
...
Reason: having MyDecider construction closer to a Bean annotation in BatchConfig (the one of decider()), make spring understand that MyDecider has to be injected properly, with values found in application.property values, and wired with TaskScheduler used (because I tried also to have SpringScheduler activation, but I wanted to swith it off if jar starting option was 'send').
NOTE: with option mode="send" spring batch job takes the way towards stepMulti and not stepMultiPolling, because MyDecider exit status was SEND and not POLLING; but that is just an explanation out of this topic, so I skip further details.
Hope this spring batch case can be helpful for someone!
Solution 6 - Spring
I'm sorry to ask the obvious, but how do you know that the @Value annotation is not working? One of the problems with the way Spring works is that the Bean pre-processing is carried out after construction of the Bean.
So if you were inspecting your Bean in the constructor with a debugger, you will not see the fields being set. You can add a method in your Bean called, say, audit() and annotate it with @PostConstruct and if you put a log statement in there, put a breakpoint on it, you should see the fields with their @Value values.
If you do that and you still do not see your @Value fields, then you might not even has scanned the Bean. A class that you think implements a Bean is still a Java class, which can be instantiated and will have its fields assigned null, if it is not being pre-processed.
To make sure your Beans are being scanned, the classes should have at least the @Component and you need to add the package of the classes to the @ComponentScan.
@ComponentScan(basePackages = { "com.example.springboot", "org.bilbo.baggins" })
If you don't have the source to the main() method is, which is where you can usually find @ComponentScan, then you can add a @Configuration class in the same package and add a @ComponentScan to that.
In this example, I have the @ComponentScan as a commented-out line in the wrong place (it should replace @ImportResources).
package com.example.springboot;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
// @ComponentScan(basePackages = { "com.example.springboot", "org.bilbo.baggins" })
@Configuration
@ImportResource({"classpath*:applicationContext.xml"})
public class Configurer {
}
I did that, to show how to use an XML file: applicationContext.xml. This contains a component-scan and creates a Bean.
(Note: only one package is stated to be scanned, component-scan seems to accumulate.)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/sc
hema/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 />
<context:component-scan base-package="org.bilbo.baggins" />
<bean id="applicationProperties"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="location" value="classpath:application.properties" />
</bean>
</beans>
It is useful to build a bean in the XML file, so that you list it and demonstrate that you have loaded the XML file. You can list the beans using the method String[] beanNames = ctx.getBeanDefinitionNames();