I am seeing a weird behavior with injecting Jackson ObjectMapper with Spring Boot 2.3.4.RELEASE.
I have both spring-boot-starter-web & spring-boot-starter-json in my maven dependencies. Yet when i auto-wire ObjectMapper in one of my @Service classes, it does not get injected & the reference is null.
I then tried creating & returning one manually in an @Primary @Bean bean method of a @Configurationclass but still with the same result.
I know the @Configuration is working fine since other bean methods in it are used to inject other objects correctly. In addition i also added a log statement inside the bean method that returns the ObjectMapper instance which is also getting logged, yet the reference on the @Service class is STILL null? (i also tried adding @AutoConfigureAfter(JacksonAutoConfiguration.class) in vain)
Anyone else faced this or knows what's going on here? please throw light..
Thanks
EDIT (10/22): Below is a snippet of the @Service class,
@Lazy
@Service
@EnableSpringConfigured
public class SampleServiceSingletonService {
private static final Logger LOGGER = LoggerFactory.getLogger(SampleServiceSingletonService.class);
@Autowired
protected ThreadPoolTaskScheduler threadPoolTaskScheduler;
@Autowired
private ObjectMapper objectMapper;
@EventListener({ApplicationReadyEvent.class})
private void initOnAppReady() {
LOGGER.info("No need to create ObjectMapper instances, most customizations can be set/overriden in application.properties, look at the one here for reference");
LOGGER.info("Injected ObjectMapper: {}", objectMapper);
LOGGER.info("Init on app ready complete..");
//...
}
EDIT 2 (10/22): For others who face this,
The problem appears to be (thanks to @M. Deinum below) that the instantiation and/or injection doesn't seem to happen at the time the ApplicationReadyEvent is fired & its event handlers are invoked. That seems strange to me for two reasons, one is as per the docs, the event is "published as late as conceivably possible to indicate that the application is ready to service requests...since all initialization steps will have been completed by then" and second i have not seen this behavior with other injections of other objects till now so i never suspected this as a cause. Below is a snippet where i see the injection working,
@Lazy
@Service
public class SampleServiceSingletonService {
private static final Logger LOGGER = LoggerFactory.getLogger(SampleServiceSingletonService.class);
@Autowired
public ThreadPoolTaskScheduler threadPoolTaskScheduler;
@Autowired
public ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Autowired
public ObjectMapper objectMapper;
@EventListener(ApplicationReadyEvent.class)
private void initOnAppReady() {
// Its null here..
//LOGGER.info("The Injected ObjectMapper: {}", objectMapper);
LOGGER.info("Init on app ready complete..");
runAsync();
}
@Async
public void runAsync() {
LOGGER.info("This is run asynchronously.");
// Its no longer null here
LOGGER.info("The Injected ObjectMapper: {}", objectMapper);
}
}
Thanks
You have marked your bean with
@Lazy. When put on a type what will happen is that a lazy proxy will be created for the object. In this case a class based proxy will be created (no interface on the class) and for this a subclass will be created to add the lazy behavior.However due to the fact that your method is
privatethere is no way to override this method in the dynamic created class and thus it will be called on the proxy instead or relaying it through to the actual object. The proxy object will not have any dependencies injected, at least not theprivatefields.So to fix, make your method
protectedorpublicso it can be properly subclasses and overridden. Or remove the@Lazyif you don't need it.See this blog for a more detailed explanation.