获取CDI托管Bean实例的规范方法:BeanManager#getReference() vs Context#get()

43

我发现在仅有一个Bean<T>的情况下(该Bean是基于Class<T>创建的),通过BeanManager获得自动创建的CDI托管bean实例通常有两种一般方法:

  1. 通过BeanManager#getReference()方式,这种方式在代码片段中更为常见:

    Bean<TestBean> bean = (Bean<TestBean>) beanManager.resolve(beanManager.getBeans(TestBean.class));
    TestBean testBean1 = (TestBean) beanManager.getReference(bean, bean.getBeanClass(), beanManager.createCreationalContext(bean));
    
  2. 通过Context#get()方法,该方法在片段中很少显示:

  3. 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<T>实例,从而获得所需的bean类和bean范围作为额外的参数。在这种情况下,我无法想象为什么它们需要从外部提供。

我可以想象Context#get()更加内存高效,因为它不会不必要地创建另一个代理实例来引用同一底层的bean实例,而只是找到并重用现有的代理实例。

这使我产生了以下问题:在什么情况下BeanManager#getReference()Context#get()更有用?它常常显示在代码片段中,并经常被推荐作为解决方案,但即使已经存在一个代理,它也会不必要地创建一个新的代理。

3个回答

37

beanManager#getReference方法返回一个客户端代理的新实例,但是该客户端代理将转发方法调用到特定上下文的当前上下文实例。 一旦获得了代理并保留它,方法调用将在当前实例(例如当前请求)上被调用。 如果上下文实例不可序列化,则使用客户端代理很有用——客户端代理将是可序列化的,并且在反序列化后会重新连接。

BeanManager#getContext方法获取目标实例而没有客户端代理。你仍然可以看到Weld的代理类名,但这是一个增强的子类,提供拦截和装饰。如果Bean未被拦截或装饰,则这将是给定Bean的普通实例。

通常情况下,(1)更适合使用,除非你有特殊的用例需要直接访问目标实例(例如访问其字段)。

换句话说

1) BeanManager#getReference返回一个'Contextual Reference',带有一个正常作用域的bean代理。 如果一个bean具有@SessionScoped属性,

@SessionScoped User user;

然后,上下文引用用户将会“指向”当前会话的相应用户实例(“上下文实例”),每次调用都如此。来自两个不同Web浏览器的user.getName()的两个不同调用将给出不同的答案。

2)Context#get()将返回内部的“上下文实例”,而没有正常的作用域代理。这通常不是用户自己应该调用的。如果以这种方式获取名为“Bob”的User user并将其存储在@ApplicationScoped bean或静态变量中, 则它将始终保持为用户“Bob” - 即使来自其他浏览器的Web请求!您将获得一个直接的、非代理的实例。


我已经更新了答案,希望现在你能得到正确的答案。有时链接对于回答非常好。 - Asif Bhutto
2
回到具体问题,Context#get()实际上并不返回任何代理。我相信我被增强的子类误导了。现在这确实更有意义了。如果你实际上需要一个可序列化的代理,请使用BeanManager#getReference()。如果您不需要可序列化的代理和/或需要通过反射探索实例,则使用Context#get()。谢谢您的答案! - BalusC
是的,你说得很对,第三条评论。不用谢,你很受欢迎。 - Asif Bhutto
同时考虑输出答案——没有摘要的链接在那里是无用的。 - Thorbjørn Ravn Andersen

1
我有一个单例(Singleton),我使用getReference()方法获取它的引用。尽管单例已经初始化完毕,但通过getReference()创建的代理每次使用时都会调用@PostConstruct。
@Startup
@ApplicationScoped
@Singleton

@PostConstruct
private void initialize() {}

通过切换到getContext().get()方法,就不再需要进行无用的@PostConstruct代理调用。

有趣的副作用。我相信这一定是使用的CDI实现中的一个错误。你使用的是哪个CDI实现版本? - BalusC

0

在将CDI与JavaFX集成时,这非常有帮助。问题是我需要引用正确作用域的对象而不是依赖范围的代理...

我使用了一个生产者方法来获取一个JavaFX节点,然后将其注入到控制器中:

@Inject
@ApplicationScoped
@FXMLFile("javafx/wares.fxml")
@FXMLController(WaresController.class)
Parent wares;

但是当使用BeanManager#getReference()时,我得到的代理“吃掉”了FXMLLoader设置的所有值,getContext.get()的方式解决了这个问题。

谢谢你


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接