Weld CDI в Java SE: аннотированный метод PreDestroy вызывается слишком рано?

учитывая следующий код, мне интересно, почему CacheManager все еще «живой» после вызова аннотированного метода @PreDestroy (CacheManager#doCleanup) (см. вывод в конце этого поста). Разве Weld не знает о том, что на него все еще ссылаются? И как вызвать этот метод, когда объект действительно больше не используется?

Основной класс

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

@Singleton
public class MyLauncher {

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

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

Класс PeriodicScanner...

@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);
    }

}

И класс CacheManager:

@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;
    }
}

Результат:

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

Как видите, периодический сканер все еще работает после закрытия контейнера. На данный момент единственный способ предотвратить слишком ранний вызов doCleanup() — это вызвать get() для объекта Future, возвращаемого startScanner():

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

Таким образом, основной поток приложения не завершится.

Кто-нибудь знает лучший способ сделать это?

Спасибо

I


person Dee.C.    schedule 06.09.2017    source источник
comment
Я думаю, что это поведение разумно. CDI ничего не знает о потоке, который вызывает его bean-компоненты. И уничтожение bean-компонентов с помощью CDI не означает их сборку мусора, просто CDI вызвал их @PreDestroy хуки и забыл о них. Попробуйте отключить ScheduledExecutorService в хуке @PreDestroy в PeriodicScanner, если вы хотите остановить PeriodicScanner после закрытия CDI.   -  person Nikos Paraskevopoulos    schedule 06.09.2017


Ответы (1)


Ожидается вывод — Weld не может знать о других запущенных вами потоках, и основной поток просто продолжает работать, пока не достигнет container.shutdown().

Этот метод (на удивление) завершает работу контейнера, что означает вызов @PreDestroy методов, а затем удаление ссылок на эти компоненты. Но другой поток по-прежнему продолжает использовать эти экземпляры.

Что вы можете сделать, это:

  • Move container.shutdown() out of main method
    • Weld container would continue to work after main() method exits
    • Вы должны поместить container.shutdown() в метод, который будет вызываться после выполнения исполнителя (зависит от вашего кода)
  • Do NOT call container.shutdown() at all
    • Weld itself registers a shutdown hook which triggers upon JVM termination
    • Жизнеспособность этого решения зависит от того, как вы завершаете свою программу.
    • Вы также можете реализовать свой собственный хук выключения и зарегистрировать его вместо этого.

В качестве примечания: если вы только ищете способ создать «ожидание» в своем основном потоке, чтобы позволить другому потоку выполнять эту работу, вам может быть лучше просто поместить эту логику в основной поток.

person Siliarus    schedule 07.09.2017