Search code examples
javamicronaut

Micronaut Injected BeanContext cannot retrieve beans


In my application that is not a webservice application, so doesn't provide a rest endpoint, I'm using an injected BeanContext to create instances of other beans whenever I need them. Those beans are @Prototype beans, not @Singleton as they should only exists when needed and discarded after use.

The code worked fine with micronaut 3.6.3 but when I upgrade to 4.2.2, the injected BeanContext cannot find any bean. beanContext.getBean(ProtoTypeBean.class); will throw a NoSuchBeanException

To clarify what I mean, I have a reproducible case in my github repository.

A simplified version of the code looks like this:

The main application starts the ApplicationContext which creates an instance of ScheduleReader and calls run()

public static void main(String[] args) {
    try (final ApplicationContext context = ApplicationContext.run()) {
        context.getBean(ScheduleReader.class).run();
    }
}

In the ScheduleReader.run() method, I use the injected BeanContext to create an instance of another Bean

@Singleton
@Slf4j
public class ScheduleReader implements Runnable {

   private final BeanContext beanContext;

   @Inject
   ScheduleReader(final BeanContext beanContext) {
      this.beanContext = beanContext;
   }

   @Override
   public void run() {
      beanContext.getBean(Action.class).run();
   }

}

Action is a @Prototype as well, but at this point, I get the following exception:

12:22:29.946 [pool-1-thread-1] ERROR com.example.ScheduleReader - Error while starting Action: No bean of type [com.example.Action] exists. Make sure the bean is not disabled by bean requirements (enable trace logging for 'io.micronaut.context.condition' to check) and if the bean is enabled then ensure the class is declared a bean and annotation processing is enabled (for Java and Kotlin the 'micronaut-inject-java' dependency should be configured as an annotation processor).
io.micronaut.context.exceptions.NoSuchBeanException: No bean of type [com.example.Action] exists. Make sure the bean is not disabled by bean requirements (enable trace logging for 'io.micronaut.context.condition' to check) and if the bean is enabled then ensure the class is declared a bean and annotation processing is enabled (for Java and Kotlin the 'micronaut-inject-java' dependency should be configured as an annotation processor).
    at io.micronaut.context.DefaultBeanContext.newNoSuchBeanException(DefaultBeanContext.java:2773)
    at io.micronaut.context.DefaultApplicationContext.newNoSuchBeanException(DefaultApplicationContext.java:304)
    at io.micronaut.context.DefaultBeanContext.resolveBeanRegistration(DefaultBeanContext.java:2735)
    at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:1729)
    at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:856)
    at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:848)
    at com.example.ScheduleReader.runPlanner(ScheduleReader.java:41)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)

When I use another ApplicationContext.run() in ScheduleReader, then I can use that application context to retrieve other beans, but I cannot imagine you now have to start ApplicationContexts every time you want to create a new instance of a bean...

The Micronaut guides are focused on launching applications with rest controllers, but as said, this is just a stand-alone application that doesn't expose endpoints. I also didn't find any clue in the migration guides on what was changed or how to solve this problem.

What am I missing?


Solution

  • The ScheduleReader uses a ScheduledExecutorService to regularly fire a new cycle of functionality. This runs in the background and causes the run method to finish. This will activate the autocloseable which will fire the DefaultBeanContext#stop() method.

    The bean definitions are stored in the beanDefinitionClasses cache of the DefaultBeanContext. Previous versions of Micronaut didn't clear this cache when calling DefaultBeanContext#stop() but current versions do clear it.

    So, because the main method finishes and the autocloseable is closed, the cache is gone by the time that the ScheduledExecutorService fires its first cycle.

    One solution is to block the run() method so that it never finishes and the autocloseable never stops. Another solution could be to remove the try() around the autocloseable and never call the .stop() method, so that the cache is never cleared. But in my opinion, the latter is not a clean solution.

    I pushed my solution to the repository in separate commits and will keep it up for future reference.