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

时间:2013-11-18 12:58:57

标签: cdi managed-bean

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

  1. BeanManager#getReference(),更常见的是Context#get() 片段:

    Bean<TestBean> bean = (Bean<TestBean>) beanManager.resolve(beanManager.getBeans(TestBean.class));
    TestBean testBean1 = (TestBean) beanManager.getReference(bean, bean.getBeanClass(), beanManager.createCreationalContext(bean));
    
  2. javadoc,在片段中不常显示:

    Bean<TestBean> bean = (Bean<TestBean>) beanManager.resolve(beanManager.getBeans(TestBean.class));
    TestBean testBean2 = beanManager.getContext(bean.getScope()).get(bean, beanManager.createCreationalContext(bean));
    
  3. 在效果中,它们最终完全相同:返回对当前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()的{​​{3}}非常明确:

      

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

    虽然BeanManager#getReference()的{​​{3}}对此不够明确:

      

    获取某个bean和bean的某个bean类型的上下文引用。

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

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

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

3 个答案:

答案 0 :(得分:35)

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

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

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

或换句话说

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

@SessionScoped User user;

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

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

答案 1 :(得分:1)

我有一个Singleton,我使用getReference()方法来获取引用。即使单例已经初始化,每次使用getReference()时,通过getReference()创建的代理称为@PostConstruct。

@Startup
@ApplicationScoped
@Singleton

@PostConstruct
private void initialize() {}

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

答案 2 :(得分:0)

当将CDI与javafx集成时,这非常有用,我需要引用正确的作用域对象原因,而不是依赖范围的代理......

我使用了一个生成器方法来获取一个注入到控制器中的javaFX节点,如下所示:

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

但是当使用BeanManager#getReference()代理时,我回来“吃掉”FXMLLoader设置的所有值,getContext.get()方式解决了它。

Thnx for this