2

I have a class which is created with new B(this); in this class B I want to use a value from the application.properties. But because (as far as I understand) because it's not created with Spring it won't have any injection (I use the @Value annotation)

That is how the class is created:

@Component
public class A {

    public B getB(){return new B(this);}

    /**
        a lot more stuff
    **/
}

The class in question:

public class B {

    private A a;

    public B(A a){
        this.a = a;
    }

    @Value("${threshold}")
    private String threshold;  //this is null because it's not created with Spring

    public String getThreshold(){
        return threshold;
    }
    /**
        a lot more stuff
    **/

}

So my question is the following:

1) How can I use the value in the application.properties file or

2) How can I write B that it is created with Spring?

Some background information:

  • I didn't wrote the initial code so I don't want to change too much but want to modify it so it can be maintained better in the future
  • I don't have that much Spring knowledge and only start getting more and more familiar with it.
  • In point 2) I'm struggling because of the constructorand how to set it via Spring...

Any help would be appreciated

Boendal
  • 2,496
  • 1
  • 23
  • 36
  • You can start by isolating configuration properties into separate POJOs. Then let spring boot inject the values into the POJO. You can use @Autowire to and make an instance of that configuration POJO where ever you want the values to be used. I will post an example if you need it. – k9yosh Dec 05 '19 at 14:31
  • Would appreciate that, I think I know what you mean but not entirely sure. – Boendal Dec 05 '19 at 14:33
  • Okay. I'll post an example answer for you. – k9yosh Dec 05 '19 at 14:33
  • Since `A` is a bean why don't you get the value from it? `public B(A a){ this.a = a; this.threshold = a.getThreshold(); }` – Yuriy Tsarkov Dec 05 '19 at 14:50
  • just annotated `@Bean` on `getB()` method – Ryuzaki L Dec 05 '19 at 15:55

4 Answers4

4

Here's an example of using @ConfigurationProperties to bind your application.properties to a POJO.

Say you have a application.properties file like this,

mail.hostname=mailer@mail.com
mail.port=9000

You can create a POJO like this for the above scenario.

(Note: If your spring boot version is lower than 2.2 you might want to annotate this class with @Configuration and @Component as well)

@ConfigurationProperties(prefix = "mail")
public class ConfigProperties {

    private String hostname;
    private String port;

    // Create Getters and Setters

}

When the spring boot application initializes, it will automatically map values from application.properties into this POJO. If you want to use this, all you have to do is create a variable and mark it with @Autowire

@Component
public class TestClass {

    @Autowire
    private ConfigProperties properties;

    public void printSomething() {
       System.println(properties.getHostname());
       System.println(properties.getPort());
    }

}

Following up on your question...

Since the class in your example is not managed by spring, and you have to keep it this way for some reason, you can use the following helper class to manually autowire a spring bean in a non spring managed class.

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

@Service
public class BeanUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    /**
     *Use this method to manually autowire Spring Beans into classes that are not managed by Spring.
     *
     *@param beanClass - Class type of the required bean.
     **/
    public static <T> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }

}

To use this in your class B do the following.

public class B {

    private ConfigProperties properties;

    public B() {
        BeanUtil.getBean(ConfigProperties.class); // This will initialize properties variable properly.
    }

    public void printSomething() {
       System.println(properties.getHostname());
       System.println(properties.getPort());
    }

}
k9yosh
  • 858
  • 1
  • 11
  • 31
  • 1
    It seems like not a solvation of the problem. Initial problem is how to use a properties in a POJO, but you can't inject or autowire a bean into a POJO. It's a same as if @Boendal would wrote `@Value("${threshold}") private String threshold;` in the `A` class – Yuriy Tsarkov Dec 05 '19 at 14:54
  • @YuriyTsarkov it's not about what he's asking but what he needs. Anyway, he asked "How can I use the value in the application.properties". That's why I posted this answer. – k9yosh Dec 05 '19 at 14:58
2

You can create a class that will load the properties for you directly from a file. The file must be in the resources package.

Config properties class where these properties are defined

@ConfigurationProperties(prefix = "reader")
public class ReaderProperties {
    private String threshold;
}

The property class

import java.util.Properties;

class MyProperties {

    private final Properties props;

    private MyProperties(Class<?> propertiesClass) throws IOException {
        this.props = new Properties();
        this.props.load(propertiesClass.getResourceAsStream("/application.properties"));
    }
    
    public String getThreshold() {
        return props.getProperty("threshold");
    }

}

Then inside B:

public class B {

    private A a;
    private String threshold;

    public B(A a){
        this.a = a;
        MyProperties props = new MyProperties(ReaderProperties.class);
        this.threshold = props.getThreshold();
    }

    public String getThreshold(){
        return threshold;
    }
    /**
        a lot more stuff
    **/

}
shyam padia
  • 397
  • 4
  • 16
Igor Flakiewicz
  • 694
  • 4
  • 15
1

You can write as below:

@Component
public class A {

    @Value("${threshold}")
    private String threshold;

    public B getB(){
        return new B(this);
    }

    public String getThreshold() {
        return threshold;
    }

}

public class B {

    private A a;
    private String threshold;

    public B(A a){
        this.a = a;
        this.threshold=a.getThreshold();
    }

    public String getThreshold(){
        return threshold;
    }

}

Victor1125
  • 642
  • 5
  • 16
0

The existing answers work if you're willing and able to annotate class B with @ConfigurationProperties but this might not be feasible if B is provided as part of non-Spring library, and or if B needs to be used in non-Spring projects.

Fortunately Spring does provide a more portable way to do this if B is a POJO/JavaBean:

Example class B:

public class B {
    private String threshold;
    // Is a POJO/JavaBean i.e. has default constructor, getters, setters etc.
}

In application.properties:

my.config.prefix.threshold=42

Define a Spring bean in a @Configuration class for your Spring Boot project e.g.:

@Configuration
public class ApplicationConfig {
    @Bean
    @ConfigurationProperties("my.config.prefix")
    public B getB() {
        // Spring will later use setters to configure the fields per my.config.prefix
        return new B();
    }
}

You can now rely on Spring to autowire the dependency e.g. using field injection1:

@Component
public class A {
    @Autowired
    private B b; // b.getThreshold() == 42
}

Unless a class is strictly for running or configuring a Spring Boot application, I would err to towards keeping the code more portable by defining a bean.


1 Field injection is generally not recommended but it's used here for concision.

xlm
  • 6,854
  • 14
  • 53
  • 55