初始化时不为@ApplicationScoped 调用@PostConstruct?

@PostConstruct is not invoked for @ApplicationScoped on initialisation?

我遇到了以下问题。我正在使用 Weld 实现 CDI.

我发现如果服务用 @ApplicationScoped 注释,那么 @PostConstruct 部分在第一次使用该服务之前不会被调用。这是重现此行为的代码:

import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer; 

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.spi.CDI;

public class TestCdi {

    public static void main(String[] args) {
        try (WeldContainer weldContainer = new Weld().containerId("test").initialize()) {
             FooService fooService = CDI.current().select(FooService.class).get();

             fooService.test();
             System.out.println("Done");
        }
    }

    @ApplicationScoped
    public static class FooService {

        @PostConstruct
        public void  init() {
            System.out.println("Post construct");
        }

        public void test() {
            System.out.println("test");
        }
    }
}

因此,如果 fooService.test(); 被注释,则 FooService.init() 不会被调用。但是删除 @ApplicationScoped 它又可以工作了!

这对我来说似乎很奇怪,我找不到此类行为的描述。

此外,javax.inject.Provider.get() 的规范说:

Provides a fully-constructed and injected instance of T.

所以,问题是什么?它是这样设计的还是这是一个错误?对我来说更重要的是:如何绕过这个问题?我需要我的服务 @ApplicationScoped.

您看到的是 Weld 对 bean 初始化的惰性方法。对于所有正常作用域的 bean(除了 CDI-provided 作用域中的 @Dependent 之外的任何东西),您实际上注入了一个代理,该代理将调用委托给上下文实例。在您尝试调用该代理上的任何 bean 方法之前,不会创建上下文实例。

CDI 规范不要求 bean 急切或懒惰,这是 implementation-based 选择(我不确定 Weld 文档现在是否提到了这一点)。在 Weld 的情况下,这主要是性能选择,因为其中许多 bean 将被初始化为空(例如,从未使用过)并且它会减慢 bootstrap 很多。

请注意,这不是不一致的状态,对于 Weld 提供的每个范围,它都是这样工作的。它也不与 javax.inject.Provider.get() 矛盾,因为它没有说明 @PostConstruct 必须在您取回实例之前调用。此外,您实际上获得的实例是代理实例,并且该实例已完全初始化。

所以它归结为惰性和急切初始化的一般问题,哪个更好and/or感觉更自然。

至于一个"solution":

  • 您可以使用 EJB 的 @javax.ejb.Singleton 并使用 @Startup 注释。这将表现得非常像 @ApplicationScoped 所以它可能足够好 如果你在 EE 环境中 当然。
  • 或者您可以在 @ApplicationScoped bean 上创建一个虚拟 ping() 方法,并在您的应用程序启动时立即调用它。这将强制创建 bean,从而调用 @PostConstruct - 就像您在上面的代码示例中使用 test() 方法所做的那样。

作为旁注 - 在您的示例中,构造函数上的 @Inject 注释没有用。只有带参数的构造函数才需要它。

用户@PostConstruct注解+@Observes ContainerInitialized事件

  1. 使用@PostConstruct确保实例正确初始化
@PostConstruct
public void setup() {
    // will be executed before it's going to be injected somewhere
}
  1. 使用ContainerInitialized CDI 事件侦听以确保初始化过程最终将在容器启动期间完成:
private void on(@Observes ContainerInitialized event) {
    // will be executed during container bootstrap
}

您可以使用其中之一或两者,具体取决于您的需要...

PostConstruct(如果需要)早于 ContainerInitialized 事件发生,但是! PostConstruct 是所谓的 Lazy,而 ContainerInitialized 事件最终将在应用 bootstrap 期间产生,并有 100% 的保证。您可以控制在使用哪个 bean 之前对其进行初始化,或者您可以确保启动过程将触发一些业务逻辑,具体取决于您的需要。

在你的例子中,使用的是二合一:

public class TestCdi {

    public static void main(String[] args) {
        try (WeldContainer weldContainer = new Weld().containerId("test").initialize()) {
            System.out.println("Done");
        }
    }

    @ApplicationScoped
    public static class FooService {

        // with @PostConstruct you will make sure
        // your bean is going to be properly configure on its creation...
        @PostConstruct
        public void init() {
            System.out.println("Post construct");
        }

        // with @Observes ContainerInitialized event you will make sure
        // needed business logic will be executed on container startup...
        private void test(@Observes ContainerInitialized event) {
            System.out.println("test");
        }
    }
}

此致

CDI 的可扩展性很强。在另一个 Whosebug Q/A: CDI Eager Application scoped bean 中给出了一个例子,他们在其中创建了一个要使用的 @eager 注释。虽然这个其他问题在 JSF 的范围内,但答案是普遍适用的