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

时间:2018-03-14 13:19:59

标签: java java-ee cdi weld

我遇到了以下问题。我正在使用Weld的{​​{1}}实现。

我发现如果服务使用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的规范说:

  

提供完整构造和注入的T实例。

那么,问题是什么?它是这样设计还是这个bug?对我来说更重要的是:如何绕过这个问题?我需要我的服务javax.inject.Provider.get()

3 个答案:

答案 0 :(得分:5)

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

CDI规范并没有要求bean渴望或懒惰,这是基于实现的选择(我不确定Weld docs现在是否提到这一点)。在Weld的情况下,这主要是性能选择,因为许多bean将被初始化为无效(例如从未使用过),并且它会大大减慢引导速度。

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

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

至于“解决方案”:

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

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

答案 1 :(得分:0)

用户@PostConstruct批注+ @观察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之前要对其进行初始化,或者可以确保启动过程将触发一些业务逻辑,具体取决于您的需求。

在这种情况下,使用1中的2:

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

致谢

答案 2 :(得分:0)

CDI非常可扩展。下面的其他stackoverflow Q / A中提供了一个示例:CDI Eager Application scoped bean,其中他们创建要使用的let allowEditing = delegate?.textFieldShouldBeginEditing() ?? true 批注。尽管这个问题在JSF的范围内,但答案通常是适用的