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 @Configuration
class 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 private
there 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 the private
fields.
So to fix, make your method protected
or public
so it can be properly subclasses and overridden. Or remove the @Lazy
if you don't need it.
See this blog (written by me) for a more detailed explanation.