【问题标题】:@PostConstruct is not invoked for @ApplicationScoped on initialisation?@PostConstruct 不会在初始化时为@ApplicationScoped 调用?
【发布时间】:2018-08-23 01:09:23
【问题描述】:

我遇到了以下问题。我正在使用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() 的规范说:

提供一个完全构造和注入的 T 实例。

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

【问题讨论】:

  • 为什么调用@PostConstruct 方法对你很重要?
  • @SteveC 有两个原因。 1.实用一。我的 init 方法中有一些逻辑,应该在服务初始化后立即执行。假设我为 init 使用了一些脆弱的资源,并希望确保它将在 init 阶段使用,而不是在我的应用程序工作的中间。 2)我很好奇这种不一致的原因

标签: java jakarta-ee cdi weld


【解决方案1】:

您看到的是 Weld 对 bean 初始化的惰性方法。对于所有普通范围的 bean(除了来自 CDI 提供的范围的 @Dependent 之外的任何内容),您实际上注入了一个代理,它将调用委托给上下文实例。在您尝试调用该代理上的任何 bean 方法之前,不会创建上下文实例。

CDI 规范不要求 bean 急切或懒惰,这是基于实现的选择(我不确定 Weld 文档现在是否提到了这一点)。在 Weld 的情况下,这主要是性能选择,因为其中许多 bean 将被初始化(例如,从未使用过),并且会大大降低引导速度。

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

所以它归结为懒惰与渴望初始化的一般问题,哪个更好和/或感觉更自然。

至于“解决方案”:

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

附带说明 - 在您的示例中,构造函数上的 @Inject 注释没有用。只有带参数的构造函数才需要。

【讨论】:

  • 我同意@Inject 注释,但不同意其余部分。
  • 1.如果没有明确指定的范围,它不会生成代理(只需删除 @ApplicationScope 并观察结果)。
  • 2.您还没有解释为什么这种行为不符合javax.Provider.get() 规范,该规范声明它返回一个完全构造的实例
  • 一旦您对我的问题提供完整答案,我将立即删除我的投票
  • 1.显然,您需要普通作用域的 bean 来创建代理,@ApplicationScoped 是普通作用域。如果你删除它,你会得到默认值,即@Dependent,并且 不是 正常范围,因此没有代理。
【解决方案2】:

用户@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 事件最终将在应用程序引导期间产生,并 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");
        }
    }
}

问候

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 2017-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-02
      • 2015-07-01
      • 1970-01-01
      • 2016-10-18
      • 1970-01-01
      相关资源
      最近更新 更多