60

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.

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
Chris
  • 1,163
  • 3
  • 13
  • 22
  • Chris answer should work ensure that the properties file is within the project classpath and if it is going to be outside war, then in application context properties placeholder should be used. It should work. – Tim Jun 04 '14 at 03:35

6 Answers6

68

It seems that the question has been already asked Spring 3.0.5 doesn't 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 Difference between applicationContext.xml and spring-servlet.xml in Spring Framework

From @Value javadoc :

Note that actual processing of the @Value annotation is performed by a BeanPostProcessor

From Spring documentation:

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.

Community
  • 1
  • 1
Boris Treukhov
  • 17,493
  • 9
  • 70
  • 91
  • 12
    Thanks, that worked. I had to add `` to the dispatcher-servlet.xml and its able to read from the properties file now. – Chris Aug 09 '12 at 20:44
  • 8
    if anyone is trying to access these properties in the class constructor, remember that you can't because they aren't wired in yet. you should implement InitializingBean::afterPropertiesSet instead – dev Oct 14 '13 at 16:36
  • This also works for injection of property values into Spring MVC interceptors as well, e.g., subclasses of HandlerInterceptorAdaptor. – otter606 Nov 20 '13 at 09:02
  • @Chris thanks for that tip. Been pulling my hair out over that one. – cbmeeks Dec 16 '15 at 14:59
  • 1
    Thank you! I wish I could have found this answer faster. To repeat, if your instance members annotated with `@Value("${key}")` are getting set to the unresolved "${key}" at runtime, then the problem may be that you have a hierarchy of application contexts, and therefore you need the property placeholders to be configured at multiple levels. – Landon Kuhn Oct 26 '16 at 21:21
8

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.

tn.stackoverflow
  • 367
  • 1
  • 3
  • 6
  • Hi, I tried your solution, but I'm having the same problem with the service, Does the service need anything special ? – Dany Y Apr 16 '16 at 07:41
4

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.

AbdusSalam
  • 485
  • 5
  • 13
3

You can @Autowire Environment and then environment.getProperty("name"). See https://stackoverflow.com/a/15562319/632293

Community
  • 1
  • 1
jediz
  • 4,459
  • 5
  • 36
  • 41
0

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!

0

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();

Bonaparte
  • 459
  • 4
  • 4