Steven
Steven

Reputation: 19

Can't autowire ConfigurationProperties class

I can't autowire a configuration properties class. The instance is always null.

I followed this article: https://www.baeldung.com/configuration-properties-in-spring-boot#1-spring-boot-22

Spring boot is version 3.1.4

This is my configuration properties class. If I debug and set breakpoints inside the setters. I can see them being called and fetching the data as expected and setting them into the class fields.

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

import java.util.List;
import java.util.Map;

@ConfigurationProperties("path.in.config")
public class MdcConfig {

    private List<String> allowedItems;
    private Map<String, String> replacementItems;

    public List<String> getAllowedItems() {
        return allowedItems;
    }

    public void setAllowedItems(List<String> allowedItems) {
        this.allowedItems = allowedItems;
    }

    public Map<String, String> getReplacementItems() {
        return replacementItems;
    }

    public void setReplacementItems(Map<String, String> replacementItems) {
        this.replacementItems = replacementItems;
    }
}

Here the main application:

@SpringBootApplication
// other stuff
@ConfigurationPropertiesScan("path.to.my.config.class")
public class Application implements CommandLineRunner {

    public static void main(final String[] args) {
        SpringApplication application = new SpringApplication(Application.class);
        application.addInitializers(new ConfigurationInitializer());
        application.run(args);
    }

Then when I try to use this class in my code as such:

@Data
@Slf4j
@Component
public class SomeClass extends SomeOtherClass<ILoggingEvent> {

    @Autowired
    private MdcConfig mdcConfig;

    @Override
    protected void append(ILoggingEvent event) {
        // ... other stuff
        List<String> mdcAllowedItems = mdcConfig.getAllowedItems(); // null pointer on "mdcConfig"
    }

Update to address question closed:

I am sorry if the question was not written well. I know why the instance is null. I don't know how to fix it with Spring. The linked questions only explain why it happens, not how to fix it. So they are not helpful.

Order in which fields in a bean are initialized

This explains that the default constructor is being called. It doesn't explain what to do but I suppose the suggestion is to put the instantiation in the default constructor? So I can do:

public class SomeClass extends SomeOtherClass<ILoggingEvent> {
    private MdcConfig mdcConfig;

    public SomeClass() {
        this.super();
        // ... some other stuff
        this.mdcConfig = new MdcConfig();
    }
}

This will instantiate MdcConfig instance and this.mdcConfig won't be null obviously. So the null pointer exception doesn't happen. But this is "MY" instance of MdcConfig, not the instance that Spring created at the application startup which holds the actual config values due to some "magic" happening when Spring instantiates the class. So even mdcConfig is not null, calling mdcConfig.getAllowedItems() for example will return null.

@Autowired bean is null when referenced in the constructor of another bean

This suggests to put @PostConstruct. But it doesn't explain where to put it and then what to do in the method where it was put. I can't open the links in the checked answer. Maybe outdated or because I'm behind company firewall. I googled about it but don't get an idea how it would help. So just guessing I should do something like this?

public class SomeClass extends SomeOtherClass<ILoggingEvent> {

    @Autowired
    private MdcConfig mdcConfig;

    @Override
    @PostConstruct
    protected void append(ILoggingEvent event) {
        // ... other stuff
        List<String> mdcAllowedItems = mdcConfig.getAllowedItems(); // still null pointer on "mdcConfig"
    }

or

@Data
@Slf4j
@Component
public class SomeClass extends SomeOtherClass<ILoggingEvent> {

    @Autowired
    private MdcConfig mdcConfig;

    @PostConstruct
    public void init() {
        // not even sure what to do here then
        this.mdcConfig = new MdcConfig(); // same as mentioned before it's not null but doesn't hold config values
    }

    @Override
    protected void append(ILoggingEvent event) {
        // ... other stuff
        List<String> mdcAllowedItems = mdcConfig.getAllowedItems(); // null pointer on "mdcConfig"
    }

So these answers don't help solve my issue.

Update 2 - I found the solution

I can't put an answer because someone voted to close my question...GREAT!

So I will put the solution here. Thanks goes to https://stackoverflow.com/a/18009788/27726130 for providing the final straw to the puzzle.

I needed to make SomeClass context aware as such:

@Data
@Slf4j
@Component
public class SomeClass extends SomeOtherClass<ILoggingEvent> implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    // ...

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        SomeClass.applicationContext = applicationContext;
    }

    // ...

}

Then in the method where I need MdcConfig instance of spring I can do:

@Override
protected void append(ILoggingEvent event) {
    // ... other stuff
    try {
        MdcConfig mdcConfig = applicationContext.getBean(MdcConfig.class); // mdcConfig is now the instance that has the config values

        List<String> mdcAllowedItems = mdcConfig.getAllowedItems();
    } catch (Exception e) {
        log.error("MdcConfig is not found in ApplicationContext.");
    }
}

Upvotes: 1

Views: 58

Answers (1)

M. Deinum
M. Deinum

Reputation: 125202

The problem is actually your code.

@Data
@Slf4j
@Component
public class SomeClass extends SomeOtherClass<ILoggingEvent> {

    @Autowired
    private MdcConfig mdcConfig;

    List<String> mdcAllowedItems = mdcConfig.getAllowedItems();

As you are using field injection the field mdcConfig will be filled after the object has been constructed. However mdcAllowedItems is trying to be initialized during the initialization of the object. The initialization happens before Spring has had a change to inject the field, and thus will fail.

You should either move the initialization of the field to an @PostConstruct method which will be called after construction and injection of the fields. However the best solution is to use constructor injection and initialize the mdcAllowedItems in the constructor.

@Data
@Slf4j
@Component
public class SomeClass extends SomeOtherClass<ILoggingEvent> {

    private final MdcConfig mdcConfig;
    List<String> mdcAllowedItems;

    public SomeClass(MdcConfig mdcConfig) {
      this.mdcConfig=mdcConfig;
      this.mdcAllowedItems = mdcConfig.getAllowedItems();
    }

Upvotes: 2

Related Questions