2011-12-07 59 views
7

我正在尝试使用BeanManager而不是实例.select()。get()来创建CDI托管bean的实例。如何通过BeanManager创建和销毁CDI(焊接)托管Bean?

这是建议作为解决方案,我已与ApplicationScoped豆和他们的家属的垃圾回收问题 - 请参阅CDI Application and Dependent scopes can conspire to impact garbage collection?背景和此建议的解决方法。

如果您在ApplicationScoped bean上使用实例编程查找方法,那么Instance对象和从它获取的所有bean最终都依赖于ApplicationScoped bean,因此共享它的生命周期。但是,如果使用BeanManager创建Bean,则您有一个Bean实例本身的句柄,显然可以明确地销毁它,我知道这意味着它将被GCed。

我目前的做法是创建一个BeanManagerUtil类中的bean,并返回豆,实例和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); 
    } 
} 

由此看来,在调用代码,然后我就可以得到实际情况下,把它放在一个地图以供稍后检索,并且正常使用:

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

当我用它做,我可以查找的破坏包装并在其上调用destroy(),和豆和应该清理其家属:

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

然而,运行几个工人,离开我的JBoss(7.1.0.Alpha1-快照)20分钟左右的时间,我可以看到GC发生

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

然而,一个JMAP直方图仍然显示unGCed,老工人和他们依赖的实例在四处闲逛。我错过了什么?

通过调试,我可以看到创建的bean的上下文字段具有正确的Worker类型的上下文,没有incompleteInstances并且没有parentDependentInstances。它有一些dependentInstances,这与worker的字段中预期的一样。

Worker上的这些字段中的一个实际上是一个实例,当我将该字段与通过编程实例查找检索的Worker的字段进行比较时,它们具有稍微不同的CreationalContext构成。通过Instance查找的Worker上的Instance字段在incompleteInstances下具有worker本身,而从BeanManager中检索的Worker上的Instance字段没有。它们都有相同的parentDependentInstances和dependentInstances。

这表明我没有正确反映实例的检索。这是否可以促成没有破坏?

最后,在调试时,我可以看到在我的DestructibleBeanInstance.destroy()中调用了bean.destroy(),并且这通过ManagedBean.destroy,我可以看到依赖对象被作为.release的一部分销毁()。但是他们仍然没有收集垃圾!

对此的任何帮助将非常感谢!谢谢。

回答

2

我会改变你粘贴的代码中的一些东西。

  1. 使该类成为一个普通的java类,没有注入并传入BeanManager。有些东西可能会搞砸了。这不太可能,但可能。
  2. 使用BeanManager.createCreationContext(null)创建一个新的CreationalContext,它将给你一个基本的依赖范围,当你完成调用CreationalContext.release()时你可以释放它。

您可以得到一切通过调用CreationalContext释放方法,你已经在DestructibleBeanInstance正常工作,你所希望的方式,假设有在CreationalContext没有其他Beans那会弄乱你的应用程序。先尝试一下,看看它是否会让事情变得糟糕。

+0

再次感谢Jason。我做了你所建议的更改,但仍未看到任何垃圾回收。然而,当我等待_full_ GC时,两种方法都会导致收集对象 - 成功!以前在完整的GC中情况并非如此。 如果你有时间,请你能解释'.createCreationContext(null)'和'.createCreationContext(bean)'之间的区别吗?我已经在文档中看到前者编写扩展,但是我认为这是为了当bean类型的版本(如果你明白我的意思)不存在的时候?再次感谢您的帮助。 –

+0

根据规范,它为您提供了一个非上下文的bean实例,所以除了创建它的那部分代码之外,不应该有任何其他引用它的东西。我问皮特缪尔一些额外的见解,但我还没有听到。 – LightGuard

2

只能在注入bean以外的其他类时才传入null。就你而言,你正在注入一个bean。然而,我仍然希望GC能够在这种情况下工作,那么你可以在Weld问题跟踪器中提交一个JIRA和一个测试用例以及重现步骤吗?

1

解决您的问题的更好的方法可能是使用动态代理来处理bean销毁。获得bean类实例程序的代码是:

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; 
} 

这样用户代码更简单。它不需要照顾破坏。 这个approuch的限制是,当接收到的beanType不是接口并且已解析的bean类是@Dependent的情况不受支持。但是很容易在周围工作。只需使用一个界面。 我测试了这个代码(使用JBoss 7.1.1),它也适用于依赖有状态会话bean。

+0

非常好的主意,谢谢@Readren –