How to create and destroy CDI (Weld) Managed Beans via the BeanManager?
Asked Answered
H

3

8

I'm trying to create instances of CDI managed beans using the BeanManager rather than Instance .select().get().

This was suggested as a workaround to an issue I've been having with ApplicationScoped beans and garbage collection of their dependents - see CDI Application and Dependent scopes can conspire to impact garbage collection? for background and this suggested workaround.

If you use the Instance programmatic lookup method on an ApplicationScoped bean, the Instance object and any beans you get from it are all ultimately dependent on the ApplicationScoped bean, and therefore share it's lifecycle. If you create beans with the BeanManager, however, you have a handle on the Bean instance itself, and apparently can explicitly destroy it, which I understand means it will be GCed.

My current approach is to create the bean within a BeanManagerUtil class, and return a composite object of Bean, instance, and CreationalContext:

public class BeanManagerUtil {

    @Inject private BeanManager beanManager;

    @SuppressWarnings("unchecked")
    public <T> DestructibleBeanInstance<T> getDestructibleBeanInstance(final Class<T> type,
            final Annotation... qualifiers) {

        DestructibleBeanInstance<T> result = null;
        Bean<T> bean = (Bean<T>) beanManager.resolve(beanManager.getBeans(type, qualifiers));
        if (bean != null) {
            CreationalContext<T> creationalContext = beanManager.createCreationalContext(bean);
            if (creationalContext != null) {
                T instance = bean.create(creationalContext);
                result = new DestructibleBeanInstance<T>(instance, bean, creationalContext);
            }
        }
        return result;
    }
}

public class DestructibleBeanInstance<T> {

    private T instance;
    private Bean<T> bean;
    private CreationalContext<T> context;

    public DestructibleBeanInstance(T instance, Bean<T> bean, CreationalContext<T> context) {
        this.instance = instance;
        this.bean = bean;
        this.context = context;
    }

    public T getInstance() {
        return instance;
    }    

    public void destroy() {
        bean.destroy(instance, context);
    }
}

From this, in the calling code, I can then get the actual instance, put it in a map for later retrieval, and use as normal:

private Map<Worker, DestructibleBeanInstance<Worker>> beansByTheirWorkers =
    new HashMap<Worker, DestructibleBeanInstance<Worker>>();
...
DestructibleBeanInstance<Worker> destructible =
        beanUtils.getDestructibleBeanInstance(Worker.class, workerBindingQualifier);
Worker worker = destructible.getInstance();
...

When I'm done with it, I can lookup the destructible wrapper and call destroy() on it, and the bean and its dependents should be cleaned up:

DestructibleBeanInstance<JamWorker> workerBean =
        beansByTheirWorkers.remove(worker);
workerBean.destroy();
worker = null;

However, after running several workers and leaving my JBoss (7.1.0.Alpha1-SNAPSHOT) for 20 minutes or so, I can see GC occurring

2011.002: [GC
Desired survivor size 15794176 bytes, new threshold 1 (max 15)
1884205K->1568621K(3128704K), 0.0091281 secs]

Yet a JMAP histogram still shows the old workers and their dependent instances hanging around, unGCed. What am I missing?

Through debugging, I can see that the context field of the bean created has the contextual of the correct Worker type, no incompleteInstances and no parentDependentInstances. It has a number of dependentInstances, which are as expected from the fields on the worker.

One of these fields on the Worker is actually an Instance, and when I compare this field with that of a Worker retrieved via programmatic Instance lookup, they have a slightly different CreationalContext makeup. The Instance field on the Worker looked up via Instance has the worker itself under incompleteInstances, whereas the Instance field on the Worker retrieved from the BeanManager doesn't. They both have identical parentDependentInstances and dependentInstances.

This suggests to me that I haven't mirrored the retrieval of the instance correctly. Could this be contributing to the lack of destruction?

Finally, when debugging, I can see bean.destroy() being called in my DestructibleBeanInstance.destroy(), and this goes through to ManagedBean.destroy, and I can see dependent objects being destroyed as part of the .release(). However they still don't get garbage collected!

Any help on this would be very much appreciated! Thanks.

Holiday answered 7/12, 2011 at 17:51 Comment(0)
L
4

I'd change a couple of things in the code you pasted.

  1. Make that class a regular java class, no injection and pass in the BeanManager. Something could be messing up that way. It's not likely, but possibly.
  2. Create a new CreationalContext by using BeanManager.createCreationContext(null) which will give you essentially a dependent scope which you can release when you're done by calling CreationalContext.release().

You may be able to get everything to work correctly the way you want by calling the release method on the CreationalContext you already have in the DestructibleBeanInstance, assuming there's no other Beans in that CreationalContext that would mess up your application. Try that first and see if it messes things up.

Luncheonette answered 8/12, 2011 at 7:19 Comment(2)
Thanks again, Jason. I made the changes you suggested, but still wasn't seeing any garbage collection. However when I waited for a full GC, both approaches led to the objects being collected - success! This wasn't the case on a full GC before. If you have time, please could you explain the difference between .createCreationContext(null) and .createCreationContext(bean)? I've seen the former in the docs for writing an extension, but I thought that was for when the 'bean' version of the type (if you see what I mean) didn't already exist? Thanks again for the help.Holiday
According to the spec it gives you a non-contextual instance of the bean, so there shouldn't be anything else that has reference to it besides that portion of code that was run to create it. I asked Pete Muir for some additional insight, but I haven't heard back yet.Luncheonette
D
2

Passing in null should only be done when you injecting some class other than a bean. In your case, you are injecting a bean. However I would still expect GC to work in this case, so could you file a JIRA in the Weld issue tracker with a test case and steps to reproduce?

Despotism answered 14/12, 2011 at 10:3 Comment(0)
D
2

A nicer way solve your problem could be to use a dynamic proxy to handle the bean destruction. The code to obtain a bean class instance programaticaly would be:

public static <B> B getBeanClassInstance(BeanManager beanManager, Class<B> beanType, Annotation... qualifiers) {
    final B result;
    Set<Bean<?>> beans = beanManager.getBeans(beanType, qualifiers);
    if (beans.isEmpty())
        result = null;
    else {
        final Bean<B> bean = (Bean<B>) beanManager.resolve(beans);
        if (bean == null)
            result = null;
        else {
            final CreationalContext<B> cc = beanManager.createCreationalContext(bean);
            final B reference = (B) beanManager.getReference(bean, beanType, cc);
            Class<? extends Annotation> scope = bean.getScope();
            if (scope.equals(Dependent.class)) {
                if (beanType.isInterface()) {
                    result = (B) Proxy.newProxyInstance(bean.getBeanClass().getClassLoader(), new Class<?>[] { beanType,
                            Finalizable.class }, new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if (method.getName().equals("finalize")) {
                                bean.destroy(reference, cc);
                            }
                            try {
                                return method.invoke(reference, args);
                            } catch (InvocationTargetException e) {
                                throw e.getCause();
                            }
                        }
                    });
                } else
                    throw new IllegalArgumentException("If the resolved bean is dependent scoped then the received beanType should be an interface in order to manage the destruction of the created dependent bean class instance.");
            } else
                result = reference;
        }
    }
    return result;
}

interface Finalizable {
    void finalize() throws Throwable;
}

This way the user code is simpler. It doesnt have to take care of the destruction. The limitation of this approuch is that the case when the received beanType isn't an interface and the resolved bean class is @Dependent is not supported. But is easy to work arround. Just use an interface. I tested this code (with JBoss 7.1.1) and it works also for dependent stateful session beans.

Dickman answered 3/3, 2013 at 9:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.