Weld CDI in Java SE: PreDestroy annotated method called too early?

1k views Asked by At

given the following code, I wonder why the CacheManager is still "alive" after the call to the @PreDestroy annotated method (CacheManager#doCleanup) (see output at the end of this post). Isn't Weld aware of the fact that it's still referenced ? And how to get this method called when the object is really not used anymore ?

Main class

public class Main {
    public static void main(String[] parameters) {
        //Init weld container        
        Weld weld = new Weld();
        WeldContainer container = weld.initialize();
        container.select(MyLauncher.class).get().startScanner();
        weld.shutdown();
    } 
}

MyLaucher class

@Singleton
public class MyLauncher {

    @Inject
    private Logger logger;
    @Inject
    private PeriodicScanner periodicScanner;

    public Future startScanner() {
        logger.info("Starting file producers...");
        return periodicScanner.doScan();
    }
}

PeriodicScanner class...

@Singleton
public class PeriodicScanner {

    @Inject
    private Logger logger;
    @Inject
    private CacheManager myCacheMgr;
    private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder()
            .setNameFormat("periodic-%d")
            .build());

    public Future doScan() {
        return scheduledExecutorService.scheduleAtFixedRate(() -> {
            myCacheMgr.doStuff();
            logger.info("Hello from PeriodicScanner");
        }, 1, 15, TimeUnit.SECONDS);
    }

}

And the CacheManager class:

@Singleton
public class CacheManager {
    @Inject
    Logger logger;

    @PostConstruct
    private void doInit(){
        logger.info("PostConstruct called for ID {}", this.hashCode());
    }

    @PreDestroy
    private void doCleanup(){
        logger.info("Cleaning up for ID {}", this.hashCode());
    }

    public int doStuff(){
        logger.info("Doing stuff from instance ID {}", this.hashCode());
        return 1;
    }
}

The output is:

Sep 06, 2017 3:47:51 PM org.jboss.weld.bootstrap.WeldStartup <clinit>
INFO: WELD-000900: 2.4.4 (Final)
Sep 06, 2017 3:47:51 PM org.jboss.weld.bootstrap.WeldStartup startContainer
INFO: WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
Sep 06, 2017 3:47:52 PM org.jboss.weld.environment.se.WeldContainer fireContainerInitializedEvent
INFO: WELD-ENV-002003: Weld SE container 2d18aac9-f66d-4373-b581-9c5cababd65a initialized
[main] INFO com.mycompany.cdiplayground.CacheManager - PostConstruct called for ID 611572016
[main] INFO com.mycompany.cdiplayground.MyLauncher - Starting file producers...
[main] INFO com.mycompany.cdiplayground.CacheManager - Cleaning up for ID 611572016
Sep 06, 2017 3:47:52 PM org.jboss.weld.environment.se.WeldContainer shutdown
INFO: WELD-ENV-002001: Weld SE container 2d18aac9-f66d-4373-b581-9c5cababd65a shut down
[periodic-0] INFO com.mycompany.cdiplayground.CacheManager - Doing stuff from instance ID 611572016
[periodic-0] INFO com.mycompany.cdiplayground.PeriodicScanner - Hello from PeriodicScanner
[periodic-0] INFO com.mycompany.cdiplayground.CacheManager - Doing stuff from instance ID 611572016
[periodic-0] INFO com.mycompany.cdiplayground.PeriodicScanner - Hello from PeriodicScanner

As you can see, the periodic scanner is still alive after the container shutdown. For the moment, the only way for me to prevent doCleanup() to be called too early is to call get() on the Future object returned by startScanner():

container.select(MyLauncher.class).get().startScanner().get();

This way, the main application thread won't exit.

Does anybody know a better way to do that?

Thanks

I

1

There are 1 answers

0
Siliarus On

The output is expected - Weld cannot know about other threads you spin up and the main thread simply keeps going until it reaches container.shutdown().

This method (surprisingly) terminates container which means calling the @PreDestroy methods and then letting go of references for those beans. But the other thread still keeps using these instances.

What you could do is:

  • Move container.shutdown() out of main method
    • Weld container would continue to work after main() method exits
    • You should place container.shutdown() to a method which will be called after the executor is done (depends on your code)
  • Do NOT call container.shutdown() at all
    • Weld itself registers a shutdown hook which triggers upon JVM termination
    • The viability of this solution depends on how you terminate your program
    • You can also implement your own shutdown hook and register that one instead

As a side note - if you are only looking for a way to create a "wait" in your main thread just to let another thread do the job, you might be better off just putting that logic into main thread.