Reputation: 19
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
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