BeanManager always returns same reference

2019-07-26 08:15发布

问题:

I am creating a custom CDI scope and am using the BeanManager to get an injection of my NavigationHandler custom class. But the beans it returns are quite strange.

So I use the BeanManager that way :

public class ScreenContext implements Context
{
    private NavigationHandler getNavigationHandler()
    {
        final Set<Bean<?>> beans = m_beanManager.getBeans(NavigationHandler.class);
        final Bean<?> bean = m_beanManager.resolve(beans);

        NavigationHandler reference =
            (NavigationHandler) m_beanManager.getReference(bean, NavigationHandler.class,
                m_beanManager.createCreationalContext(bean));

        System.out.println("Found "+reference+" (hash="+reference.hashCode()+")");
        return reference;
    }
    ...
}

I expect, when I use my project using two different browsers, to get two different NavigationHandler, which are defined that way :

@Named
@WindowScoped
public class NavigationHandler
    implements Serializable, INavigationHandlerController

But my debugger returns true when I test reference1==reference2. I also get strange hash codes :

Found NavigationHandler@593e785f (hash=1261587818)
Found NavigationHandler@b6d51bd (hash=1261587818)

I don't understand why the hashes used in the toString() are different, but the hash used in hashCode() are the same.

回答1:

I think I figured out the reason for these two linked problems, that was a tricky one !

m_beanManager.getReference(..) does not return the NavigationHandler instance, but a proxy which is supposed to select and act as the correct NavigationHandler among the existing ones in the scope's context.

Link to understand the concept of Proxy/Context/BeanManager: https://developer.jboss.org/blogs/stuartdouglas/2010/10/12/weld-cdi-and-proxies

So my getNavigationHandler() method is not suited for the work : my pool which calls this method will hold NavigationHandler proxies instead of NavigationHandlers. Because my pool is not an @Injected field, the proxy will not get automatically updated by CDI, hence the reference returned is always the one from the last context actively used by a proxy.

For the same reason in this output:

Found NavigationHandler@593e785f (hash=1261587818)
Found NavigationHandler@b6d51bd (hash=1261587818)

In one case I get the hash of the NavigationHandler instance, and in the other case I get the hash of the NavigationHandler's proxy. Yet I don't know which one is which. I am willing to believe the proxy's toString() is used, as beanManager.getReference(..) is supposed to serve a new proxy each time, and the hashCode is supposed to be practically unique for object each instances.

Link that says every instance's hashcode is unique hashcode and cannot change over time: http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode%28%29

So the correct way to implement getNavigationHandler() is:

private getNavigationHandlergetgetNavigationHandler()
{
    final Set<Bean<?>> beans = m_beanManager.getBeans(getNavigationHandler.class);
    final Bean<?> bean = m_beanManager.resolve(beans);

    /* Works : pure reference (not proxied) */
    Class<? extends Annotation> scopeType = bean.getScope();
    Context context = m_beanManager.getContext(scopeType);
    CreationalContext<?> creationalContext = m_beanManager.createCreationalContext(bean);
    // Casts below are necessary since inheritence does not work for templates
    getNavigationHandler reference =
        context.get((Bean<NavigationHandler>) bean, (CreationalContext<NavigationHandler>) creationalContext);

    return reference;
}

Link that explains the difference between beanManager.getReference(..) and beanManager.getContext(..).get(..): Canonical way to obtain CDI managed bean instance: BeanManager#getReference() vs Context#get()