Search code examples
springpropertiesannotationsautowiredapplication.properties

Cannot inject property value to class parameter (@Value annotatnion)


I'm learing Spring Boot and I have probably very simple question, but it's not clear enough for me. I'm facing some problem with @Value annotation - I would like to know why apprication property cannot be injected to class parameter.

I prepared some very basic project using Spring Initializr and I added one property to my "application.properties" resource. Moreover, I created two additional classes: "YellowCar" (which works fine) and "RedCar" (which does not work - the parameter cannot be properly injected).

"application.properties" file:

car.age=15

The main class of my application:

package com.example.helper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;

@SpringBootApplication
public class HelperApplication implements CommandLineRunner {

    @Autowired
    private Environment env;

    public static void main(String[] args) {
        SpringApplication.run(HelperApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println (new RedCar(env));
        System.out.println (new YellowCar());
    }

}

RedCar is build by passing the Environment variable to constructor:

package com.example.helper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class RedCar {

    private int age;

    @Autowired
    public RedCar (Environment env) {
        this.age = new Integer(env.getRequiredProperty("car.age")).intValue();
    }

    @Override
    public String toString() {
        return "Car [age=" + age + "]";
    }

}

YellowCar is build without passing the Environment variable to the constructor, but using the @Value annotation:

package com.example.helper;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class YellowCar {

    @Value("${car.age}")
    private int age;

    @Override
    public String toString() {
        return "YellowCar [age=" + age + "]";
    }

}

This is the program output:

Car [age=15]
YellowCar [age=0]

As you can see, age of YellowCar was not properly injected (it equals 0).

My goal: I'd like not to pass everywhere Environment object to the constructor of other classes... I'd like to use @Value annotatnio instead. Could somebody explain me: 1) why my code is not working? 2) how this code should be updated in order to get the following output?

Car [age=15]
YellowCar [age=15]

Thanks!


Solution

  • This happens, because you instantiate YellowCar directly. In order to make thing work, @Autowire YellowCar inside HelperApplication and use this injected instance like this(not tested):

    @Autowired
    private YellowCar yellowCar;
    
    public static void main(String[] args) {
        SpringApplication.run(HelperApplication.class, args);
    }
    
    @Override
    public void run(String... args) throws Exception {
        System.out.println (new RedCar(env));
        System.out.println (yellowCar);
    }
    

    Explanation:

    When you creating class using new, Spring knows nothing about this new instance, hence, it cannot inject anything. But when instance is created through Spring infrastructure (I'm trying not use a lot of slang words here), it will have all fields injected: when you mark class with @Component annotation or create method with @Bean annotation, you tell Spring, that you want it to be a Bean. On application startup, spring creates instances of this beans - by default only one instance per context - and inject this instance into all other beans, where requested. In this case, this instance is being processed by spring infrastructure (exact class which do it - AutowiredAnnotationBeanPostProcessor), and instances of other beans will be injected into our bean.

    UPD: You can do the same thing with RedCar:

    @Autowired
    private YellowCar yellowCar;
    
    @Autowired
    private RedCar redCar;
    
    public static void main(String[] args) {
        SpringApplication.run(HelperApplication.class, args);
    }
    
    @Override
    public void run(String... args) throws Exception {
        System.out.println (redCar);
        System.out.println (yellowCar);
    }