Spring Boot 2.x Jackson ObjectMapper instance does not get injected in service class – tried both boot default & with bean method from config class

huangapple go评论89阅读模式
英文:

Spring Boot 2.x Jackson ObjectMapper instance does not get injected in service class - tried both boot default & with bean method from config class

问题

我在使用Spring Boot 2.3.4.RELEASE注入Jackson的ObjectMapper时遇到了奇怪的行为。

我在Maven依赖中同时添加了spring-boot-starter-webspring-boot-starter-json。然而,当我在一个@Service类中尝试自动注入ObjectMapper时,它并没有被注入,引用是null。

然后,我尝试在一个@Configuration类的@Primary @Bean方法中手动创建并返回一个ObjectMapper,但结果仍然相同。

我知道@Configuration是正常工作的,因为它内部的其他Bean方法被用来正确地注入其他对象。此外,我还在返回ObjectMapper实例的Bean方法内部添加了日志语句,这些日志也被记录下来,然而在@Service类中的引用仍然是null?(我还尝试徒劳地添加了@AutoConfigureAfter(JacksonAutoConfiguration.class)

还有其他人遇到过这个问题吗?或者有谁知道发生了什么?请指点一下...

谢谢

编辑(10/22):以下是@Service类的片段,

@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("不需要创建ObjectMapper实例,大多数定制可以在application.properties中设置/覆盖,在这里可以找到参考");
      LOGGER.info("注入的ObjectMapper: {}", objectMapper);
      LOGGER.info("应用就绪初始化完成..");
      //...
  }
}

编辑 2(10/22):对于其他遇到这个问题的人,

问题似乎是(感谢下面的@M. Deinum),在ApplicationReadyEvent被触发并且其事件处理程序被调用的时候,实例化和/或注入似乎并没有发生。这对我来说很奇怪,因为根据文档,该事件“尽可能晚地发布,以指示应用程序已准备好提供服务...因为到那时所有的初始化步骤都已经完成”,而且我之前没有看到过其他对象的注入出现这种行为,所以我从未怀疑过这可能是一个原因。以下是我看到注入正常工作的片段,

@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() {
      // 这里是null..
      //LOGGER.info("The Injected ObjectMapper: {}", objectMapper);
      LOGGER.info("应用就绪初始化完成..");
      runAsync();
  }

  @Async
  public void runAsync() {
      LOGGER.info("这是异步运行的。");
      // 现在不再是null了
      LOGGER.info("The Injected ObjectMapper: {}", objectMapper);
  }
}

谢谢

英文:

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

答案1

得分: 1

你已经使用@Lazy注解标记了你的bean。当应用在一个类型上时,会创建一个惰性代理对象。在这种情况下,将会创建一个基于类的代理(类上没有接口),为此会创建一个子类以添加惰性行为。

然而,由于你的方法是private的,无法在动态创建的类中对其进行重写,因此该方法将在代理上被调用,而不是传递到实际对象。代理对象将不会注入任何依赖项,至少不会有private字段的注入。

所以,为了修复这个问题,将你的方法改为protected或者public,这样它就可以被正确地子类化和重写。或者如果不需要的话,移除@Lazy注解。

更详细的解释请参考这篇博客

英文:

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 for a more detailed explanation.

答案2

得分: -2

你应该为你的私有ObjectMapper字段添加settergetter,这样Spring就能够为其赋值。

英文:

You should add setter and getter to your

     private ObjectMapper objectMapper;

so Spring is able to assign it a value

huangapple
  • 本文由 发表于 2020年10月22日 17:46:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/64479597.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定