获取CDI托管bean实例的典型方法:BeanManager#getReference()vs Context#get()

Bal*_*usC 38 cdi managed-bean

我认为有两种通用方法可以获得一个自动创建的CDI托管bean实例BeanManager,只需要一个Bean<T>开始(基于它创建Class<T>):

  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));
    
    Run Code Online (Sandbox Code Playgroud)
  2. 通过Context#get(),在片段中不常显示:

    Bean<TestBean> bean = (Bean<TestBean>) beanManager.resolve(beanManager.getBeans(TestBean.class));
    TestBean testBean2 = beanManager.getContext(bean.getScope()).get(bean, beanManager.createCreationalContext(bean));
    
    Run Code Online (Sandbox Code Playgroud)

实际上,它们最终完全相同:返回对当前CDI托管bean实例的代理引用,并自动创建bean实例(如果范围中尚不存在).

但是他们做的有点不同:BeanManager#getReference()总是创建一个全新的代理实例,而Context#get()如果之前已经创建过,则重用现有的代理实例.当在现有TestBean实例的action方法中执行上述代码时,这是显而易见的:

System.out.println(testBean1 == testBean2); // false
System.out.println(testBean1 == this); // false
System.out.println(testBean2 == this); // true
Run Code Online (Sandbox Code Playgroud)

的javadocContext#get()是非常明确的在此:

返回某个上下文类型的现有实例,或通过调用Contextual.create(CreationalContext)创建一个新实例并返回新实例.

javadoc中BeanManager#getReference()不够明确在此:

获得某个bean和bean的某个bean类型的上下文引用.

这让我感到困惑.你什么时候使用这一个?对于这两种方式,无论如何都需要一个Bean<T>实例,bean类和bean范围随时可用,这是额外的参数.我无法想象为什么在这种特殊情况下他们需要外部供应.

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

这让我想到了以下问题:什么时候BeanManager#getReference()更有用Context#get()呢?它通常以片段形式显示,更经常被推荐为解决方案,但它只会在不存在的情况下不必要地创建新的代理.

Asi*_*tto 36

beanManager#getReference为您提供了客户端代理的新实例,但客户端代理会将方法调用转发到特定上下文的当前上下文实例.获得代理并保留代理后,将在当前实例上调用方法调用(例如,当前请求).如果上下文实例不可序列化,它也很有用 - 客户端代理将在反序列化之后重新连接.

BeanManager#getContext获取没有客户端代理的目标实例.您仍然可以在类名中看到Weld的代理,但这是一个增强的子类,提供拦截和修饰.如果bean没有被拦截也没有装饰,那么这将是给定bean的普通实例.

通常(1)更合适,除非您有一个特殊的用例,您需要直接访问目标实例(例如访问其字段).

或者换句话说

1)BeanManager#getReference将返回一个'Contextual Reference',其中包含bean的正常范围代理.如果一个bean有@SessionScopedas

@SessionScoped User user;
Run Code Online (Sandbox Code Playgroud)

然后,上下文引用用户将"指向" 每个调用的当前会话的相应用户实例("上下文实例").user.getName()来自两个不同Web浏览器的两种不同调用将为您提供不同的答案.

2)Context#get()将返回一个没有正常范围代理的内部'Contextual Instance'.这通常不是用户应该自称的.如果User user以这种方式获得"Bob"并将其存储在@ApplicationScopedbean或静态变量中,那么它将始终保持为用户"Bob" - 即使是来自其他浏览器的Web请求!您将获得一个直接的非代理实例.

  • 回到具体问题,好吧,`Context#get()`因此实际上不返回任何代理.我相信我被增强的子类误导了.这使得现在确实更有意义.如果您确实需要一个可序列化的代理,请使用`BeanManager#getReference()`.如果您不需要可序列化代理和/或需要通过反射来探索实例,那么使用`Context#get()`.谢谢你的回答! (2认同)