获取 CDI 托管 bean 实例的规范方法:BeanManager#getReference() 与 Context#get()
Posted
技术标签:
【中文标题】获取 CDI 托管 bean 实例的规范方法:BeanManager#getReference() 与 Context#get()【英文标题】:Canonical way to obtain CDI managed bean instance: BeanManager#getReference() vs Context#get() 【发布时间】:2013-12-01 15:21:39 【问题描述】:我认为,当只有Bean<T>
开头(基于Class<T>
创建)时,有两种通用方法可以通过BeanManager
获取自动创建的CDI 托管bean 实例:
作者BeanManager#getReference()
,更常见于
sn-ps:
Bean<TestBean> bean = (Bean<TestBean>) beanManager.resolve(beanManager.getBeans(TestBean.class));
TestBean testBean1 = (TestBean) beanManager.getReference(bean, bean.getBeanClass(), beanManager.createCreationalContext(bean));
Context#get()
,在 sn-ps 中很少出现:
Bean<TestBean> bean = (Bean<TestBean>) beanManager.resolve(beanManager.getBeans(TestBean.class));
TestBean testBean2 = beanManager.getContext(bean.getScope()).get(bean, beanManager.createCreationalContext(bean));
实际上,它们最终做的事情完全相同:返回对当前 CDI 托管 bean 实例的代理引用,如果该 bean 实例在作用域中不存在,则自动创建它。
但他们的做法有点不同:BeanManager#getReference()
总是创建一个全新的代理实例,而 Context#get()
重用现有的代理实例(如果之前已经创建)。当上述代码在现有TestBean
实例的操作方法中执行时,这一点很明显:
System.out.println(testBean1 == testBean2); // false
System.out.println(testBean1 == this); // false
System.out.println(testBean2 == this); // true
Context#get()
的 javadoc 在这方面非常明确:
返回某个上下文类型的现有实例或通过调用 Contextual.create(CreationalContext) 创建一个新实例并返回新实例。
虽然BeanManager#getReference()
的javadoc 在这方面不够明确:
获取某个bean的上下文引用以及该bean的某个bean类型。
这让我很困惑。你什么时候使用一个或另一个?对于这两种方式,您无论如何需要一个Bean<T>
实例,bean 类和bean 范围很容易从中获得,这是作为附加参数所必需的。我无法想象为什么在这种特定情况下需要从外部提供它们。
我可以想象Context#get()
的内存效率更高,因为它不会不必要地创建另一个引用相同底层 bean 实例的代理实例,而只是查找并重用现有的代理实例。
这让我想到了以下问题:BeanManager#getReference()
究竟什么时候比Context#get()
更有用?它更常显示在 sn-ps 中,并且更经常被推荐为解决方案,但它只会不必要地创建一个新代理,即使已经存在一个代理。
【问题讨论】:
【参考方案1】:beanManager#getReference 为您提供客户端代理的新实例,但客户端代理会将方法调用转发到特定上下文的当前上下文实例。 一旦您获得代理并保留它,将在当前实例(例如当前请求)上调用方法调用。 如果上下文实例不可序列化,这也很有用 - 客户端代理将在您反序列化后重新连接。
BeanManager#getContext 无需客户端代理即可获取目标实例。您可能仍会在类名中看到 Weld 的代理,但它是提供拦截和装饰的增强子类。如果 bean 没有被拦截或修饰,这将是给定 bean 的一个普通实例。
通常 (1) 更合适,除非您有特殊用例需要直接访问目标实例(例如访问其字段)。
或者换句话说
1) BeanManager#getReference 将返回一个“上下文引用”,其中包含 bean 的正常范围代理。
如果一个bean有@SessionScoped
作为
@SessionScoped User user;
然后上下文引用用户将“指向”每次调用的当前会话的相应用户实例(“上下文实例”)。
从两个不同的网络浏览器对user.getName()
的两次不同调用会给你不同的答案。
2) Context#get() 将返回一个没有正常作用域代理的内部“上下文实例”。这通常不是用户应该自称的。如果您以这种方式获得“Bob”的User user
并将其存储在@ApplicationScoped
bean 或静态变量中,
那么它将永远是用户“Bob”——即使是来自其他浏览器的网络请求!您将获得一个直接的、非代理的实例。
【讨论】:
我已经更新了答案,希望现在你会得到真正的答案。有时链接非常适合答案 回到具体问题,好吧,Context#get()
因此实际上没有返回任何代理。我相信我被增强的子类误导了。这现在确实更有意义。如果您确实需要一个可序列化的代理,请使用BeanManager#getReference()
。如果您不需要可序列化代理和/或需要通过反射探索实例,请使用Context#get()
。谢谢你的回答!
是的,你是绝对正确的第三条评论。不用说谢谢,不客气
也认为打印出来的答案 - 没有摘要的链接在那里毫无用处。【参考方案2】:
这在将 CDI 与 javafx 集成时非常有用,问题是我需要引用正确的作用域对象,而不是依赖作用域的代理...
我使用了生产者方法来获取一个 javaFX 节点,该节点像这样注入到控制器中:
@Inject
@ApplicationScoped
@FXMLFile("javafx/wares.fxml")
@FXMLController(WaresController.class)
Parent wares;
但是当使用 BeanManager#getReference() 代理时,我得到“吃掉”FXMLLoader 设置的所有值,getContext.get() 方法解决了它。
谢谢
【讨论】:
【参考方案3】:我有一个 Singleton,我使用 getReference() 方法来获取引用。即使单例已经初始化,通过 getReference() 创建的代理在每次使用 getReference() 时都会调用 @PostConstruct。
@Startup
@ApplicationScoped
@Singleton
@PostConstruct
private void initialize()
通过切换到 getContext().get() 方法,不再进行不必要的@PostConstruct 代理调用。
【讨论】:
有趣的副作用。我相信这一定是使用的 CDI 实现中的一个错误。您使用的是哪个 CDI impl/version?以上是关于获取 CDI 托管 bean 实例的规范方法:BeanManager#getReference() 与 Context#get()的主要内容,如果未能解决你的问题,请参考以下文章
CDI 托管 bean 和 JSF 托管 bean 可以相互通信吗?
使用 CDI(上下文和依赖注入)支持 bean 而不是托管 Bean