Canonical way to obtain CDI managed bean instance: BeanManager#getReference() vs Context#get()
Asked Answered
O

3

41

I figured that there are two general ways to obtain an auto-created CDI managed bean instance via BeanManager when having solely a Bean<T> to start with (which is created based on Class<T>):

  1. By BeanManager#getReference(), which is more often shown in snippets:

    Bean<TestBean> bean = (Bean<TestBean>) beanManager.resolve(beanManager.getBeans(TestBean.class));
    TestBean testBean1 = (TestBean) beanManager.getReference(bean, bean.getBeanClass(), beanManager.createCreationalContext(bean));
    
  2. By Context#get(), which is less often shown in snippets:

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

In effects, they do ultimately exactly the same thing: returning a proxied reference to the current CDI managed bean instance and auto-creates the bean instance if it doesn't already exist in the scope.

But they do it a bit differently: the BeanManager#getReference() always creates a whole new proxy instance, while the Context#get() reuses an existing proxy instance if already created before. This is evident when the above code is executed in an action method of an existing TestBean instance:

System.out.println(testBean1 == testBean2); // false
System.out.println(testBean1 == this); // false
System.out.println(testBean2 == this); // true

The javadoc of Context#get() is very explicit in this:

Return an existing instance of certain contextual type or create a new instance by calling Contextual.create(CreationalContext) and return the new instance.

while the javadoc of BeanManager#getReference() is not explicit enough on this:

Obtains a contextual reference for a certain bean and a certain bean type of the bean.

This got me confused. When do you use the one or the other? For the both ways you need a Bean<T> instance anyway, from which the bean class and bean scope is readily available which is required as additional argument. I can't imagine why they would need to be supplied externally in this specific case.

I can imagine that Context#get() is more memory efficient as it doesn't unnecessarily create another proxy instance referring the very same underlying bean instance, but just finds and reuses an existing proxy instance.

This puts me to the following question: when exactly is the BeanManager#getReference() more useful than Context#get()? It's more often shown in snippets and more often recommended as solution, but it only unnecessarily creates a new proxy even when one already exists.

Orban answered 18/11, 2013 at 12:58 Comment(0)
V
36

beanManager#getReference gives you a new instance of a client proxy but the client proxy will forward method calls to the current contextual instance of a particular context. Once you obtain the proxy and keep it and the method calls will be invoked on the current instance (e.g. current request). It is also useful if the contextual instance is not serializable - the client proxy will be and will reconnect after you deserialize it.

BeanManager#getContext obtains the target instance without a client proxy. You may still see a Weld's proxy in the class name but that is an enhanced subclass that provides interception and decoration. If the bean is not intercepted nor decorated this will be a plain instance of the given bean.

Usually (1) is more suitable unless you have a special use-case where you need to access the target instance directly (e.g. to access its fields).

Or in other words

1) BeanManager#getReference will return a 'Contextual Reference', with a normal scoping proxy for the bean. If a bean have with @SessionScoped as

@SessionScoped User user;

Then the contextual reference user will 'point' to the respective User instance (the 'Contextual Instance') of the current session for each invocation. Two different invocations to user.getName() from two different web browsers will give you different answers.

2) Context#get() will return a the internal 'Contextual Instance' without the normal scoping proxy.This is usually nothing a user should call himself. If you get the User user for "Bob" that way and store it in an @ApplicationScoped bean or in a static variable, then it will always remain to be the user "Bob" - even for web requests from other browsers! You will get a direct, non-proxied instance.

Vinna answered 19/11, 2013 at 1:25 Comment(4)
I've updated the answer, hope now you will get true answer.some time links are fantastic for answerVinna
Coming back to the concrete question, okay, the Context#get() thus actually doesn't return any proxy. I believe I'm being misled by the enhanced subclass. This makes now indeed more sense. If you actually need a serializable proxy at hands, use BeanManager#getReference(). If you don't need a serializable proxy and/or need to explore the instance by reflection, then use Context#get(). Thank you for the answer!Orban
Yeah you are absolutely right third comment. No need to say thanks, you are welcomeVinna
Also think printed out answers - links without summaries are useless there.Ame
P
1

I have a Singleton that I was using the getReference() method to get the reference to. Even though the singleton was already initialized, the proxy created through getReference() called the @PostConstruct each time getReference() was used.

@Startup
@ApplicationScoped
@Singleton

@PostConstruct
private void initialize() {}

By switching to the getContext().get() method, the unnecessary @PostConstruct proxy calls are no longer made.

Provided answered 3/2, 2015 at 21:41 Comment(1)
Interesting side effect. I believe this must be a bug in CDI implementation used. Which CDI impl/version was you using?Orban
B
0

This was very helpful when integrating CDI with javafx, the thing was that I needed a reference to the right scoped object cause and not the proxy of the dependent scope...

I used a producer method to get a javaFX Node that gets injected into the controler like so:

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

but when using the BeanManager#getReference() the proxy I get back "eats" all the values that are set by the FXMLLoader, the getContext.get() way solved it.

Thnx for this

Bereniceberenson answered 17/1, 2014 at 11:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.