我遇到了以下问题。我正在使用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()
。
答案 0 :(得分:5)
你所看到的是Weld对bean初始化的懒惰方法。对于所有正常的作用域bean(CDI提供的作用域中除了@Dependent
之外的任何东西),您实际上注入了一个代理,该代理将调用委托给上下文实例。在您尝试在该代理上调用任何bean方法之前,不会创建上下文实例。
CDI规范并没有要求bean渴望或懒惰,这是基于实现的选择(我不确定Weld docs现在是否提到这一点)。在Weld的情况下,这主要是性能选择,因为许多bean将被初始化为无效(例如从未使用过),并且它会大大减慢引导速度。
请注意,这不是一个不一致的状态,对于Weld提供的每个范围,它都是这样的。它也与javax.inject.Provider.get()
不矛盾,因为它没有声明在获得实例之前必须调用@PostConstruct
。此外,您实际获得的实例是代理实例,并且无论如何都要完全初始化。
所以它归结为懒惰与急切初始化的一般问题,哪个更好和/或感觉更自然。
至于“解决方案”:
@javax.ejb.Singleton
并使用@Startup
注释。这样的行为与@ApplicationScoped
非常相似,所以如果你在EE环境中,它可能已经足够了 ping()
bean上创建一个虚拟@ApplicationScoped
方法,并在应用程序启动后立即调用它。这将强制创建bean,从而调用@PostConstruct
- 就像在上面的代码示例中使用test()
方法一样。作为旁注 - 在您的示例中,构造函数上的@Inject
注释没有用处。只有具有参数的构造函数才需要它。
答案 1 :(得分:0)
用户@PostConstruct批注+ @观察ContainerInitialized事件
@PostConstruct
确保实例将正确初始化@PostConstruct
public void setup() {
// will be executed before it's going to be injected somewhere
}
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的范围内,但答案通常是适用的