【问题标题】:Weld CDI in Java SE: PreDestroy annotated method called too early?在 Java SE 中焊接 CDI:过早调用 PreDestroy 注释方法?
【发布时间】:2018-02-14 23:35:19
【问题描述】:

鉴于以下代码,我想知道为什么在调用 @PreDestroy 注释方法 (CacheManager#doCleanup) 后 CacheManager 仍然“活动”(请参阅​​本文末尾的输出)。 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() 被过早调用的唯一方法是在 startScanner() 返回的 Future 对象上调用 get():

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

这样,主应用程序线程不会退出。

有人知道更好的方法吗?

谢谢

【问题讨论】:

  • 我认为这种做法是合理的。 CDI 对调用其 bean 的 线程 一无所知。 CDI对bean的“破坏”并不意味着垃圾收集它们,只是CDI调用了它们的@PreDestroy钩子并忘记了它们。如果您想在 CDI 关闭后停止 PeriodicScanner,请尝试在 PeriodicScanner 中的 @PreDestroy 挂钩中关闭 ScheduledExecutorService

标签: java cdi weld-se


【解决方案1】:

输出是预期的 - Weld 无法知道您启动的其他线程,并且主线程继续运行,直到达到 container.shutdown()

这个方法(令人惊讶地)终止了容器,这意味着调用@PreDestroy 方法,然后放弃对这些bean 的引用。但是另一个线程仍然继续使用这些实例。

你可以做的是:

  • container.shutdown()移出主方法
    • main() 方法退出后焊接容器将继续工作
    • 您应该将container.shutdown() 放置在执行程序完成后将调用的方法中(取决于您的代码)
  • 不要打电话给container.shutdown()
    • Weld 本身注册了一个 shutdown hook,它会在 JVM 终止时触发
    • 此解决方案的可行性取决于您终止程序的方式
    • 您还可以实现自己的关闭挂钩并注册那个挂钩

附带说明 - 如果您只是在寻找一种在主线程中创建“等待”以让另一个线程完成工作的方法,那么您最好将该逻辑放入主线程中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-31
    • 2018-01-19
    • 2021-05-03
    • 2018-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多